Пример #1
0
int
mit_prop_dump(void *arg, const char *file)
{
    krb5_error_code ret;
    char line [2048];
    FILE *f;
    int lineno = 0;
    struct hdb_entry_ex ent;

    struct prop_data *pd = arg;

    f = fopen(file, "r");
    if(f == NULL)
	return errno;

    while(fgets(line, sizeof(line), f)) {
	char *p = line, *q;

	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) {
	    int major;
	    q = nexttoken(&p); /* load_dump */
	    if(strcmp(q, "load_dump"))
		errx(1, "line %d: unknown version", lineno);
	    q = nexttoken(&p); /* load_dump */
	    if(strcmp(q, "version"))
		errx(1, "line %d: unknown version", lineno);
	    q = nexttoken(&p); /* x.0 */
	    if(sscanf(q, "%d", &major) != 1)
		errx(1, "line %d: unknown version", lineno);
	    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) {
	    continue;
	} else if(strcmp(q, "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);
    }
    fclose(f);
    return 0;
}
Пример #2
0
static krb5_error_code
mdb_value2entry(krb5_context context, krb5_data *data, hdb_entry *entry)
{
    krb5_error_code ret;
    krb5_storage *sp;
    uint32_t u32;
    uint16_t u16, num_keys, num_tl;
    size_t i, j;
    char *p = NULL;

    sp = krb5_storage_from_data(data);
    if (sp == NULL) {
	krb5_set_error_message(context, ENOMEM, "out of memory");
	return ENOMEM;
    }
    
    krb5_storage_set_byteorder(sp, KRB5_STORAGE_BYTEORDER_LE);

    /* 16: baselength */
    CHECK(ret = krb5_ret_uint16(sp, &u16));
    if (u16 != KDB_V1_BASE_LENGTH) { ret = EINVAL; goto out; }
    /* 32: attributes */
    CHECK(ret = krb5_ret_uint32(sp, &u32));
    entry->flags.postdate =	 !(u16 & KRB5_KDB_DISALLOW_POSTDATED);
    entry->flags.forwardable =	 !(u16 & KRB5_KDB_DISALLOW_FORWARDABLE);
    entry->flags.initial =	!!(u16 & KRB5_KDB_DISALLOW_TGT_BASED);
    entry->flags.renewable =	 !(u16 & KRB5_KDB_DISALLOW_RENEWABLE);
    entry->flags.proxiable =	 !(u16 & KRB5_KDB_DISALLOW_PROXIABLE);
    /* DUP_SKEY */
    entry->flags.invalid =	!!(u16 & KRB5_KDB_DISALLOW_ALL_TIX);
    entry->flags.require_preauth =!!(u16 & KRB5_KDB_REQUIRES_PRE_AUTH);
    entry->flags.require_hwauth =!!(u16 & KRB5_KDB_REQUIRES_HW_AUTH);
    entry->flags.server =	 !(u16 & KRB5_KDB_DISALLOW_SVR);
    entry->flags.change_pw = 	!!(u16 & KRB5_KDB_PWCHANGE_SERVICE);
    entry->flags.client =	   1; /* XXX */

    /* 32: max time */
    CHECK(ret = krb5_ret_uint32(sp, &u32));
    if (u32) {
	entry->max_life = malloc(sizeof(*entry->max_life));
	*entry->max_life = u32;
    }
    /* 32: max renewable time */
    CHECK(ret = krb5_ret_uint32(sp, &u32));
    if (u32) {
	entry->max_renew = malloc(sizeof(*entry->max_renew));
	*entry->max_renew = u32;
    }
    /* 32: client expire */
    CHECK(ret = krb5_ret_uint32(sp, &u32));
    if (u32) {
	entry->valid_end = malloc(sizeof(*entry->valid_end));
	*entry->valid_end = u32;
    }
    /* 32: passwd expire */
    CHECK(ret = krb5_ret_uint32(sp, &u32));
    if (u32) {
	entry->pw_end = malloc(sizeof(*entry->pw_end));
	*entry->pw_end = u32;
    }
    /* 32: last successful passwd */
    CHECK(ret = krb5_ret_uint32(sp, &u32));
    /* 32: last failed attempt */
    CHECK(ret = krb5_ret_uint32(sp, &u32));
    /* 32: num of failed attempts */
    CHECK(ret = krb5_ret_uint32(sp, &u32));
    /* 16: num tl data */
    CHECK(ret = krb5_ret_uint16(sp, &u16));
    num_tl = u16;
    /* 16: num key data */
    CHECK(ret = krb5_ret_uint16(sp, &u16));
    num_keys = u16;
    /* 16: principal length */
    CHECK(ret = krb5_ret_uint16(sp, &u16));
    /* length: principal */
    {
	p = malloc(u16 + 1);
	krb5_storage_read(sp, p, u16);
	p[u16] = '\0';
	CHECK(ret = krb5_parse_name(context, p, &entry->principal));
	free(p); p = NULL;
    }
    /* for num tl data times
           16: tl data type
           16: tl data length
           length: length */
    for (i = 0; i < num_tl; i++) {
	CHECK(ret = krb5_ret_uint16(sp, &u16));
	CHECK(ret = krb5_ret_uint16(sp, &u16));
	krb5_storage_seek(sp, u16, SEEK_CUR);
    }
    /* for num key data times
       16: version (num keyblocks)
       16: kvno
       for version times:
           16: type
           16: length
           length: keydata */
    for (i = 0; i < num_keys; i++) {
	int keep = 0;
	uint16_t version;
	void *ptr;

	CHECK(ret = krb5_ret_uint16(sp, &u16));
	version = u16;
	CHECK(ret = krb5_ret_uint16(sp, &u16));

	if (entry->kvno < u16) {
	    keep = 1;
	    entry->kvno = u16;
	    for (j = 0; j < entry->keys.len; j++) {
		free_Key(&entry->keys.val[j]);
		free(entry->keys.val);
		entry->keys.len = 0;
		entry->keys.val = NULL;
	    }
	} else if (entry->kvno == u16)
	    keep = 1;

	if (keep) {
	    Key *k;

	    ptr = realloc(entry->keys.val, sizeof(entry->keys.val[0]) * (entry->keys.len + 1));
	    if (ptr == NULL) {
		ret = ENOMEM;
		goto out;
	    }
	    entry->keys.val = ptr;

	    /* k points to current Key */
	    k = &entry->keys.val[entry->keys.len];

	    memset(k, 0, sizeof(*k));
	    entry->keys.len += 1;

	    entry->keys.val[i].mkvno = malloc(sizeof(*entry->keys.val[i].mkvno));
	    if (entry->keys.val[i].mkvno == NULL) {
		ret = ENOMEM;
		goto out;
	    }
	    *entry->keys.val[i].mkvno = 1;

	    for (j = 0; j < version; j++) {
		uint16_t type;
		CHECK(ret = krb5_ret_uint16(sp, &type));
		CHECK(ret = krb5_ret_uint16(sp, &u16));
		if (j == 0) { /* key */
		    k->key.keytype = type;
		    if (u16 < 2) {
			ret = EINVAL;
			goto out;
		    }
		    krb5_storage_seek(sp, 2, SEEK_CUR); /* skip real length */
		    k->key.keyvalue.length = u16 - 2;
		    k->key.keyvalue.data = malloc(k->key.keyvalue.length);
		    krb5_storage_read(sp, k->key.keyvalue.data, k->key.keyvalue.length);
		} else if (j == 1) { /* salt */
		    k->salt = calloc(1, sizeof(*k->salt));
		    if (k->salt == NULL) {
			ret = ENOMEM;
			goto out;
		    }
		    k->salt->type = type;
		    if (u16 != 0) {
			k->salt->salt.data = malloc(u16);
			if (k->salt->salt.data == NULL) {
			    ret = ENOMEM;
			    goto out;
			}
			k->salt->salt.length = u16;
			krb5_storage_read(sp, k->salt->salt.data, k->salt->salt.length);
		    }			
		    fix_salt(context, entry, entry->keys.len - 1);
		} else {
		    krb5_storage_seek(sp, u16, SEEK_CUR);
		}
	    }
	} else {
	    /* skip */
	    for (j = 0; j < version; j++) {
		CHECK(ret = krb5_ret_uint16(sp, &u16));
		CHECK(ret = krb5_ret_uint16(sp, &u16));
		krb5_storage_seek(sp, u16, u16);
	    }
	}
    }

    return 0;
 out:
    if (p)
	free(p);
    free_hdb_entry(entry);
    return ret;
}
Пример #3
0
int
mit_prop_dump(void *arg, const char *file)
{
    krb5_error_code ret;
    char line [2048];
    FILE *f;
    int lineno = 0;
    struct hdb_entry_ex ent;

    struct prop_data *pd = arg;

    f = fopen(file, "r");
    if(f == NULL)
	return errno;
    
    while(fgets(line, sizeof(line), f)) {
	char *p = line, *q;

	int i;

	int num_tl_data;
	int num_key_data;
	int extra_data_length;
	int attributes;

	int tmp;

	lineno++;

	memset(&ent, 0, sizeof(ent));

	q = nexttoken(&p);
	if(strcmp(q, "kdb5_util") == 0) {
	    int major;
	    q = nexttoken(&p); /* load_dump */
	    if(strcmp(q, "load_dump"))
		errx(1, "line %d: unknown version", lineno);
	    q = nexttoken(&p); /* load_dump */
	    if(strcmp(q, "version"))
		errx(1, "line %d: unknown version", lineno);
	    q = nexttoken(&p); /* x.0 */
	    if(sscanf(q, "%d", &major) != 1)
		errx(1, "line %d: unknown version", lineno);
	    if(major != 4)
		errx(1, "unknown dump file format, got %d, expected 4", major);
	    continue;
	} else if(strcmp(q, "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;
	}
	q = nexttoken(&p); /* length of principal */
	num_tl_data = getint(&p); /* number of tl-data */
	num_key_data = getint(&p); /* number of key-data */
	extra_data_length = 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;
	}
	q = nexttoken(&p); /* last auth */
	q = nexttoken(&p); /* last failed auth */
	q = 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_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);
		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);
	for(i = 0; i < num_key_data; i++) {
	    int key_versions;
	    key_versions = getint(&p); /* key data version */
	    ent.entry.kvno = getint(&p); /* XXX kvno */
	    
	    ALLOC(ent.entry.keys.val[i].mkvno);
	    *ent.entry.keys.val[i].mkvno = 0;
	    
	    /* 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;
		    tmp = getint(&p);	/* -1, if no data. */
		}
		fix_salt(pd->context, &ent.entry, i);
	    }
	}
	q = nexttoken(&p); /* extra data */
	v5_prop(pd->context, NULL, &ent, arg);
    }
    fclose(f);
    return 0;
}
Пример #4
0
static krb5_error_code
mdb_value2entry(krb5_context context, krb5_data *data, krb5_kvno kvno, hdb_entry *entry)
{
    krb5_error_code ret;
    krb5_storage *sp;
    uint32_t u32;
    uint16_t u16, num_keys, num_tl;
    size_t i, j;
    char *p;

    sp = krb5_storage_from_data(data);
    if (sp == NULL) {
        krb5_set_error_message(context, ENOMEM, "out of memory");
        return ENOMEM;
    }

    krb5_storage_set_byteorder(sp, KRB5_STORAGE_BYTEORDER_LE);

    /*
     * 16: baselength
     *
     * The story here is that these 16 bits have to be a constant:
     * KDB_V1_BASE_LENGTH.  Once upon a time a different value here
     * would have been used to indicate the presence of "extra data"
     * between the "base" contents and the {principal name, TL data,
     * keys} that follow it.  Nothing supports such "extra data"
     * nowadays, so neither do we here.
     *
     * XXX But... surely we ought to log about this extra data, or skip
     * it, or something, in case anyone has MIT KDBs with ancient
     * entries in them...  Logging would allow the admin to know which
     * entries to dump with MIT krb5's kdb5_util.
     */
    CHECK(ret = krb5_ret_uint16(sp, &u16));
    if (u16 != KDB_V1_BASE_LENGTH) {
        ret = EINVAL;
        goto out;
    }
    /* 32: attributes */
    CHECK(ret = krb5_ret_uint32(sp, &u32));
    entry->flags.postdate =	 !(u32 & KRB5_KDB_DISALLOW_POSTDATED);
    entry->flags.forwardable =	 !(u32 & KRB5_KDB_DISALLOW_FORWARDABLE);
    entry->flags.initial =	!!(u32 & KRB5_KDB_DISALLOW_TGT_BASED);
    entry->flags.renewable =	 !(u32 & KRB5_KDB_DISALLOW_RENEWABLE);
    entry->flags.proxiable =	 !(u32 & KRB5_KDB_DISALLOW_PROXIABLE);
    /* DUP_SKEY */
    entry->flags.invalid =	!!(u32 & KRB5_KDB_DISALLOW_ALL_TIX);
    entry->flags.require_preauth =!!(u32 & KRB5_KDB_REQUIRES_PRE_AUTH);
    entry->flags.require_hwauth =!!(u32 & KRB5_KDB_REQUIRES_HW_AUTH);
    entry->flags.server =	 !(u32 & KRB5_KDB_DISALLOW_SVR);
    entry->flags.change_pw = 	!!(u32 & KRB5_KDB_PWCHANGE_SERVICE);
    entry->flags.client =	   1; /* XXX */

    /* 32: max time */
    CHECK(ret = krb5_ret_uint32(sp, &u32));
    if (u32) {
        entry->max_life = malloc(sizeof(*entry->max_life));
        *entry->max_life = u32;
    }
    /* 32: max renewable time */
    CHECK(ret = krb5_ret_uint32(sp, &u32));
    if (u32) {
        entry->max_renew = malloc(sizeof(*entry->max_renew));
        *entry->max_renew = u32;
    }
    /* 32: client expire */
    CHECK(ret = krb5_ret_uint32(sp, &u32));
    if (u32) {
        entry->valid_end = malloc(sizeof(*entry->valid_end));
        *entry->valid_end = u32;
    }
    /* 32: passwd expire */
    CHECK(ret = krb5_ret_uint32(sp, &u32));
    if (u32) {
        entry->pw_end = malloc(sizeof(*entry->pw_end));
        *entry->pw_end = u32;
    }
    /* 32: last successful passwd */
    CHECK(ret = krb5_ret_uint32(sp, &u32));
    /* 32: last failed attempt */
    CHECK(ret = krb5_ret_uint32(sp, &u32));
    /* 32: num of failed attempts */
    CHECK(ret = krb5_ret_uint32(sp, &u32));
    /* 16: num tl data */
    CHECK(ret = krb5_ret_uint16(sp, &u16));
    num_tl = u16;
    /* 16: num key data */
    CHECK(ret = krb5_ret_uint16(sp, &u16));
    num_keys = u16;
    /* 16: principal length */
    CHECK(ret = krb5_ret_uint16(sp, &u16));
    /* length: principal */
    {
        /*
         * Note that the principal name includes the NUL in the entry,
         * but we don't want to take chances, so we add an extra NUL.
         */
        p = malloc(u16 + 1);
        if (p == NULL) {
            ret = ENOMEM;
            goto out;
        }
        krb5_storage_read(sp, p, u16);
        p[u16] = '\0';
        CHECK(ret = krb5_parse_name(context, p, &entry->principal));
        free(p);
    }
    /* for num tl data times
           16: tl data type
           16: tl data length
           length: length */
    for (i = 0; i < num_tl; i++) {
        /* 16: TL data type */
        CHECK(ret = krb5_ret_uint16(sp, &u16));
        /* 16: TL data length */
        CHECK(ret = krb5_ret_uint16(sp, &u16));
        krb5_storage_seek(sp, u16, SEEK_CUR);
    }
    /*
     * for num key data times
     * 16: "version"
     * 16: kvno
     * for version times:
     *     16: type
     *     16: length
     *     length: keydata
     *
     * "version" here is really 1 or 2, the first meaning there's only
     * keys for this kvno, the second meaning there's keys and salt[s?].
     * That's right... hold that gag reflex, you can do it.
     */
    for (i = 0; i < num_keys; i++) {
        int keep = 0;
        uint16_t version;
        void *ptr;

        CHECK(ret = krb5_ret_uint16(sp, &u16));
        version = u16;
        CHECK(ret = krb5_ret_uint16(sp, &u16));

        /*
         * First time through, and until we find one matching key,
         * entry->kvno == 0.
         */
        if ((entry->kvno < u16) && (kvno == 0 || kvno == u16)) {
            keep = 1;
            entry->kvno = u16;
            /*
             * Found a higher kvno than earlier, so free the old highest
             * kvno keys.
             *
             * XXX Of course, we actually want to extract the old kvnos
             * as well, for some of the kadm5 APIs.  We shouldn't free
             * these keys, but keep them elsewhere.
             */
            for (j = 0; j < entry->keys.len; j++)
                free_Key(&entry->keys.val[j]);
            free(entry->keys.val);
            entry->keys.len = 0;
            entry->keys.val = NULL;
        } else if (entry->kvno == u16)
            /* Accumulate keys */
            keep = 1;

        if (keep) {
            Key *k;

            ptr = realloc(entry->keys.val, sizeof(entry->keys.val[0]) * (entry->keys.len + 1));
            if (ptr == NULL) {
                ret = ENOMEM;
                goto out;
            }
            entry->keys.val = ptr;

            /* k points to current Key */
            k = &entry->keys.val[entry->keys.len];

            memset(k, 0, sizeof(*k));
            entry->keys.len += 1;

            k->mkvno = malloc(sizeof(*k->mkvno));
            if (k->mkvno == NULL) {
                ret = ENOMEM;
                goto out;
            }
            *k->mkvno = 1;

            for (j = 0; j < version; j++) {
                uint16_t type;
                CHECK(ret = krb5_ret_uint16(sp, &type));
                CHECK(ret = krb5_ret_uint16(sp, &u16));
                if (j == 0) {
                    /* This "version" means we have a key */
                    k->key.keytype = type;
                    if (u16 < 2) {
                        ret = EINVAL;
                        goto out;
                    }
                    /*
                     * MIT stores keys encrypted keys as {16-bit length
                     * of plaintext key, {encrypted key}}.  The reason
                     * for this is that the Kerberos cryptosystem is not
                     * length-preserving.  Heimdal's approach is to
                     * truncate the plaintext to the expected length of
                     * the key given its enctype, so we ignore this
                     * 16-bit length-of-plaintext-key field.
                     */
                    krb5_storage_seek(sp, 2, SEEK_CUR); /* skip real length */
                    k->key.keyvalue.length = u16 - 2;   /* adjust cipher len */
                    k->key.keyvalue.data = malloc(k->key.keyvalue.length);
                    krb5_storage_read(sp, k->key.keyvalue.data,
                                      k->key.keyvalue.length);
                } else if (j == 1) {
                    /* This "version" means we have a salt */
                    k->salt = calloc(1, sizeof(*k->salt));
                    if (k->salt == NULL) {
                        ret = ENOMEM;
                        goto out;
                    }
                    k->salt->type = type;
                    if (u16 != 0) {
                        k->salt->salt.data = malloc(u16);
                        if (k->salt->salt.data == NULL) {
                            ret = ENOMEM;
                            goto out;
                        }
                        k->salt->salt.length = u16;
                        krb5_storage_read(sp, k->salt->salt.data, k->salt->salt.length);
                    }
                    fix_salt(context, entry, entry->keys.len - 1);
                } else {
                    /*
                     * Whatever this "version" might be, we skip it
                     *
                     * XXX A krb5.conf parameter requesting that we log
                     * about strangeness like this, or return an error
                     * from here, might be nice.
                     */
                    krb5_storage_seek(sp, u16, SEEK_CUR);
                }
            }
        } else {
            /*
             * XXX For now we skip older kvnos, but we should extract
             * them...
             */
            for (j = 0; j < version; j++) {
                /* enctype */
                CHECK(ret = krb5_ret_uint16(sp, &u16));
                /* encrypted key (or plaintext salt) */
                CHECK(ret = krb5_ret_uint16(sp, &u16));
                krb5_storage_seek(sp, u16, SEEK_CUR);
            }
        }
    }

    if (entry->kvno == 0 && kvno != 0) {
        ret = HDB_ERR_NOT_FOUND_HERE;
        goto out;
    }

    return 0;
out:
    if (ret == HEIM_ERR_EOF)
        /* Better error code than "end of file" */
        ret = HEIM_ERR_BAD_HDBENT_ENCODING;
    return ret;
}