예제 #1
0
static krb5_error_code
decode_rx_header (krb5_storage *sp,
		  struct rx_header *h)
{
    krb5_error_code ret;

    ret = krb5_ret_uint32(sp, &h->epoch);
    if (ret) return ret;
    ret = krb5_ret_uint32(sp, &h->connid);
    if (ret) return ret;
    ret = krb5_ret_uint32(sp, &h->callid);
    if (ret) return ret;
    ret = krb5_ret_uint32(sp, &h->seqno);
    if (ret) return ret;
    ret = krb5_ret_uint32(sp, &h->serialno);
    if (ret) return ret;
    ret = krb5_ret_uint8(sp,  &h->type);
    if (ret) return ret;
    ret = krb5_ret_uint8(sp,  &h->flags);
    if (ret) return ret;
    ret = krb5_ret_uint8(sp,  &h->status);
    if (ret) return ret;
    ret = krb5_ret_uint8(sp,  &h->secindex);
    if (ret) return ret;
    ret = krb5_ret_uint16(sp, &h->reserved);
    if (ret) return ret;
    ret = krb5_ret_uint16(sp, &h->serviceid);
    if (ret) return ret;

    return 0;
}
예제 #2
0
/*
 * Request:
 *	NameZ
 *	Mode
 *
 * Response:
 *	
 */
static krb5_error_code
kcm_op_chmod(krb5_context context,
	     kcm_client *client,
	     kcm_operation opcode,
	     krb5_storage *request,
	     krb5_storage *response)
{
    uint16_t mode;
    krb5_error_code ret;
    kcm_ccache ccache;
    char *name;

    ret = krb5_ret_stringz(request, &name);
    if (ret)
	return ret;

    KCM_LOG_REQUEST_NAME(context, client, opcode, name);

    ret = krb5_ret_uint16(request, &mode);
    if (ret) {
	free(name);
	return ret;
    }

    ret = kcm_ccache_resolve_client(context, client, opcode,
				    name, &ccache);
    if (ret) {
	free(name);
	return ret;
    }

    ret = kcm_chmod(context, client, ccache, mode);

    free(name);
    kcm_release_ccache(context, ccache);

    return ret;
}
예제 #3
0
파일: test_store.c 프로젝트: Henauxg/minix
static void
test_uint16(krb5_context context, krb5_storage *sp)
{
    krb5_error_code ret;
    int i;
    uint16_t val[] = {
	0, 1, 65535
    }, v;

    krb5_storage_truncate(sp, 0);

    for (i = 0; i < sizeof(val[0])/sizeof(val); i++) {

	ret = krb5_store_uint16(sp, val[i]);
	if (ret)
	    krb5_err(context, 1, ret, "krb5_store_uint16");
	krb5_storage_seek(sp, 0, SEEK_SET);
	ret = krb5_ret_uint16(sp, &v);
	if (ret)
	    krb5_err(context, 1, ret, "krb5_ret_uint16");
	if (v != val[i])
	    krb5_errx(context, 1, "store and ret mismatch");
    }
}
예제 #4
0
static krb5_error_code
verify_logonname(krb5_context context,
		 const struct PAC_INFO_BUFFER *logon_name,
		 const krb5_data *data,
		 time_t authtime,
		 krb5_const_principal principal)
{
    krb5_error_code ret;
    krb5_principal p2;
    uint32_t time1, time2;
    krb5_storage *sp;
    uint16_t len;
    char *s;

    sp = krb5_storage_from_readonly_mem((const char *)data->data + logon_name->offset_lo,
					logon_name->buffersize);
    if (sp == NULL)
	return krb5_enomem(context);

    krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);

    CHECK(ret, krb5_ret_uint32(sp, &time1), out);
    CHECK(ret, krb5_ret_uint32(sp, &time2), out);

    {
	uint64_t t1, t2;
	t1 = unix2nttime(authtime);
	t2 = ((uint64_t)time2 << 32) | time1;
	if (t1 != t2) {
	    krb5_storage_free(sp);
	    krb5_set_error_message(context, EINVAL, "PAC timestamp mismatch");
	    return EINVAL;
	}
    }
    CHECK(ret, krb5_ret_uint16(sp, &len), out);
    if (len == 0) {
	krb5_storage_free(sp);
	krb5_set_error_message(context, EINVAL, "PAC logon name length missing");
	return EINVAL;
    }

    s = malloc(len);
    if (s == NULL) {
	krb5_storage_free(sp);
	return krb5_enomem(context);
    }
    ret = krb5_storage_read(sp, s, len);
    if (ret != len) {
	krb5_storage_free(sp);
	krb5_set_error_message(context, EINVAL, "Failed to read PAC logon name");
	return EINVAL;
    }
    krb5_storage_free(sp);
    {
	size_t ucs2len = len / 2;
	uint16_t *ucs2;
	size_t u8len;
	unsigned int flags = WIND_RW_LE;

	ucs2 = malloc(sizeof(ucs2[0]) * ucs2len);
	if (ucs2 == NULL)
	    return krb5_enomem(context);

	ret = wind_ucs2read(s, len, &flags, ucs2, &ucs2len);
	free(s);
	if (ret) {
	    free(ucs2);
	    krb5_set_error_message(context, ret, "Failed to convert string to UCS-2");
	    return ret;
	}
	ret = wind_ucs2utf8_length(ucs2, ucs2len, &u8len);
	if (ret) {
	    free(ucs2);
	    krb5_set_error_message(context, ret, "Failed to count length of UCS-2 string");
	    return ret;
	}
	u8len += 1; /* Add space for NUL */
	s = malloc(u8len);
	if (s == NULL) {
	    free(ucs2);
	    return krb5_enomem(context);
	}
	ret = wind_ucs2utf8(ucs2, ucs2len, s, &u8len);
	free(ucs2);
	if (ret) {
	    free(s);
	    krb5_set_error_message(context, ret, "Failed to convert to UTF-8");
	    return ret;
	}
    }
    ret = krb5_parse_name_flags(context, s, KRB5_PRINCIPAL_PARSE_NO_REALM, &p2);
    free(s);
    if (ret)
	return ret;

    if (krb5_principal_compare_any_realm(context, principal, p2) != TRUE) {
	ret = EINVAL;
	krb5_set_error_message(context, ret, "PAC logon name mismatch");
    }
    krb5_free_principal(context, p2);
    return ret;
