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.

    Support for additional TL data types can be added in
    lib/hdb/hdb-mitdb.c:_hdb_mdb_value2entry() and
    lib/hdb/print.c:entry2mit_string_int().
This commit is contained in:
Nicolas Williams
2012-04-30 03:28:00 -05:00
parent 6c4764fbc7
commit 57f1545a46
10 changed files with 964 additions and 422 deletions

View File

@@ -33,6 +33,17 @@
#include "hprop.h"
extern krb5_error_code _hdb_mdb_value2entry(krb5_context context,
krb5_data *data,
krb5_kvno target_kvno,
hdb_entry *entry);
extern int _hdb_mit_dump2mitdb_entry(krb5_context context,
char *line,
krb5_storage *sp);
/*
can have any number of princ stanzas.
format is as follows (only \n indicates newlines)
@@ -74,19 +85,6 @@ unless no extra data
*/
static int
hex_to_octet_string(const char *ptr, krb5_data *data)
{
size_t i;
unsigned int v;
for(i = 0; i < data->length; i++) {
if(sscanf(ptr + 2 * i, "%02x", &v) != 1)
return -1;
((unsigned char*)data->data)[i] = v;
}
return 2 * i;
}
static char *
nexttoken(char **p)
{
@@ -97,321 +95,116 @@ nexttoken(char **p)
return q;
}
static size_t
getdata(char **p, unsigned char *buf, size_t len)
{
size_t i;
int v;
char *q = nexttoken(p);
i = 0;
while(*q && i < len) {
if(sscanf(q, "%02x", &v) != 1)
break;
buf[i++] = v;
q += 2;
}
return i;
}
static int
getint(char **p)
{
int val;
char *q = nexttoken(p);
sscanf(q, "%d", &val);
return val;
}
#include <kadm5/admin.h>
static void
attr_to_flags(unsigned attr, HDBFlags *flags)
static int
my_fgetln(FILE *f, char **buf, size_t *sz, size_t *len)
{
flags->postdate = !(attr & KRB5_KDB_DISALLOW_POSTDATED);
flags->forwardable = !(attr & KRB5_KDB_DISALLOW_FORWARDABLE);
flags->initial = !!(attr & KRB5_KDB_DISALLOW_TGT_BASED);
flags->renewable = !(attr & KRB5_KDB_DISALLOW_RENEWABLE);
flags->proxiable = !(attr & KRB5_KDB_DISALLOW_PROXIABLE);
/* DUP_SKEY */
flags->invalid = !!(attr & KRB5_KDB_DISALLOW_ALL_TIX);
flags->require_preauth = !!(attr & KRB5_KDB_REQUIRES_PRE_AUTH);
flags->require_hwauth = !!(attr & KRB5_KDB_REQUIRES_HW_AUTH);
flags->server = !(attr & KRB5_KDB_DISALLOW_SVR);
flags->change_pw = !!(attr & KRB5_KDB_PWCHANGE_SERVICE);
flags->client = 1; /* XXX */
}
char *p, *n;
#define KRB5_KDB_SALTTYPE_NORMAL 0
#define KRB5_KDB_SALTTYPE_V4 1
#define KRB5_KDB_SALTTYPE_NOREALM 2
#define KRB5_KDB_SALTTYPE_ONLYREALM 3
#define KRB5_KDB_SALTTYPE_SPECIAL 4
#define KRB5_KDB_SALTTYPE_AFS3 5
static krb5_error_code
fix_salt(krb5_context context, hdb_entry *ent, int key_num)
{
krb5_error_code ret;
Salt *salt = ent->keys.val[key_num].salt;
/* fix salt type */
switch((int)salt->type) {
case KRB5_KDB_SALTTYPE_NORMAL:
salt->type = KRB5_PADATA_PW_SALT;
break;
case KRB5_KDB_SALTTYPE_V4:
krb5_data_free(&salt->salt);
salt->type = KRB5_PADATA_PW_SALT;
break;
case KRB5_KDB_SALTTYPE_NOREALM:
{
size_t len;
size_t i;
char *p;
len = 0;
for (i = 0; i < ent->principal->name.name_string.len; ++i)
len += strlen(ent->principal->name.name_string.val[i]);
ret = krb5_data_alloc (&salt->salt, len);
if (ret)
return ret;
p = salt->salt.data;
for (i = 0; i < ent->principal->name.name_string.len; ++i) {
memcpy (p,
ent->principal->name.name_string.val[i],
strlen(ent->principal->name.name_string.val[i]));
p += strlen(ent->principal->name.name_string.val[i]);
}
salt->type = KRB5_PADATA_PW_SALT;
break;
if (!*buf) {
*buf = malloc(*sz ? *sz : 2048);
if (!*buf)
return ENOMEM;
if (!*sz)
*sz = 2048;
}
case KRB5_KDB_SALTTYPE_ONLYREALM:
krb5_data_free(&salt->salt);
ret = krb5_data_copy(&salt->salt,
ent->principal->realm,
strlen(ent->principal->realm));
if(ret)
return ret;
salt->type = KRB5_PADATA_PW_SALT;
break;
case KRB5_KDB_SALTTYPE_SPECIAL:
salt->type = KRB5_PADATA_PW_SALT;
break;
case KRB5_KDB_SALTTYPE_AFS3:
krb5_data_free(&salt->salt);
ret = krb5_data_copy(&salt->salt,
ent->principal->realm,
strlen(ent->principal->realm));
if(ret)
return ret;
salt->type = KRB5_PADATA_AFS3_SALT;
break;
default:
abort();
*len = 0;
while ((p = fgets(&(*buf)[*len], *sz, f))) {
if (strcspn(*buf, "\r\n") || feof(f)) {
*len = strlen(*buf);
return 0;
}
*len += strlen(&(*buf)[*len]); /* *len should be == *sz */
n = realloc(buf, *sz + (*sz >> 1));
if (!n) {
free(*buf);
*buf = NULL;
*sz = 0;
*len = 0;
return ENOMEM;
}
*buf = n;
*sz += *sz >> 1;
}
return 0;
return 0; /* *len == 0 || no EOL -> EOF */
}
int
mit_prop_dump(void *arg, const char *file)
{
krb5_error_code ret;
char line [2048];
FILE *f;
size_t line_bufsz = 0;
size_t line_len = 0;
char *line = NULL;
int lineno = 0;
FILE *f;
struct hdb_entry_ex ent;
struct prop_data *pd = arg;
krb5_storage *sp = NULL;
krb5_data kdb_ent;
memset(&ent, 0, sizeof (ent));
f = fopen(file, "r");
if(f == NULL)
if (f == NULL)
return errno;
while(fgets(line, sizeof(line), f)) {
char *p = line, *q;
ret = ENOMEM;
sp = krb5_storage_emem();
if (!sp)
goto out;
while ((ret = my_fgetln(f, &line, &line_bufsz, &line_len)) == 0) {
char *p = line;
char *q;
lineno++;
int i;
int num_tl_data;
int num_key_data;
int high_kvno;
int attributes;
int tmp;
lineno++;
memset(&ent, 0, sizeof(ent));
q = nexttoken(&p);
if(strcmp(q, "kdb5_util") == 0) {
if(strncmp(line, "kdb5_util", strlen("kdb5_util")) == 0) {
int major;
q = nexttoken(&p);
if (strcmp(q, "kdb5_util"))
errx(1, "line %d: unknown version", lineno);
q = nexttoken(&p); /* load_dump */
if(strcmp(q, "load_dump"))
if (strcmp(q, "load_dump"))
errx(1, "line %d: unknown version", lineno);
q = nexttoken(&p); /* load_dump */
if(strcmp(q, "version"))
if (strcmp(q, "version"))
errx(1, "line %d: unknown version", lineno);
q = nexttoken(&p); /* x.0 */
if(sscanf(q, "%d", &major) != 1)
if (sscanf(q, "%d", &major) != 1)
errx(1, "line %d: unknown version", lineno);
if(major != 4 && major != 5 && major != 6)
if (major != 4 && major != 5 && major != 6)
errx(1, "unknown dump file format, got %d, expected 4-6",
major);
continue;
} else if(strcmp(q, "policy") == 0) {
} else if(strncmp(p, "policy", strlen("policy")) == 0) {
warnx("line: %d: ignoring policy (not supported)", lineno);
continue;
} else if(strcmp(q, "princ") != 0) {
} else if(strncmp(p, "princ", strlen("princ")) != 0) {
warnx("line %d: not a principal", lineno);
continue;
}
tmp = getint(&p);
if(tmp != 38) {
warnx("line %d: bad base length %d != 38", lineno, tmp);
continue;
}
nexttoken(&p); /* length of principal */
num_tl_data = getint(&p); /* number of tl-data */
num_key_data = getint(&p); /* number of key-data */
getint(&p); /* length of extra data */
q = nexttoken(&p); /* principal name */
krb5_parse_name(pd->context, q, &ent.entry.principal);
attributes = getint(&p); /* attributes */
attr_to_flags(attributes, &ent.entry.flags);
tmp = getint(&p); /* max life */
if(tmp != 0) {
ALLOC(ent.entry.max_life);
*ent.entry.max_life = tmp;
}
tmp = getint(&p); /* max renewable life */
if(tmp != 0) {
ALLOC(ent.entry.max_renew);
*ent.entry.max_renew = tmp;
}
tmp = getint(&p); /* expiration */
if(tmp != 0 && tmp != 2145830400) {
ALLOC(ent.entry.valid_end);
*ent.entry.valid_end = tmp;
}
tmp = getint(&p); /* pw expiration */
if(tmp != 0) {
ALLOC(ent.entry.pw_end);
*ent.entry.pw_end = tmp;
}
nexttoken(&p); /* last auth */
nexttoken(&p); /* last failed auth */
nexttoken(&p); /* fail auth count */
for(i = 0; i < num_tl_data; i++) {
unsigned long val;
int tl_type, tl_length;
unsigned char *buf;
krb5_principal princ;
tl_type = getint(&p); /* data type */
tl_length = getint(&p); /* data length */
#define mit_KRB5_TL_LAST_PWD_CHANGE 1
#define mit_KRB5_TL_MOD_PRINC 2
switch(tl_type) {
case mit_KRB5_TL_LAST_PWD_CHANGE:
buf = malloc(tl_length);
if (buf == NULL)
errx(ENOMEM, "malloc");
getdata(&p, buf, tl_length); /* data itself */
val = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
free(buf);
ALLOC(ent.entry.extensions);
ALLOC_SEQ(ent.entry.extensions, 1);
ent.entry.extensions->val[0].mandatory = 0;
ent.entry.extensions->val[0].data.element
= choice_HDB_extension_data_last_pw_change;
ent.entry.extensions->val[0].data.u.last_pw_change = val;
break;
case mit_KRB5_TL_MOD_PRINC:
buf = malloc(tl_length);
if (buf == NULL)
errx(ENOMEM, "malloc");
getdata(&p, buf, tl_length); /* data itself */
val = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
ret = krb5_parse_name(pd->context, (char *)buf + 4, &princ);
if (ret)
krb5_err(pd->context, 1, ret,
"parse_name: %s", (char *)buf + 4);
free(buf);
ALLOC(ent.entry.modified_by);
ent.entry.modified_by->time = val;
ent.entry.modified_by->principal = princ;
break;
default:
nexttoken(&p);
break;
}
}
ALLOC_SEQ(&ent.entry.keys, num_key_data);
high_kvno = -1;
for(i = 0; i < num_key_data; i++) {
int key_versions;
int kvno;
key_versions = getint(&p); /* key data version */
kvno = getint(&p);
/*
* An MIT dump file may contain multiple sets of keys with
* different kvnos. Since the Heimdal database can only represent
* one kvno per principal, we only want the highest set. Assume
* that set will be given first, and discard all keys with lower
* kvnos.
*/
if (kvno > high_kvno && high_kvno != -1)
errx(1, "line %d: high kvno keys given after low kvno keys",
lineno);
else if (kvno < high_kvno) {
nexttoken(&p); /* key type */
nexttoken(&p); /* key length */
nexttoken(&p); /* key */
if (key_versions > 1) {
nexttoken(&p); /* salt type */
nexttoken(&p); /* salt length */
nexttoken(&p); /* salt */
}
ent.entry.keys.len--;
continue;
}
ent.entry.kvno = kvno;
high_kvno = kvno;
ALLOC(ent.entry.keys.val[i].mkvno);
*ent.entry.keys.val[i].mkvno = 1;
/* key version 0 -- actual key */
ent.entry.keys.val[i].key.keytype = getint(&p); /* key type */
tmp = getint(&p); /* key length */
/* the first two bytes of the key is the key length --
skip it */
krb5_data_alloc(&ent.entry.keys.val[i].key.keyvalue, tmp - 2);
q = nexttoken(&p); /* key itself */
hex_to_octet_string(q + 4, &ent.entry.keys.val[i].key.keyvalue);
if(key_versions > 1) {
/* key version 1 -- optional salt */
ALLOC(ent.entry.keys.val[i].salt);
ent.entry.keys.val[i].salt->type = getint(&p); /* salt type */
tmp = getint(&p); /* salt length */
if(tmp > 0) {
krb5_data_alloc(&ent.entry.keys.val[i].salt->salt, tmp - 2);
q = nexttoken(&p); /* salt itself */
hex_to_octet_string(q + 4,
&ent.entry.keys.val[i].salt->salt);
} else {
ent.entry.keys.val[i].salt->salt.length = 0;
ent.entry.keys.val[i].salt->salt.data = NULL;
getint(&p); /* -1, if no data. */
}
fix_salt(pd->context, &ent.entry, i);
}
}
nexttoken(&p); /* extra data */
v5_prop(pd->context, NULL, &ent, arg);
krb5_storage_truncate(sp, 0);
ret = _hdb_mit_dump2mitdb_entry(pd->context, line, sp);
if (ret) break;
ret = krb5_storage_to_data(sp, &kdb_ent);
if (ret) break;
ret = _hdb_mdb_value2entry(pd->context, &kdb_ent, 0, &ent.entry);
krb5_data_free(&kdb_ent);
if (ret) break;
ret = v5_prop(pd->context, NULL, &ent, arg);
hdb_free_entry(pd->context, &ent);
if (ret) break;
}
out:
fclose(f);
return 0;
free(line);
if (sp)
krb5_storage_free(sp);
if (ret && ret == ENOMEM)
errx(1, "out of memory");
if (ret)
errx(1, "line %d: problem parsing dump line", lineno);
return ret;
}