5000d023a4
The "-f" dump option allows a dump of the Heimdal
KDC in a format that the MIT kdb5_util command can
load into a MIT KDC's database.
This makes transitioning from the Heimdal KDC to
the current MIT one feasible without having to
re-create the KDC database from scratch.
glebius@ did the initial work, cherry picking these
commits from the Heimdal sources on github and then doing
extensive merge conflict resolution and other fixes so
that it would build.
Heimdal commit fca5399 authored by Nico Williams:
Initial commit for second approach for multiple kvno. NOT TESTED!
Heimdal commit 57f1545 authored by Nico Williams:
Add support for writing to KDB and dumping HDB to MIT KDB dump format
Before this change Heimdal could read KDBs. Now it can write to
them too.
Heimdal can now also dump HDBs (including KDBs) in MIT format,
which can then be imported with kdb5_util load.
This is intended to help in migrations from MIT to Heimdal by
allowing migrations from Heimdal to MIT so that it is possible
to rollback from Heimdal to MIT should there be any issues. The
idea is to allow a) running Heimdal kdc/kadmind with a KDB, or
b) running Heimdal with an HDB converted from a KDB and then
rollback by dumping the HDB and loading a KDB.
Note that not all TL data types are supported, only two: last
password change and modify-by. This is the minimum necessary.
PKINIT users may need to add support for KRB5_TL_USER_CERTIFICATE,
and for databases with K/M history we may need to add KRB5_TL_MKVNO
support.
This resulted in a Heimdal kadmin that would dump
the KDC database in MIT format. However, there
were issues when this dump was loaded into the
current MIT KDC in FreeBSD current/15.0.
The changes I did to make the dump more useful are listed below:
When "-f MIT" is used for "kadmin -l dump" it writes
the dump out in MIT format. This dump format is understood
by the MIT kdb5_util command. The patch modifies the above
so that the MIT KDC's master key keytab file can be provided
as the argument to "-f" so that the principals are re-encrypted in
it. This allows any principal with at least one strong encryption
type key to work without needing a change_password.
The strong encryption types supported by the Heimdal KDC are:
aes256-cts-hmac-sha1-96
aes128-cts-hmac-sha1-96
The issues my changes address are:
- If there are weak encryption keys in a principal's entry,
MIT's kadmin.local will report that the principcal's entry
is incomplete or corrupted.
- The keys are encrypted in Heimdal's master key. The
"-d" option can be used on the "kadmin -l dump" to
de-encrypt them, but the passwords will not work on the
current MIT KDC.
To try and deal with the above issues, this patch modied the above to:
- Does not dump the weak keys.
- Re-encrypts the strong keys in MIT's master key if the argument
to "-f" is actually a filename which holds the MIT KDC's
master key keytab and not "MIT".
- For principals that only have weak keys, it generates
a fake strong key. This key will not work on the MIT
KDC, but the principal entry will work once a
change_password is done to it.
- It always generates a "modified_by" entry, faking one if
not already present in the Heimdal KDC database.
This was necessary, since the MIT kadmin will
report that the principal entry is "incomplete or
corrupted" without one.
It also fixed a problem where "get principal" no longer
worked after the initial patch was applied.
A man page update will be done as a separate commit.
I believe this commit is acceptable since the Heimdal
sources are now essentially deprecated in favor of the
MIT sources and that this new "-f" patch simplifies
the transition to the MIT KDC.
Discussed with: glebius, cy
MFC after: 3 days
491 lines
12 KiB
C
491 lines
12 KiB
C
/*
|
|
* Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan
|
|
* (Royal Institute of Technology, Stockholm, Sweden).
|
|
* All rights reserved.
|
|
*
|
|
* Portions Copyright (c) 2009 Apple Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* 3. Neither the name of the Institute nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "krb5_locl.h"
|
|
#include "hdb_locl.h"
|
|
|
|
#ifdef HAVE_DLFCN_H
|
|
#include <dlfcn.h>
|
|
#endif
|
|
|
|
/*! @mainpage Heimdal database backend library
|
|
*
|
|
* @section intro Introduction
|
|
*
|
|
* Heimdal libhdb library provides the backend support for Heimdal kdc
|
|
* and kadmind. Its here where plugins for diffrent database engines
|
|
* can be pluged in and extend support for here Heimdal get the
|
|
* principal and policy data from.
|
|
*
|
|
* Example of Heimdal backend are:
|
|
* - Berkeley DB 1.85
|
|
* - Berkeley DB 3.0
|
|
* - Berkeley DB 4.0
|
|
* - New Berkeley DB
|
|
* - LDAP
|
|
*
|
|
*
|
|
* The project web page: http://www.h5l.org/
|
|
*
|
|
*/
|
|
|
|
const int hdb_interface_version = HDB_INTERFACE_VERSION;
|
|
|
|
static struct hdb_method methods[] = {
|
|
#if HAVE_DB1 || HAVE_DB3
|
|
{ HDB_INTERFACE_VERSION, "db:", hdb_db_create},
|
|
#endif
|
|
#if HAVE_DB1
|
|
{ HDB_INTERFACE_VERSION, "mit-db:", hdb_mdb_create},
|
|
#endif
|
|
#if HAVE_NDBM
|
|
{ HDB_INTERFACE_VERSION, "ndbm:", hdb_ndbm_create},
|
|
#endif
|
|
{ HDB_INTERFACE_VERSION, "keytab:", hdb_keytab_create},
|
|
#if defined(OPENLDAP) && !defined(OPENLDAP_MODULE)
|
|
{ HDB_INTERFACE_VERSION, "ldap:", hdb_ldap_create},
|
|
{ HDB_INTERFACE_VERSION, "ldapi:", hdb_ldapi_create},
|
|
#endif
|
|
#ifdef HAVE_SQLITE3
|
|
{ HDB_INTERFACE_VERSION, "sqlite:", hdb_sqlite_create},
|
|
#endif
|
|
{0, NULL, NULL}
|
|
};
|
|
|
|
#if HAVE_DB1 || HAVE_DB3
|
|
static struct hdb_method dbmetod =
|
|
{ HDB_INTERFACE_VERSION, "", hdb_db_create };
|
|
#elif defined(HAVE_NDBM)
|
|
static struct hdb_method dbmetod =
|
|
{ HDB_INTERFACE_VERSION, "", hdb_ndbm_create };
|
|
#endif
|
|
|
|
|
|
krb5_error_code
|
|
hdb_next_enctype2key(krb5_context context,
|
|
const hdb_entry *e,
|
|
krb5_enctype enctype,
|
|
Key **key)
|
|
{
|
|
Key *k;
|
|
|
|
for (k = *key ? (*key) + 1 : e->keys.val;
|
|
k < e->keys.val + e->keys.len;
|
|
k++)
|
|
{
|
|
if(k->key.keytype == enctype){
|
|
*key = k;
|
|
return 0;
|
|
}
|
|
}
|
|
krb5_set_error_message(context, KRB5_PROG_ETYPE_NOSUPP,
|
|
"No next enctype %d for hdb-entry",
|
|
(int)enctype);
|
|
return KRB5_PROG_ETYPE_NOSUPP; /* XXX */
|
|
}
|
|
|
|
krb5_error_code
|
|
hdb_enctype2key(krb5_context context,
|
|
hdb_entry *e,
|
|
krb5_enctype enctype,
|
|
Key **key)
|
|
{
|
|
*key = NULL;
|
|
return hdb_next_enctype2key(context, e, enctype, key);
|
|
}
|
|
|
|
void
|
|
hdb_free_key(Key *key)
|
|
{
|
|
memset(key->key.keyvalue.data,
|
|
0,
|
|
key->key.keyvalue.length);
|
|
free_Key(key);
|
|
free(key);
|
|
}
|
|
|
|
|
|
krb5_error_code
|
|
hdb_lock(int fd, int operation)
|
|
{
|
|
int i, code = 0;
|
|
|
|
for(i = 0; i < 3; i++){
|
|
code = flock(fd, (operation == HDB_RLOCK ? LOCK_SH : LOCK_EX) | LOCK_NB);
|
|
if(code == 0 || errno != EWOULDBLOCK)
|
|
break;
|
|
sleep(1);
|
|
}
|
|
if(code == 0)
|
|
return 0;
|
|
if(errno == EWOULDBLOCK)
|
|
return HDB_ERR_DB_INUSE;
|
|
return HDB_ERR_CANT_LOCK_DB;
|
|
}
|
|
|
|
krb5_error_code
|
|
hdb_unlock(int fd)
|
|
{
|
|
int code;
|
|
code = flock(fd, LOCK_UN);
|
|
if(code)
|
|
return 4711 /* XXX */;
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
hdb_free_entry(krb5_context context, hdb_entry_ex *ent)
|
|
{
|
|
Key *k;
|
|
int i;
|
|
|
|
if (ent->free_entry)
|
|
(*ent->free_entry)(context, ent);
|
|
|
|
for(i = 0; i < ent->entry.keys.len; i++) {
|
|
k = &ent->entry.keys.val[i];
|
|
|
|
memset (k->key.keyvalue.data, 0, k->key.keyvalue.length);
|
|
}
|
|
free_hdb_entry(&ent->entry);
|
|
}
|
|
|
|
krb5_error_code
|
|
hdb_foreach(krb5_context context,
|
|
HDB *db,
|
|
unsigned flags,
|
|
hdb_foreach_func_t func,
|
|
void *data)
|
|
{
|
|
krb5_error_code ret;
|
|
hdb_entry_ex entry;
|
|
ret = db->hdb_firstkey(context, db, flags, &entry);
|
|
if (ret == 0)
|
|
krb5_clear_error_message(context);
|
|
while(ret == 0){
|
|
ret = (*func)(context, db, &entry, data);
|
|
hdb_free_entry(context, &entry);
|
|
if(ret == 0)
|
|
ret = db->hdb_nextkey(context, db, flags, &entry);
|
|
}
|
|
if(ret == HDB_ERR_NOENTRY)
|
|
ret = 0;
|
|
return ret;
|
|
}
|
|
|
|
krb5_error_code
|
|
hdb_check_db_format(krb5_context context, HDB *db)
|
|
{
|
|
krb5_data tag;
|
|
krb5_data version;
|
|
krb5_error_code ret, ret2;
|
|
unsigned ver;
|
|
int foo;
|
|
|
|
ret = db->hdb_lock(context, db, HDB_RLOCK);
|
|
if (ret)
|
|
return ret;
|
|
|
|
tag.data = (void *)(intptr_t)HDB_DB_FORMAT_ENTRY;
|
|
tag.length = strlen(tag.data);
|
|
ret = (*db->hdb__get)(context, db, tag, &version);
|
|
ret2 = db->hdb_unlock(context, db);
|
|
if(ret)
|
|
return ret;
|
|
if (ret2)
|
|
return ret2;
|
|
foo = sscanf(version.data, "%u", &ver);
|
|
krb5_data_free (&version);
|
|
if (foo != 1)
|
|
return HDB_ERR_BADVERSION;
|
|
if(ver != HDB_DB_FORMAT)
|
|
return HDB_ERR_BADVERSION;
|
|
return 0;
|
|
}
|
|
|
|
krb5_error_code
|
|
hdb_init_db(krb5_context context, HDB *db)
|
|
{
|
|
krb5_error_code ret, ret2;
|
|
krb5_data tag;
|
|
krb5_data version;
|
|
char ver[32];
|
|
|
|
ret = hdb_check_db_format(context, db);
|
|
if(ret != HDB_ERR_NOENTRY)
|
|
return ret;
|
|
|
|
ret = db->hdb_lock(context, db, HDB_WLOCK);
|
|
if (ret)
|
|
return ret;
|
|
|
|
tag.data = (void *)(intptr_t)HDB_DB_FORMAT_ENTRY;
|
|
tag.length = strlen(tag.data);
|
|
snprintf(ver, sizeof(ver), "%u", HDB_DB_FORMAT);
|
|
version.data = ver;
|
|
version.length = strlen(version.data) + 1; /* zero terminated */
|
|
ret = (*db->hdb__put)(context, db, 0, tag, version);
|
|
ret2 = db->hdb_unlock(context, db);
|
|
if (ret) {
|
|
if (ret2)
|
|
krb5_clear_error_message(context);
|
|
return ret;
|
|
}
|
|
return ret2;
|
|
}
|
|
|
|
#ifdef HAVE_DLOPEN
|
|
|
|
/*
|
|
* Load a dynamic backend from /usr/heimdal/lib/hdb_NAME.so,
|
|
* looking for the hdb_NAME_create symbol.
|
|
*/
|
|
|
|
static const struct hdb_method *
|
|
find_dynamic_method (krb5_context context,
|
|
const char *filename,
|
|
const char **rest)
|
|
{
|
|
static struct hdb_method method;
|
|
struct hdb_so_method *mso;
|
|
char *prefix, *path, *symbol;
|
|
const char *p;
|
|
void *dl;
|
|
size_t len;
|
|
|
|
p = strchr(filename, ':');
|
|
|
|
/* if no prefix, don't know what module to load, just ignore it */
|
|
if (p == NULL)
|
|
return NULL;
|
|
|
|
len = p - filename;
|
|
*rest = filename + len + 1;
|
|
|
|
prefix = malloc(len + 1);
|
|
if (prefix == NULL)
|
|
krb5_errx(context, 1, "out of memory");
|
|
strlcpy(prefix, filename, len + 1);
|
|
|
|
if (asprintf(&path, LIBDIR "/hdb_%s.so", prefix) == -1)
|
|
krb5_errx(context, 1, "out of memory");
|
|
|
|
#ifndef RTLD_NOW
|
|
#define RTLD_NOW 0
|
|
#endif
|
|
#ifndef RTLD_GLOBAL
|
|
#define RTLD_GLOBAL 0
|
|
#endif
|
|
|
|
dl = dlopen(path, RTLD_NOW | RTLD_GLOBAL);
|
|
if (dl == NULL) {
|
|
krb5_warnx(context, "error trying to load dynamic module %s: %s\n",
|
|
path, dlerror());
|
|
free(prefix);
|
|
free(path);
|
|
return NULL;
|
|
}
|
|
|
|
if (asprintf(&symbol, "hdb_%s_interface", prefix) == -1)
|
|
krb5_errx(context, 1, "out of memory");
|
|
|
|
mso = (struct hdb_so_method *) dlsym(dl, symbol);
|
|
if (mso == NULL) {
|
|
krb5_warnx(context, "error finding symbol %s in %s: %s\n",
|
|
symbol, path, dlerror());
|
|
dlclose(dl);
|
|
free(symbol);
|
|
free(prefix);
|
|
free(path);
|
|
return NULL;
|
|
}
|
|
free(path);
|
|
free(symbol);
|
|
|
|
if (mso->version != HDB_INTERFACE_VERSION) {
|
|
krb5_warnx(context,
|
|
"error wrong version in shared module %s "
|
|
"version: %d should have been %d\n",
|
|
prefix, mso->version, HDB_INTERFACE_VERSION);
|
|
dlclose(dl);
|
|
free(prefix);
|
|
return NULL;
|
|
}
|
|
|
|
if (mso->create == NULL) {
|
|
krb5_errx(context, 1,
|
|
"no entry point function in shared mod %s ",
|
|
prefix);
|
|
dlclose(dl);
|
|
free(prefix);
|
|
return NULL;
|
|
}
|
|
|
|
method.create = mso->create;
|
|
method.prefix = prefix;
|
|
|
|
return &method;
|
|
}
|
|
#endif /* HAVE_DLOPEN */
|
|
|
|
/*
|
|
* find the relevant method for `filename', returning a pointer to the
|
|
* rest in `rest'.
|
|
* return NULL if there's no such method.
|
|
*/
|
|
|
|
static const struct hdb_method *
|
|
find_method (const char *filename, const char **rest)
|
|
{
|
|
const struct hdb_method *h;
|
|
|
|
for (h = methods; h->prefix != NULL; ++h) {
|
|
if (strncmp (filename, h->prefix, strlen(h->prefix)) == 0) {
|
|
*rest = filename + strlen(h->prefix);
|
|
return h;
|
|
}
|
|
}
|
|
#if defined(HAVE_DB1) || defined(HAVE_DB3) || defined(HAVE_NDBM)
|
|
if (strncmp(filename, "/", 1) == 0
|
|
|| strncmp(filename, "./", 2) == 0
|
|
|| strncmp(filename, "../", 3) == 0)
|
|
{
|
|
*rest = filename;
|
|
return &dbmetod;
|
|
}
|
|
#endif
|
|
|
|
return NULL;
|
|
}
|
|
|
|
krb5_error_code
|
|
hdb_list_builtin(krb5_context context, char **list)
|
|
{
|
|
const struct hdb_method *h;
|
|
size_t len = 0;
|
|
char *buf = NULL;
|
|
|
|
for (h = methods; h->prefix != NULL; ++h) {
|
|
if (h->prefix[0] == '\0')
|
|
continue;
|
|
len += strlen(h->prefix) + 2;
|
|
}
|
|
|
|
len += 1;
|
|
buf = malloc(len);
|
|
if (buf == NULL) {
|
|
krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
|
|
return ENOMEM;
|
|
}
|
|
buf[0] = '\0';
|
|
|
|
for (h = methods; h->prefix != NULL; ++h) {
|
|
if (h != methods)
|
|
strlcat(buf, ", ", len);
|
|
strlcat(buf, h->prefix, len);
|
|
}
|
|
*list = buf;
|
|
return 0;
|
|
}
|
|
|
|
krb5_error_code
|
|
_hdb_keytab2hdb_entry(krb5_context context,
|
|
const krb5_keytab_entry *ktentry,
|
|
hdb_entry_ex *entry)
|
|
{
|
|
entry->entry.kvno = ktentry->vno;
|
|
entry->entry.created_by.time = ktentry->timestamp;
|
|
|
|
entry->entry.keys.val = calloc(1, sizeof(entry->entry.keys.val[0]));
|
|
if (entry->entry.keys.val == NULL)
|
|
return ENOMEM;
|
|
entry->entry.keys.len = 1;
|
|
|
|
entry->entry.keys.val[0].mkvno = NULL;
|
|
entry->entry.keys.val[0].salt = NULL;
|
|
|
|
return krb5_copy_keyblock_contents(context,
|
|
&ktentry->keyblock,
|
|
&entry->entry.keys.val[0].key);
|
|
}
|
|
|
|
/**
|
|
* Create a handle for a Kerberos database
|
|
*
|
|
* Create a handle for a Kerberos database backend specified by a
|
|
* filename. Doesn't create a file if its doesn't exists, you have to
|
|
* use O_CREAT to tell the backend to create the file.
|
|
*/
|
|
|
|
krb5_error_code
|
|
hdb_create(krb5_context context, HDB **db, const char *filename)
|
|
{
|
|
const struct hdb_method *h;
|
|
const char *residual;
|
|
krb5_error_code ret;
|
|
struct krb5_plugin *list = NULL, *e;
|
|
|
|
if(filename == NULL)
|
|
filename = HDB_DEFAULT_DB;
|
|
krb5_add_et_list(context, initialize_hdb_error_table_r);
|
|
h = find_method (filename, &residual);
|
|
|
|
if (h == NULL) {
|
|
ret = _krb5_plugin_find(context, PLUGIN_TYPE_DATA, "hdb", &list);
|
|
if(ret == 0 && list != NULL) {
|
|
for (e = list; e != NULL; e = _krb5_plugin_get_next(e)) {
|
|
h = _krb5_plugin_get_symbol(e);
|
|
if (strncmp (filename, h->prefix, strlen(h->prefix)) == 0
|
|
&& h->interface_version == HDB_INTERFACE_VERSION) {
|
|
residual = filename + strlen(h->prefix);
|
|
break;
|
|
}
|
|
}
|
|
if (e == NULL) {
|
|
h = NULL;
|
|
_krb5_plugin_free(list);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_DLOPEN
|
|
if (h == NULL)
|
|
h = find_dynamic_method (context, filename, &residual);
|
|
#endif
|
|
if (h == NULL)
|
|
krb5_errx(context, 1, "No database support for %s", filename);
|
|
return (*h->create)(context, db, residual);
|
|
}
|