out:
    return ret;
}
예제 #5
0
krb5_error_code
kcm_dispatch(krb5_context context,
	     kcm_client *client,
	     krb5_data *req_data,
	     krb5_data *resp_data)
{
    krb5_error_code ret;
    kcm_method method;
    krb5_storage *req_sp = NULL;
    krb5_storage *resp_sp = NULL;
    uint16_t opcode;

    resp_sp = krb5_storage_emem();
    if (resp_sp == NULL) {
	return ENOMEM;
    }

    if (client->pid == -1) {
	kcm_log(0, "Client had invalid process number");
	ret = KRB5_FCC_INTERNAL;
	goto out;
    }

    req_sp = krb5_storage_from_data(req_data);
    if (req_sp == NULL) {
	kcm_log(0, "Process %d: failed to initialize storage from data",
		client->pid);
	ret = KRB5_CC_IO;
	goto out;
    }

    ret = krb5_ret_uint16(req_sp, &opcode);
    if (ret) {
	kcm_log(0, "Process %d: didn't send a message", client->pid);
	goto out;
    }

    if (opcode >= sizeof(kcm_ops)/sizeof(kcm_ops[0])) {
	kcm_log(0, "Process %d: invalid operation code %d",
		client->pid, opcode);
	ret = KRB5_FCC_INTERNAL;
	goto out;
    }
    method = kcm_ops[opcode].method;
    if (method == NULL) {
	kcm_log(0, "Process %d: operation code %s not implemented",
		client->pid, kcm_op2string(opcode));
	ret = KRB5_FCC_INTERNAL;
	goto out;
    }

    /* seek past place for status code */
    krb5_storage_seek(resp_sp, 4, SEEK_SET);

    ret = (*method)(context, client, opcode, req_sp, resp_sp);

out:
    if (req_sp != NULL) {
	krb5_storage_free(req_sp);
    }

    krb5_storage_seek(resp_sp, 0, SEEK_SET);
    krb5_store_int32(resp_sp, ret);

    ret = krb5_storage_to_data(resp_sp, resp_data);
    krb5_storage_free(resp_sp);

    return ret;
}
예제 #6
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;
}
예제 #7
0
파일: pac.c 프로젝트: InvLim/heimdal
static krb5_error_code
verify_logonname(krb5_context context,
		 const struct PAC_INFO_BUFFER *logon_name,
		 const krb5_data *data,
		 time_t authtime,
		 krb5_const_principal principal)
{
    krb5_error_code ret;
    uint32_t time1, time2;
    krb5_storage *sp;
    uint16_t len;
    char *s = NULL;
    char *principal_string = NULL;
    char *logon_string = NULL;

    sp = krb5_storage_from_readonly_mem((const char *)data->data + logon_name->offset_lo,
					logon_name->buffersize);
    if (sp == NULL)
	return krb5_enomem(context);

    krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE);

    CHECK(ret, krb5_ret_uint32(sp, &time1), out);
    CHECK(ret, krb5_ret_uint32(sp, &time2), out);

    {
	uint64_t t1, t2;
	t1 = unix2nttime(authtime);
	t2 = ((uint64_t)time2 << 32) | time1;
	/*
	 * When neither the ticket nor the PAC set an explicit authtime,
	 * both times are zero, but relative to different time scales.
	 * So we must compare "not set" values without converting to a
	 * common time reference.
         */
	if (t1 != t2 && (t2 != 0 && authtime != 0)) {
	    krb5_storage_free(sp);
	    krb5_set_error_message(context, EINVAL, "PAC timestamp mismatch");
	    return EINVAL;
	}
    }
    CHECK(ret, krb5_ret_uint16(sp, &len), out);
    if (len == 0) {
	krb5_storage_free(sp);
	krb5_set_error_message(context, EINVAL, "PAC logon name length missing");
	return EINVAL;
    }

    s = malloc(len);
    if (s == NULL) {
	krb5_storage_free(sp);
	return krb5_enomem(context);
    }
    ret = krb5_storage_read(sp, s, len);
    if (ret != len) {
	krb5_storage_free(sp);
	krb5_set_error_message(context, EINVAL, "Failed to read PAC logon name");
	return EINVAL;
    }
    krb5_storage_free(sp);
    {
	size_t ucs2len = len / 2;
	uint16_t *ucs2;
	size_t u8len;
	unsigned int flags = WIND_RW_LE;

	ucs2 = malloc(sizeof(ucs2[0]) * ucs2len);
	if (ucs2 == NULL)
	    return krb5_enomem(context);

	ret = wind_ucs2read(s, len, &flags, ucs2, &ucs2len);
	free(s);
	if (ret) {
	    free(ucs2);
	    krb5_set_error_message(context, ret, "Failed to convert string to UCS-2");
	    return ret;
	}
	ret = wind_ucs2utf8_length(ucs2, ucs2len, &u8len);
	if (ret) {
	    free(ucs2);
	    krb5_set_error_message(context, ret, "Failed to count length of UCS-2 string");
	    return ret;
	}
	u8len += 1; /* Add space for NUL */
	logon_string = malloc(u8len);
	if (logon_string == NULL) {
	    free(ucs2);
	    return krb5_enomem(context);
	}
	ret = wind_ucs2utf8(ucs2, ucs2len, logon_string, &u8len);
	free(ucs2);
	if (ret) {
	    free(logon_string);
	    krb5_set_error_message(context, ret, "Failed to convert to UTF-8");
	    return ret;
	}
    }
    ret = krb5_unparse_name_flags(context, principal,
				  KRB5_PRINCIPAL_UNPARSE_NO_REALM |
				  KRB5_PRINCIPAL_UNPARSE_DISPLAY,
				  &principal_string);
    if (ret) {
	free(logon_string);
	return ret;
    }

    ret = strcmp(logon_string, principal_string);
    if (ret != 0) {
	ret = EINVAL;
	krb5_set_error_message(context, ret, "PAC logon name [%s] mismatch principal name [%s]",
			       logon_string, principal_string);
    }
    free(logon_string);
    free(principal_string);
    return ret;
out:
    return ret;
}
예제 #8
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;
}