/*
 * lib/krb5/ccache/cc_keyring.c
 *
 * Copyright (c) 2006
 * The Regents of the University of Michigan
 * ALL RIGHTS RESERVED
 *
 * Permission is granted to use, copy, create derivative works
 * and redistribute this software and such derivative works
 * for any purpose, so long as the name of The University of
 * Michigan is not used in any advertising or publicity
 * pertaining to the use of distribution of this software
 * without specific, written prior authorization.  If the
 * above copyright notice or any other identification of the
 * University of Michigan is included in any copy of any
 * portion of this software, then the disclaimer below must
 * also be included.
 *
 * THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION
 * FROM THE UNIVERSITY OF MICHIGAN AS TO ITS FITNESS FOR ANY
 * PURPOSE, AND WITHOUT WARRANTY BY THE UNIVERSITY OF
 * MICHIGAN OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
 * WITHOUT LIMITATION THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
 * REGENTS OF THE UNIVERSITY OF MICHIGAN SHALL NOT BE LIABLE
 * FOR ANY DAMAGES, INCLUDING SPECIAL, INDIRECT, INCIDENTAL, OR
 * CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OF THE SOFTWARE, EVEN
 * IF IT HAS BEEN OR IS HEREAFTER ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGES.
 *
 */
/*
 * Copyright 1990,1991,1992,1993,1994,2000,2004 Massachusetts Institute of Technology.
 * All Rights Reserved.
 *
 * Original stdio support copyright 1995 by Cygnus Support.
 *
 * Export of this software from the United States of America may
 *   require a specific license from the United States Government.
 *   It is the responsibility of any person or organization contemplating
 *   export to obtain such a license before exporting.
 * 
 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
 * distribute this software and its documentation for any purpose and
 * without fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright notice and
 * this permission notice appear in supporting documentation, and that
 * the name of M.I.T. not be used in advertising or publicity pertaining
 * to distribution of the software without specific, written prior
 * permission.  Furthermore if you modify this software you must label
 * your software as modified software and not distribute it in such a
 * fashion that it might be confused with the original M.I.T. software.
 * M.I.T. makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is" without express
 * or implied warranty.
 */

/*
 * Implementation of a credentials cache stored in the Linux keyring facility
 *
 * Some assumptions:
 *
 *	- A credentials cache "file" == a keyring with separate keys
 *	  for the information in the ccache (see below)
 *	- A credentials cache keyring will contain only keys,
 *	  not other keyrings
 *	- Each Kerberos ticket will have its own key within the ccache keyring
 *	- The principal information for the ccache is stored in a
 *	  special key, which is not counted in the 'numkeys' count
 */

#include "k5-int.h"

#ifdef USE_KEYRING_CCACHE

#include <errno.h>
#include <keyutils.h>

#ifdef DEBUG
#define KRCC_DEBUG	    1
#endif

#if KRCC_DEBUG
void debug_print(char *fmt, ...);	/* prototype to silence warning */
#include <syslog.h>
#define DEBUG_PRINT(x) debug_print x
void
debug_print(char *fmt, ...)
{
	va_list ap;
	va_start(ap, fmt);
#ifdef DEBUG_STDERR
	vfprintf(stderr, fmt, ap);
#else
	vsyslog(LOG_ERR, fmt, ap);
#endif
	va_end(ap);
}
#else
#define DEBUG_PRINT(x)
#endif

/*
 * We always use "user" key type
 */
#define KRCC_KEY_TYPE_USER "user"

/*
 * We create ccaches as separate keyrings
 */
#define KRCC_KEY_TYPE_KEYRING "keyring"

/*
 * Special name of the key within a ccache keyring
 * holding principal information
 */
#define KRCC_SPEC_PRINC_KEYNAME "__krb5_princ__"

/*
 * XXX The following two really belong in some external
 * header since outside programs will need to use these
 * same names.
 */
/*
 * Special name for key to communicate key serial numbers
 * This is used by the Linux gssd process to pass the
 * user's keyring values it gets in an upcall.
 * The format of the contents should be
 *    <session_key>:<process_key>:<thread_key>
 */
#define KRCC_SPEC_IDS_KEYNAME "_gssd_keyring_ids_"

/*
 * Special name for the key to communicate the name(s)
 * of credentials caches to be used for requests.
 * This should currently contain a single name, but
 * in the future may contain a list that may be
 * intelligently chosen from.
 */
#define KRCC_SPEC_CCACHE_SET_KEYNAME "__krb5_cc_set__"

#define KRB5_OK 0

/* Hopefully big enough to hold a serialized credential */
#define GUESS_CRED_SIZE 4096

#define ALLOC(NUM,TYPE) \
    (((NUM) <= (((size_t)0-1)/ sizeof(TYPE)))           \
     ? (TYPE *) calloc((NUM), sizeof(TYPE))             \
     : (errno = ENOMEM,(TYPE *) 0))

#define CHECK_N_GO(ret, errdest) if (ret != KRB5_OK) goto errdest
#define CHECK(ret) if (ret != KRB5_OK) goto errout
#define CHECK_OUT(ret) if (ret != KRB5_OK) return ret

typedef struct krb5_krcc_ring_ids {
    key_serial_t	session;
    key_serial_t	process;
    key_serial_t	thread;
} krb5_krcc_ring_ids_t;

typedef struct _krb5_krcc_cursor
{
    int     numkeys;
    int     currkey;
    key_serial_t princ_id;
    key_serial_t *keys;
} *krb5_krcc_cursor;

/*
 * This represents a credentials cache "file"
 * where ring_id is the keyring serial number for
 * this credentials cache "file".  Each key
 * in the keyring contains a separate key.
 */
typedef struct _krb5_krcc_data
{
    char   *name;		/* Name for this credentials cache */
    k5_mutex_t lock;		/* synchronization */
    key_serial_t parent_id;	/* parent keyring of this ccache keyring */
    key_serial_t ring_id;	/* keyring representing ccache */
    key_serial_t princ_id;	/* key holding principal info */
    int     numkeys;		/* # of keys in this ring
				 * (does NOT include principal info) */
} krb5_krcc_data;

/* Passed internally to assure we don't go past the bounds of our buffer */
typedef struct _krb5_krcc_buffer_cursor
{
    char   *bpp;
    char   *endp;
} krb5_krcc_bc;

/* Global mutex */
k5_mutex_t krb5int_krcc_mutex = K5_MUTEX_PARTIAL_INITIALIZER;

/*
 * Internal functions (exported via the krb5_krcc_ops)
 */

extern const krb5_cc_ops krb5_krcc_ops;

static const char *KRB5_CALLCONV krb5_krcc_get_name
    (krb5_context, krb5_ccache id);

static krb5_error_code KRB5_CALLCONV krb5_krcc_resolve
    (krb5_context, krb5_ccache * id, const char *residual);

static krb5_error_code KRB5_CALLCONV krb5_krcc_generate_new
    (krb5_context, krb5_ccache * id);

static krb5_error_code KRB5_CALLCONV krb5_krcc_initialize
    (krb5_context, krb5_ccache id, krb5_principal princ);

static krb5_error_code KRB5_CALLCONV krb5_krcc_destroy
    (krb5_context, krb5_ccache id);

static krb5_error_code KRB5_CALLCONV krb5_krcc_close
    (krb5_context, krb5_ccache id);

static krb5_error_code KRB5_CALLCONV krb5_krcc_store
    (krb5_context, krb5_ccache id, krb5_creds * creds);

static krb5_error_code KRB5_CALLCONV krb5_krcc_retrieve
    (krb5_context, krb5_ccache id, krb5_flags whichfields,
     krb5_creds * mcreds, krb5_creds * creds);

static krb5_error_code KRB5_CALLCONV krb5_krcc_get_principal
    (krb5_context, krb5_ccache id, krb5_principal * princ);

static krb5_error_code KRB5_CALLCONV krb5_krcc_start_seq_get
    (krb5_context, krb5_ccache id, krb5_cc_cursor * cursor);

static krb5_error_code KRB5_CALLCONV krb5_krcc_next_cred
    (krb5_context, krb5_ccache id, krb5_cc_cursor * cursor,
     krb5_creds * creds);

static krb5_error_code KRB5_CALLCONV krb5_krcc_end_seq_get
    (krb5_context, krb5_ccache id, krb5_cc_cursor * cursor);

static krb5_error_code KRB5_CALLCONV krb5_krcc_remove_cred
    (krb5_context context, krb5_ccache cache, krb5_flags flags,
     krb5_creds * creds);

static krb5_error_code KRB5_CALLCONV krb5_krcc_set_flags
    (krb5_context, krb5_ccache id, krb5_flags flags);

static krb5_error_code KRB5_CALLCONV krb5_krcc_get_flags
    (krb5_context context, krb5_ccache id, krb5_flags * flags);

/*
 * Internal utility functions
 */

static krb5_error_code krb5_krcc_clearcache
    (krb5_context context, krb5_ccache id);

static krb5_error_code krb5_krcc_new_data
    (const char *, key_serial_t ring, key_serial_t parent_ring,
     krb5_krcc_data **);

static krb5_error_code krb5_krcc_save_principal
    (krb5_context context, krb5_ccache id, krb5_principal princ);

static krb5_error_code krb5_krcc_retrieve_principal
    (krb5_context context, krb5_ccache id, krb5_principal * princ);

static int krb5_krcc_get_ring_ids(krb5_krcc_ring_ids_t *p);

/* Routines to parse a key from a keyring into a cred structure */
static krb5_error_code krb5_krcc_parse
    (krb5_context, krb5_ccache id, krb5_pointer buf, unsigned int len,
     krb5_krcc_bc * bc);
static krb5_error_code krb5_krcc_parse_cred
    (krb5_context context, krb5_ccache id, krb5_creds * creds,
     char *payload, int psize);
static krb5_error_code krb5_krcc_parse_principal
    (krb5_context context, krb5_ccache id, krb5_principal * princ,
     krb5_krcc_bc * bc);
static krb5_error_code krb5_krcc_parse_keyblock
    (krb5_context context, krb5_ccache id, krb5_keyblock * keyblock,
     krb5_krcc_bc * bc);
static krb5_error_code krb5_krcc_parse_times
    (krb5_context context, krb5_ccache id, krb5_ticket_times * t,
     krb5_krcc_bc * bc);
static krb5_error_code krb5_krcc_parse_krb5data
    (krb5_context context, krb5_ccache id, krb5_data * data,
     krb5_krcc_bc * bc);
static krb5_error_code krb5_krcc_parse_int32
    (krb5_context context, krb5_ccache id, krb5_int32 * i, krb5_krcc_bc * bc);
static krb5_error_code krb5_krcc_parse_octet
    (krb5_context context, krb5_ccache id, krb5_octet * octet,
     krb5_krcc_bc * bc);
static krb5_error_code krb5_krcc_parse_addrs
    (krb5_context context, krb5_ccache id, krb5_address *** a,
     krb5_krcc_bc * bc);
static krb5_error_code krb5_krcc_parse_addr
    (krb5_context context, krb5_ccache id, krb5_address * a,
     krb5_krcc_bc * bc);
static krb5_error_code krb5_krcc_parse_authdata
    (krb5_context context, krb5_ccache id, krb5_authdata *** ad,
     krb5_krcc_bc * bc);
static krb5_error_code krb5_krcc_parse_authdatum
    (krb5_context context, krb5_ccache id, krb5_authdata * ad,
     krb5_krcc_bc * bc);
static krb5_error_code krb5_krcc_parse_ui_2
    (krb5_context, krb5_ccache id, krb5_ui_2 * i, krb5_krcc_bc * bc);

/* Routines to unparse a cred structure into keyring key */
static krb5_error_code krb5_krcc_unparse
    (krb5_context, krb5_ccache id, krb5_pointer buf, unsigned int len,
     krb5_krcc_bc * bc);
static krb5_error_code krb5_krcc_unparse_cred
    (krb5_context context, krb5_ccache id, krb5_creds * creds,
     char **datapp, unsigned int *lenptr);
static krb5_error_code krb5_krcc_unparse_principal
    (krb5_context, krb5_ccache id, krb5_principal princ, krb5_krcc_bc * bc);
static krb5_error_code krb5_krcc_unparse_keyblock
    (krb5_context, krb5_ccache id, krb5_keyblock * keyblock,
     krb5_krcc_bc * bc);
static krb5_error_code krb5_krcc_unparse_times
    (krb5_context, krb5_ccache id, krb5_ticket_times * t, krb5_krcc_bc * bc);
static krb5_error_code krb5_krcc_unparse_krb5data
    (krb5_context, krb5_ccache id, krb5_data * data, krb5_krcc_bc * bc);
static krb5_error_code krb5_krcc_unparse_int32
    (krb5_context, krb5_ccache id, krb5_int32 i, krb5_krcc_bc * bc);
static krb5_error_code krb5_krcc_unparse_octet
    (krb5_context, krb5_ccache id, krb5_int32 i, krb5_krcc_bc * bc);
static krb5_error_code krb5_krcc_unparse_addrs
    (krb5_context, krb5_ccache, krb5_address ** a, krb5_krcc_bc * bc);
static krb5_error_code krb5_krcc_unparse_addr
    (krb5_context, krb5_ccache, krb5_address * a, krb5_krcc_bc * bc);
static krb5_error_code krb5_krcc_unparse_authdata
    (krb5_context, krb5_ccache, krb5_authdata ** ad, krb5_krcc_bc * bc);
static krb5_error_code krb5_krcc_unparse_authdatum
    (krb5_context, krb5_ccache, krb5_authdata * ad, krb5_krcc_bc * bc);
static krb5_error_code krb5_krcc_unparse_ui_4
    (krb5_context, krb5_ccache id, krb5_ui_4 i, krb5_krcc_bc * bc);
static krb5_error_code krb5_krcc_unparse_ui_2
    (krb5_context, krb5_ccache id, krb5_int32 i, krb5_krcc_bc * bc);

/* Note the following is a stub function for Linux */
extern krb5_error_code krb5_change_cache(void);

/*
 * Determine how many keys exist in a ccache keyring.
 * Subtracts out the "hidden" key holding the principal information.
 */
static int KRB5_CALLCONV
krb5_krcc_getkeycount(key_serial_t cred_ring)
{
    int res, nkeys;
    
    res = keyctl_read(cred_ring, NULL, 0);
    if (res > 0)
	nkeys = (res / sizeof(key_serial_t)) - 1;
    else
	nkeys = 0;
    return(nkeys);
}

/*
 * Modifies:
 * id
 *
 * Effects:
 * Creates/refreshes the cred cache id.  If the cache exists, its
 * contents are destroyed.
 *
 * Errors:
 * system errors
 * permission errors
 */

static krb5_error_code KRB5_CALLCONV
krb5_krcc_initialize(krb5_context context, krb5_ccache id,
		     krb5_principal princ)
{
    krb5_error_code kret;

    DEBUG_PRINT(("krb5_krcc_initialize: entered\n"));

    kret = k5_mutex_lock(&((krb5_krcc_data *) id->data)->lock);
    if (kret)
	return kret;

    kret = krb5_krcc_clearcache(context, id);
    if (kret != KRB5_OK)
	goto out;

    kret = krb5_krcc_save_principal(context, id, princ);
    if (kret == KRB5_OK)
	krb5_change_cache();

out:
    k5_mutex_unlock(&((krb5_krcc_data *) id->data)->lock);
    return kret;
}

/*
 * Modifies:
 * id
 *
 * Effects:
 * Invalidates the id, and frees any resources associated with the cache.
 * (Does NOT destroy the underlying ccache in the keyring.)
 */
static krb5_error_code KRB5_CALLCONV
krb5_krcc_close(krb5_context context, krb5_ccache id)
{
    krb5_krcc_data *d;

    DEBUG_PRINT(("krb5_krcc_close: entered\n"));

    d = (krb5_krcc_data *) id->data;

    krb5_xfree(d->name);
    k5_mutex_destroy(&d->lock);
    krb5_xfree(d);

    krb5_xfree(id);

    return KRB5_OK;
}

/*
 * Modifies:
 * id
 *
 * Effects:
 * Clears out a ccache keyring, unlinking all keys within it
 * (Including the "hidden" key with the principal information)
 *
 * Requires:
 * Must be called with mutex locked.
 *
 * Errors:
 * system errors
 */

static  krb5_error_code
krb5_krcc_clearcache(krb5_context context, krb5_ccache id)
{
    krb5_krcc_data *d;
    int     res;

    k5_assert_locked(&((krb5_krcc_data *) id->data)->lock);

    d = (krb5_krcc_data *) id->data;

    DEBUG_PRINT(("krb5_krcc_clearcache: ring_id %d, princ_id %d, "
		 "numkeys is %d\n", d->ring_id, d->princ_id, d->numkeys));

    res = keyctl_clear(d->ring_id);
    if (res != 0) {
	return errno;
    }
    d->numkeys = 0;
    d->princ_id = 0;

    return KRB5_OK;
}

/*
 * Effects:
 * Destroys the contents of id.
 *
 * Errors:
 * system errors
 */
static krb5_error_code KRB5_CALLCONV
krb5_krcc_destroy(krb5_context context, krb5_ccache id)
{
    krb5_error_code kret;
    krb5_krcc_data *d;
    int     res;

    DEBUG_PRINT(("krb5_krcc_destroy: entered\n"));

    d = (krb5_krcc_data *) id->data;

    kret = k5_mutex_lock(&d->lock);
    if (kret)
	return kret;

    krb5_krcc_clearcache(context, id);
    krb5_xfree(d->name);
    res = keyctl_unlink(d->ring_id, d->parent_id);
    if (res < 0) {
	kret = errno;
	DEBUG_PRINT(("krb5_krcc_destroy: unlinking key %d from ring %d: %s",
		     d->ring_id, d->parent_id, error_message(errno)));
	goto cleanup;
    }
cleanup:
    k5_mutex_unlock(&d->lock);
    k5_mutex_destroy(&d->lock);
    krb5_xfree(d);
    krb5_xfree(id);

    krb5_change_cache();

    return KRB5_OK;
}


/*
 * Requires:
 * residual is a legal path name, and a null-terminated string
 *
 * Modifies:
 * id
 *
 * Effects:
 * creates a cred cache that will reside in the keyring with
 * a name of <residual>.
 *
 * Returns:
 * A filled in krb5_ccache structure "id".
 *
 * Errors:
 * KRB5_CC_NOMEM - there was insufficient memory to allocate the
 *              krb5_ccache.  id is undefined.
 * permission errors
 */

static krb5_error_code KRB5_CALLCONV
krb5_krcc_resolve(krb5_context context, krb5_ccache * id, const char *full_residual)
{
    krb5_ccache lid;
    krb5_error_code kret;
    krb5_krcc_data *d;
    key_serial_t key;
    key_serial_t pkey = 0;
    int     nkeys = 0;
    int     res;
    krb5_krcc_ring_ids_t ids;
    key_serial_t ring_id;
    const char *residual;

    DEBUG_PRINT(("krb5_krcc_resolve: entered with name '%s'\n",
		 full_residual));

    res = krb5_krcc_get_ring_ids(&ids);
    if (res) {
	kret = EINVAL;
	DEBUG_PRINT(("krb5_krcc_resolve: Error getting ring id values!\n"));
	return kret;
    }

    if (strncmp(full_residual, "thread:", 7) == 0) {
	residual = full_residual + 7;
	ring_id = ids.thread;
    } else if (strncmp(full_residual, "process:", 8) == 0) {
	residual = full_residual + 8;
	ring_id = ids.process;
    } else {
	residual = full_residual;
	ring_id = ids.session;
    }

    DEBUG_PRINT(("krb5_krcc_resolve: searching ring %d for residual '%s'\n",
    		ring_id, residual));

    /*
     * Use keyctl_search instead of request_key. If we're supposed
     * to be looking for a process ccache, we shouldn't find a
     * thread ccache.
     * XXX But should we look in the session ring if we don't find it
     * in the process ring?  Same goes for thread.  Should we look in
     * the process and session rings if not found in the thread ring?
     *
     */
    key = keyctl_search(ring_id, KRCC_KEY_TYPE_KEYRING, residual, 0);
    if (key < 0) {
	key = add_key(KRCC_KEY_TYPE_KEYRING, residual, NULL, 0, ring_id);
	if (key < 0) {
	    kret = errno;
	    DEBUG_PRINT(("krb5_krcc_resolve: Error adding new "
			 "keyring '%s': %s\n", residual, strerror(errno)));
	    return kret;
	}
	DEBUG_PRINT(("krb5_krcc_resolve: new keyring '%s', "
			"key %d, added to keyring %d\n",
			residual, key, ring_id));
    } else {
	DEBUG_PRINT(("krb5_krcc_resolve: found existing "
			"key %d, with name '%s' in keyring %d\n",
			key, residual, ring_id));
	/* Determine key containing principal information */
	pkey = keyctl_search(key, KRCC_KEY_TYPE_USER,
			     KRCC_SPEC_PRINC_KEYNAME, 0);
	if (pkey < 0) {
	    DEBUG_PRINT(("krb5_krcc_resolve: Error locating principal "
			 "info for existing ccache in ring %d: %s\n",
			 key, strerror(errno)));
	    pkey = 0;
	}
	/* Determine how many keys exist */
	nkeys = krb5_krcc_getkeycount(key);
    }

    lid = (krb5_ccache) malloc(sizeof(struct _krb5_ccache));
    if (lid == NULL)
	return KRB5_CC_NOMEM;


    kret = krb5_krcc_new_data(residual, key, ring_id, &d);
    if (kret) {
	free(lid);
	return kret;
    }

    DEBUG_PRINT(("krb5_krcc_resolve: ring_id %d, princ_id %d, "
		 "nkeys %d\n", key, pkey, nkeys));
    d->princ_id = pkey;
    d->numkeys = nkeys;
    lid->ops = &krb5_krcc_ops;
    lid->data = d;
    lid->magic = KV5M_CCACHE;
    *id = lid;
    return KRB5_OK;
}

/*
 * Effects:
 * Prepares for a sequential search of the credentials cache.
 * Returns a krb5_cc_cursor to be used with krb5_krcc_next_cred and
 * krb5_krcc_end_seq_get.
 *
 * If the cache is modified between the time of this call and the time
 * of the final krb5_krcc_end_seq_get, the results are undefined.
 *
 * Errors:
 * KRB5_CC_NOMEM
 * system errors
 */
static krb5_error_code KRB5_CALLCONV
krb5_krcc_start_seq_get(krb5_context context, krb5_ccache id,
			krb5_cc_cursor * cursor)
{
    krb5_krcc_cursor krcursor;
    krb5_error_code kret;
    krb5_krcc_data *d;
    unsigned int size;
    int     res;

    DEBUG_PRINT(("krb5_krcc_start_seq_get: entered\n"));

    d = id->data;
    kret = k5_mutex_lock(&d->lock);
    if (kret)
	return kret;

    /*
     * Determine how many keys currently exist and update numkeys.
     * We cannot depend on the current value of numkeys because
     * the ccache may have been updated elsewhere
     */
    d->numkeys = krb5_krcc_getkeycount(d->ring_id);

    size = sizeof(*krcursor) + ((d->numkeys + 1) * sizeof(key_serial_t));

    krcursor = (krb5_krcc_cursor) malloc(size);
    if (krcursor == NULL) {
	k5_mutex_unlock(&d->lock);
	return KRB5_CC_NOMEM;
    }

    krcursor->keys = (key_serial_t *) ((char *) krcursor + sizeof(*krcursor));
    res = keyctl_read(d->ring_id, (char *) krcursor->keys,
		      ((d->numkeys + 1) * sizeof(key_serial_t)));
    if (res < 0 || res > ((d->numkeys + 1) * sizeof(key_serial_t))) {
	DEBUG_PRINT(("Read %d bytes from keyring, numkeys %d: %s\n",
		     res, d->numkeys, strerror(errno)));
	free(krcursor);
	k5_mutex_unlock(&d->lock);
	return KRB5_CC_IO;
    }

    krcursor->numkeys = d->numkeys;
    krcursor->currkey = 0;
    krcursor->princ_id = d->princ_id;

    k5_mutex_unlock(&d->lock);
    *cursor = (krb5_cc_cursor) krcursor;
    return KRB5_OK;
}

/*
 * Requires:
 * cursor is a krb5_cc_cursor originally obtained from
 * krb5_krcc_start_seq_get.
 *
 * Modifes:
 * cursor, creds
 *
 * Effects:
 * Fills in creds with the "next" credentals structure from the cache
 * id.  The actual order the creds are returned in is arbitrary.
 * Space is allocated for the variable length fields in the
 * credentials structure, so the object returned must be passed to
 * krb5_destroy_credential.
 *
 * The cursor is updated for the next call to krb5_krcc_next_cred.
 *
 * Errors:
 * system errors
 */
static krb5_error_code KRB5_CALLCONV
krb5_krcc_next_cred(krb5_context context, krb5_ccache id,
		    krb5_cc_cursor * cursor, krb5_creds * creds)
{
    krb5_krcc_cursor krcursor;
    krb5_error_code kret;
    int     psize;
    void   *payload = NULL;

    DEBUG_PRINT(("krb5_krcc_next_cred: entered\n"));

    /*
     * The cursor has the entire list of keys.
     * (Note that we don't support _remove_cred.)
     */
    krcursor = (krb5_krcc_cursor) * cursor;
    if (krcursor == NULL)
	return KRB5_CC_END;
    memset(creds, 0, sizeof(krb5_creds));

    /* If we're pointing past the end of the keys array, there are no more */
    if (krcursor->currkey > krcursor->numkeys)
	return KRB5_CC_END;

    /* If we're pointing at the entry with the principal, skip it */
    if (krcursor->keys[krcursor->currkey] == krcursor->princ_id) {
	krcursor->currkey++;
	/* Check if we have now reached the end */
	if (krcursor->currkey > krcursor->numkeys)
	  return KRB5_CC_END;
    }

    /* Read the key, the right size buffer will ba allocated and returned */
    psize = keyctl_read_alloc(krcursor->keys[krcursor->currkey], &payload);
    if (psize == -1) {
	DEBUG_PRINT(("Error reading key %d: %s\n",
		     krcursor->keys[krcursor->currkey],
		     strerror(errno)));
	kret = KRB5_FCC_NOFILE;
	goto freepayload;
    }
    krcursor->currkey++;

    kret = krb5_krcc_parse_cred(context, id, creds, payload, psize);

  freepayload:
    if (payload) free(payload);
    return kret;
}

/*
 * Requires:
 * cursor is a krb5_cc_cursor originally obtained from
 * krb5_krcc_start_seq_get.
 *
 * Modifies:
 * id, cursor
 *
 * Effects:
 * Finishes sequential processing of the keyring credentials ccache id,
 * and invalidates the cursor (it must never be used after this call).
 */
/* ARGSUSED */
static krb5_error_code KRB5_CALLCONV
krb5_krcc_end_seq_get(krb5_context context, krb5_ccache id,
		      krb5_cc_cursor * cursor)
{
    DEBUG_PRINT(("krb5_krcc_end_seq_get: entered\n"));

    free(*cursor);
    *cursor = 0L;
    return KRB5_OK;
}

/* Utility routine: Creates the back-end data for a keyring cache.

   Call with the global list lock held.  */
static  krb5_error_code
krb5_krcc_new_data(const char *name, key_serial_t ring,
		   key_serial_t parent_ring, krb5_krcc_data ** datapp)
{
    krb5_error_code kret;
    krb5_krcc_data *d;

    d = malloc(sizeof(krb5_krcc_data));
    if (d == NULL)
	return KRB5_CC_NOMEM;

    kret = k5_mutex_init(&d->lock);
    if (kret) {
	krb5_xfree(d);
	return kret;
    }

    d->name = strdup(name);
    if (d->name == NULL) {
	k5_mutex_destroy(&d->lock);
	krb5_xfree(d);
	return KRB5_CC_NOMEM;
    }
    d->princ_id = 0;
    d->ring_id = ring;
    d->parent_id = parent_ring;
    d->numkeys = 0;

    *datapp = d;
    return 0;
}

/* Utility routine: Creates a random memory ccache name. 
 * This algorithm was selected because it creates readable 
 * random ccache names in a fixed size buffer.  */

static krb5_error_code
random_string (krb5_context context, char *string, krb5_int32 length)
{
    static const unsigned char charlist[] =
	"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    krb5_error_code err = 0;
    unsigned char *bytes = NULL;
    size_t bytecount = length - 1;

    if (!err) {
	bytes = malloc (bytecount);
	if (bytes == NULL) { err = ENOMEM; }
    }

    if (!err) {
	krb5_data data;
	data.length = bytecount;
	data.data = (char *) bytes;
	err = krb5_c_random_make_octets (context, &data);
    }

    if (!err) {
	krb5_int32 i;
	for (i = 0; i < bytecount; i++) {
	    string [i] = charlist[bytes[i] % (sizeof (charlist) - 1)];
	}
	string[length - 1] = '\0';
    }

    if (bytes != NULL) { free (bytes); }

    return err;
}

/*
 * Effects:
 * Creates a new keyring cred cache whose name is guaranteed to be
 * unique.
 *
 * Returns:
 * The filled in krb5_ccache id.
 *
 * Errors:
 * KRB5_CC_NOMEM - there was insufficient memory to allocate the
 *              krb5_ccache.  id is undefined.
 */
static krb5_error_code KRB5_CALLCONV
krb5_krcc_generate_new(krb5_context context, krb5_ccache * id)
{
    krb5_ccache lid;
    char uniquename[8];
    krb5_error_code kret;
    krb5_krcc_data *d;
    key_serial_t ring_id = KEY_SPEC_SESSION_KEYRING;
    key_serial_t key;

    DEBUG_PRINT(("krb5_krcc_generate_new: entered\n"));

    /* Allocate memory */
    lid = (krb5_ccache) malloc(sizeof(struct _krb5_ccache));
    if (lid == NULL)
	return KRB5_CC_NOMEM;

    lid->ops = &krb5_krcc_ops;

    kret = k5_mutex_lock(&krb5int_krcc_mutex);
    if (kret) {
	free(lid);
	return kret;
    }

/* XXX These values are platform-specific and should not be here! */
/* XXX There is a bug in FC5 where these are not included in errno.h  */
#ifndef ENOKEY
#define ENOKEY          126     /* Required key not available */
#endif
#ifndef EKEYEXPIRED
#define EKEYEXPIRED     127     /* Key has expired */
#endif
#ifndef EKEYREVOKED
#define EKEYREVOKED     128     /* Key has been revoked */
#endif
#ifndef EKEYREJECTED
#define EKEYREJECTED    129     /* Key was rejected by service */
#endif

    /*
     * Loop until we successfully create a new ccache keyring with
     * a unique name, or we get an error.
     */
    while (1) {
	random_string(context, uniquename, sizeof(uniquename));
	DEBUG_PRINT(("krb5_krcc_generate_new: searching for name '%s'\n",
		     uniquename));
	key = keyctl_search(ring_id, KRCC_KEY_TYPE_KEYRING, uniquename, 0);
/*XXX*/ DEBUG_PRINT(("krb5_krcc_generate_new: after searching for '%s', key = %d, errno = %d\n", uniquename, key, errno));
	if (key < 0 && errno == ENOKEY) {
	    /* name does not already exist, create it to reserve the name */
	    key = add_key(KRCC_KEY_TYPE_KEYRING, uniquename, NULL, 0, ring_id);
	    if (key < 0) {
		kret = errno;
		DEBUG_PRINT(("krb5_krcc_generate_new: '%s' trying to "
			     "create '%s'\n", strerror(errno), uniquename));
		k5_mutex_unlock(&krb5int_krcc_mutex);
		return kret;
	    }
	    break;
	}
    }
	    
    kret = krb5_krcc_new_data(uniquename, key, ring_id, &d);
    k5_mutex_unlock(&krb5int_krcc_mutex);
    if (kret) {
	krb5_xfree(lid);
	return kret;
    }
    lid->data = d;
    *id = lid;
    krb5_change_cache();
    return KRB5_OK;
}

/*
 * Requires:
 * id is a keyring credential cache
 *
 * Returns:
 * The name of the keyring cred cache id.
 */
static const char *KRB5_CALLCONV
krb5_krcc_get_name(krb5_context context, krb5_ccache id)
{
    DEBUG_PRINT(("krb5_krcc_get_name: entered\n"));
    return (char *) ((krb5_krcc_data *) id->data)->name;
}

/*
 * Modifies:
 * id, princ
 *
 * Effects:
 * Retrieves the primary principal from id, as set with
 * krb5_krcc_initialize.  The principal is returned is allocated
 * storage that must be freed by the caller via krb5_free_principal.
 *
 * Errors:
 * system errors
 * KRB5_CC_NOMEM
 */
static krb5_error_code KRB5_CALLCONV
krb5_krcc_get_principal(krb5_context context, krb5_ccache id,
			krb5_principal * princ)
{
    DEBUG_PRINT(("krb5_krcc_get_principal: entered\n"));

    return krb5_krcc_retrieve_principal(context, id, princ);
}

static krb5_error_code KRB5_CALLCONV
krb5_krcc_retrieve(krb5_context context, krb5_ccache id,
		   krb5_flags whichfields, krb5_creds * mcreds,
		   krb5_creds * creds)
{
    DEBUG_PRINT(("krb5_krcc_retrieve: entered\n"));

    return krb5_cc_retrieve_cred_default(context, id, whichfields,
					 mcreds, creds);
}

/*
 * Non-functional stub implementation for krb5_krcc_remove
 *
 * Errors:
 *    KRB5_CC_NOSUPP - not implemented
 */
static krb5_error_code KRB5_CALLCONV
krb5_krcc_remove_cred(krb5_context context, krb5_ccache cache,
		      krb5_flags flags, krb5_creds * creds)
{
    DEBUG_PRINT(("krb5_krcc_remove_cred: entered (returning KRB5_CC_NOSUPP)\n"));

    return KRB5_CC_NOSUPP;
}

/*
 * Requires:
 * id is a cred cache returned by krb5_krcc_resolve or
 * krb5_krcc_generate_new, but has not been opened by krb5_krcc_initialize.
 *
 * Modifies:
 * id
 *
 * Effects:
 * Sets the operational flags of id to flags.
 */
static krb5_error_code KRB5_CALLCONV
krb5_krcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags)
{
    DEBUG_PRINT(("krb5_krcc_set_flags: entered\n"));

    return KRB5_OK;
}

static krb5_error_code KRB5_CALLCONV
krb5_krcc_get_flags(krb5_context context, krb5_ccache id, krb5_flags * flags)
{
    DEBUG_PRINT(("krb5_krcc_get_flags: entered\n"));

    *flags = 0;
    return KRB5_OK;
}

/* store: Save away creds in the ccache keyring.  */
static krb5_error_code KRB5_CALLCONV
krb5_krcc_store(krb5_context context, krb5_ccache id, krb5_creds * creds)
{
    krb5_error_code kret;
    krb5_krcc_data *d = (krb5_krcc_data *) id->data;
    char   *payload = NULL;
    unsigned int payloadlen;
    key_serial_t newkey;
    char   *keyname = NULL;

    DEBUG_PRINT(("krb5_krcc_store: entered\n"));

    kret = k5_mutex_lock(&d->lock);
    if (kret)
	return kret;

    /* Get the service principal name and use it as the key name */
    kret = krb5_unparse_name(context, creds->server, &keyname);
    if (kret) {
	DEBUG_PRINT(("Error unparsing service principal name!\n"));
	goto errout;
    }

    /* Serialize credential into memory */
    kret = krb5_krcc_unparse_cred(context, id, creds, &payload, &payloadlen);
    if (kret != KRB5_OK)
	goto errout;

    /* Add new key (credentials) into keyring */
    DEBUG_PRINT(("krb5_krcc_store: adding new key '%s' to keyring %d\n",
			keyname, d->ring_id));
    newkey = add_key(KRCC_KEY_TYPE_USER, keyname, payload,
		     payloadlen, d->ring_id);
    if (newkey < 0) {
	kret = errno;
	DEBUG_PRINT(("Error adding user key '%s': %s\n",
		     keyname, strerror(kret)));
    } else {
	d->numkeys++;
	kret = KRB5_OK;
    }

  errout:
    if (keyname)
	krb5_free_unparsed_name(context, keyname);
    if (payload)
	free(payload);

    k5_mutex_unlock(&d->lock);
    return kret;
}

static  krb5_error_code
krb5_krcc_save_principal(krb5_context context, krb5_ccache id,
			 krb5_principal princ)
{
    krb5_krcc_data *d;
    krb5_error_code kret;
    char   *payload;
    key_serial_t newkey;
    unsigned int payloadsize;
    krb5_krcc_bc bc;

    k5_assert_locked(&((krb5_krcc_data *) id->data)->lock);

    d = (krb5_krcc_data *) id->data;

    payload = malloc(GUESS_CRED_SIZE);
    if (payload == NULL)
	return KRB5_CC_NOMEM;

    bc.bpp = payload;
    bc.endp = payload + GUESS_CRED_SIZE;

    kret = krb5_krcc_unparse_principal(context, id, princ, &bc);
    CHECK_N_GO(kret, errout);

    /* Add new key into keyring */
    payloadsize = bc.bpp - payload;
#ifdef KRCC_DEBUG
    {
	krb5_error_code rc;
	char *princname = NULL;
	rc = krb5_unparse_name(context, princ, &princname);
	DEBUG_PRINT(("krb5_krcc_save_principal: adding new key '%s' "
		     "to keyring %d for principal '%s'\n",
		     KRCC_SPEC_PRINC_KEYNAME, d->ring_id,
		     rc ? "<unknown>" : princname));
	if (rc == 0)
	    krb5_free_unparsed_name(context, princname);
    }
#endif
    newkey = add_key(KRCC_KEY_TYPE_USER, KRCC_SPEC_PRINC_KEYNAME, payload,
    		     payloadsize, d->ring_id);
    if (newkey < 0) {
	kret = errno;
	DEBUG_PRINT(("Error adding principal key: %s\n", strerror(kret)));
    } else {
	d->princ_id = newkey;
	kret = KRB5_OK;
    }

  errout:
    free(payload);
    return kret;
}

static  krb5_error_code
krb5_krcc_retrieve_principal(krb5_context context, krb5_ccache id,
			     krb5_principal * princ)
{
    krb5_krcc_data *d = (krb5_krcc_data *) id->data;
    krb5_error_code kret;
    void   *payload = NULL;
    int     psize;
    krb5_krcc_bc bc;

    kret = k5_mutex_lock(&d->lock);
    if (kret)
	return kret;

    if (!d->princ_id) {
	princ = 0L;
	kret = KRB5_FCC_NOFILE;
	goto errout;
    }

    psize = keyctl_read_alloc(d->princ_id, &payload);
    if (psize == -1) {
	DEBUG_PRINT(("Reading principal key %d: %s\n",
		     d->princ_id, strerror(errno)));
	kret = KRB5_CC_IO;
	goto errout;
    }
    bc.bpp = payload;
    bc.endp = (char *)payload + psize;
    kret = krb5_krcc_parse_principal(context, id, princ, &bc);

  errout:
    if (payload)
	free(payload);
    k5_mutex_unlock(&d->lock);
    return kret;
}

static int
krb5_krcc_get_ring_ids(krb5_krcc_ring_ids_t *p)
{
    key_serial_t ids_key;
    char ids_buf[128];
    key_serial_t session, process, thread;
    long val;

    DEBUG_PRINT(("krb5_krcc_get_ring_ids: entered\n"));

    if (!p)
	return EINVAL;

    /* Use the defaults in case we find no ids key */
    p->session = KEY_SPEC_SESSION_KEYRING;
    p->process = KEY_SPEC_PROCESS_KEYRING;
    p->thread = KEY_SPEC_THREAD_KEYRING;

    /*
     * Note that in the "normal" case, this will not be found.
     * The Linux gssd creates this key while creating a
     * context to communicate the user's key serial numbers.
     */
    ids_key = request_key(KRCC_KEY_TYPE_USER, KRCC_SPEC_IDS_KEYNAME, NULL, 0);
    if (ids_key < 0)
	goto out;

    DEBUG_PRINT(("krb5_krcc_get_ring_ids: processing '%s' key %d\n",
		KRCC_SPEC_IDS_KEYNAME, ids_key));
    /*
     * Read and parse the ids file
     */
    memset(ids_buf, '\0', sizeof(ids_buf));
    val = keyctl_read(ids_key, ids_buf, sizeof(ids_buf));
    if (val > sizeof(ids_buf))
	goto out;

    val = sscanf(ids_buf, "%d:%d:%d", &session, &process, &thread);
    if (val != 3)
	goto out;

    p->session = session;
    p->process = process;
    p->thread = thread;

  out:
    DEBUG_PRINT(("krb5_krcc_get_ring_ids: returning %d:%d:%d\n",
		p->session, p->process, p->thread));
    return 0;
}

/*
 * ===============================================================
 * INTERNAL functions to parse a credential from a key payload
 * (Borrowed heavily from krb5_fcc_{read|store}_ funtions.)
 * ===============================================================
 */

/*
 * Effects:
 * Copies len bytes from the key payload buffer into buf.
 * Updates payload pointer and returns KRB5_CC_END if we
 * try to read past the end of the payload buffer's data.
 *
 * Requires:
 * Must be called with mutex locked.
 *
 * Errors:
 * KRB5_CC_END - there were not len bytes available
 */
static  krb5_error_code
krb5_krcc_parse(krb5_context context, krb5_ccache id, krb5_pointer buf,
		unsigned int len, krb5_krcc_bc * bc)
{
    DEBUG_PRINT(("krb5_krcc_parse: entered\n"));

    if ((bc->endp == bc->bpp) || (bc->endp - bc->bpp) < len)
	return KRB5_CC_END;

    memcpy(buf, bc->bpp, len);
    bc->bpp += len;

    return KRB5_OK;
}

/*
 * Take a key (credential) read from a keyring entry
 * and parse it into a credential structure.
 */
static  krb5_error_code
krb5_krcc_parse_cred(krb5_context context, krb5_ccache id, krb5_creds * creds,
		     char *payload, int psize)
{
    krb5_error_code kret;
    krb5_octet octet;
    krb5_int32 int32;
    krb5_krcc_bc bc;

    /* Parse the pieces of the credential */
    bc.bpp = payload;
    bc.endp = bc.bpp + psize;
    kret = krb5_krcc_parse_principal(context, id, &creds->client, &bc);
    CHECK_N_GO(kret, out);

    kret = krb5_krcc_parse_principal(context, id, &creds->server, &bc);
    CHECK_N_GO(kret, cleanclient);

    kret = krb5_krcc_parse_keyblock(context, id, &creds->keyblock, &bc);
    CHECK_N_GO(kret, cleanserver);

    kret = krb5_krcc_parse_times(context, id, &creds->times, &bc);
    CHECK_N_GO(kret, cleanserver);

    kret = krb5_krcc_parse_octet(context, id, &octet, &bc);
    CHECK_N_GO(kret, cleanserver);
    creds->is_skey = octet;

    kret = krb5_krcc_parse_int32(context, id, &int32, &bc);
    CHECK_N_GO(kret, cleanserver);
    creds->ticket_flags = int32;

    kret = krb5_krcc_parse_addrs(context, id, &creds->addresses, &bc);
    CHECK_N_GO(kret, cleanblock);

    kret = krb5_krcc_parse_authdata(context, id, &creds->authdata, &bc);
    CHECK_N_GO(kret, cleanaddrs);

    kret = krb5_krcc_parse_krb5data(context, id, &creds->ticket, &bc);
    CHECK_N_GO(kret, cleanauthdata);

    kret = krb5_krcc_parse_krb5data(context, id, &creds->second_ticket, &bc);
    CHECK_N_GO(kret, cleanticket);

    kret = KRB5_OK;
    goto out;

  cleanticket:
    memset(creds->ticket.data, 0, (unsigned) creds->ticket.length);
    krb5_xfree(creds->ticket.data);
  cleanauthdata:
    /* XXX ??? */
  cleanaddrs:
    krb5_free_addresses(context, creds->addresses);
  cleanblock:
    krb5_xfree(creds->keyblock.contents);
  cleanserver:
    krb5_free_principal(context, creds->server);
  cleanclient:
    krb5_free_principal(context, creds->client);

  out:
    return kret;
}

static  krb5_error_code
krb5_krcc_parse_principal(krb5_context context, krb5_ccache id,
			  krb5_principal * princ, krb5_krcc_bc * bc)
{
    krb5_error_code kret;
    register krb5_principal tmpprinc;
    krb5_int32 length, type;
    int     i;

    /* Read principal type */
    kret = krb5_krcc_parse_int32(context, id, &type, bc);
    if (kret != KRB5_OK)
	return kret;

    /* Read the number of components */
    kret = krb5_krcc_parse_int32(context, id, &length, bc);
    if (kret != KRB5_OK)
	return kret;

    if (length < 0)
	return KRB5_CC_NOMEM;

    tmpprinc = (krb5_principal) malloc(sizeof(krb5_principal_data));
    if (tmpprinc == NULL)
	return KRB5_CC_NOMEM;
    if (length) {
	size_t  msize = length;
	if (msize != length) {
	    free(tmpprinc);
	    return KRB5_CC_NOMEM;
	}
	tmpprinc->data = ALLOC(msize, krb5_data);
	if (tmpprinc->data == 0) {
	    free((char *) tmpprinc);
	    return KRB5_CC_NOMEM;
	}
    } else
	tmpprinc->data = 0;
    tmpprinc->magic = KV5M_PRINCIPAL;
    tmpprinc->length = length;
    tmpprinc->type = type;

    kret = krb5_krcc_parse_krb5data(context, id,
				    krb5_princ_realm(context, tmpprinc), bc);
    i = 0;
    CHECK(kret);

    for (i = 0; i < length; i++) {
	kret = krb5_krcc_parse_krb5data(context, id,
					krb5_princ_component(context, tmpprinc,
							     i), bc);
	CHECK(kret);
    }
    *princ = tmpprinc;
    return KRB5_OK;

  errout:
    while (--i >= 0)
	free(krb5_princ_component(context, tmpprinc, i)->data);
    free((char *) tmpprinc->data);
    free((char *) tmpprinc);
    return kret;
}

static  krb5_error_code
krb5_krcc_parse_keyblock(krb5_context context, krb5_ccache id,
			 krb5_keyblock * keyblock, krb5_krcc_bc * bc)
{
    krb5_error_code kret;
    krb5_ui_2 ui2;
    krb5_int32 int32;

    keyblock->magic = KV5M_KEYBLOCK;
    keyblock->contents = 0;

    kret = krb5_krcc_parse_ui_2(context, id, &ui2, bc);
    CHECK(kret);
    keyblock->enctype = ui2;

    kret = krb5_krcc_parse_int32(context, id, &int32, bc);
    CHECK(kret);
    if (int32 < 0)
	return KRB5_CC_NOMEM;
    keyblock->length = int32;
    /* Overflow check.  */
    if (keyblock->length != int32)
	return KRB5_CC_NOMEM;
    if (keyblock->length == 0)
	return KRB5_OK;
    keyblock->contents = ALLOC(keyblock->length, krb5_octet);
    if (keyblock->contents == NULL)
	return KRB5_CC_NOMEM;

    kret = krb5_krcc_parse(context, id, keyblock->contents,
			   keyblock->length, bc);
    CHECK(kret);

    return KRB5_OK;
  errout:
    if (keyblock->contents)
	krb5_xfree(keyblock->contents);
    return kret;
}

static  krb5_error_code
krb5_krcc_parse_times(krb5_context context, krb5_ccache id,
		      krb5_ticket_times * t, krb5_krcc_bc * bc)
{
    krb5_error_code kret;
    krb5_int32 i;

    kret = krb5_krcc_parse_int32(context, id, &i, bc);
    CHECK(kret);
    t->authtime = i;

    kret = krb5_krcc_parse_int32(context, id, &i, bc);
    CHECK(kret);
    t->starttime = i;

    kret = krb5_krcc_parse_int32(context, id, &i, bc);
    CHECK(kret);
    t->endtime = i;

    kret = krb5_krcc_parse_int32(context, id, &i, bc);
    CHECK(kret);
    t->renew_till = i;

    return 0;
  errout:
    return kret;
}

static  krb5_error_code
krb5_krcc_parse_krb5data(krb5_context context, krb5_ccache id,
			 krb5_data * data, krb5_krcc_bc * bc)
{
    krb5_error_code kret;
    krb5_int32 len;

    data->magic = KV5M_DATA;
    data->data = 0;

    kret = krb5_krcc_parse_int32(context, id, &len, bc);
    CHECK(kret);
    if (len < 0)
	return KRB5_CC_NOMEM;
    data->length = len;
    if (data->length != len || data->length + 1 == 0)
	return KRB5_CC_NOMEM;

    if (data->length == 0) {
	data->data = 0;
	return KRB5_OK;
    }

    data->data = (char *) malloc(data->length + 1);
    if (data->data == NULL)
	return KRB5_CC_NOMEM;

    kret = krb5_krcc_parse(context, id, data->data, (unsigned) data->length,
			   bc);
    CHECK(kret);

    data->data[data->length] = 0;	/* Null terminate, just in case.... */
    return KRB5_OK;
  errout:
    if (data->data)
	krb5_xfree(data->data);
    return kret;
}

static  krb5_error_code
krb5_krcc_parse_int32(krb5_context context, krb5_ccache id, krb5_int32 * i,
		      krb5_krcc_bc * bc)
{
    krb5_error_code kret;
    unsigned char buf[4];
    krb5_int32 val;

    kret = krb5_krcc_parse(context, id, buf, 4, bc);
    if (kret)
	return kret;
    val = buf[0];
    val = (val << 8) | buf[1];
    val = (val << 8) | buf[2];
    val = (val << 8) | buf[3];
    *i = val;
    return 0;
}

static  krb5_error_code
krb5_krcc_parse_octet(krb5_context context, krb5_ccache id, krb5_octet * i,
		      krb5_krcc_bc * bc)
{
    return krb5_krcc_parse(context, id, (krb5_pointer) i, 1, bc);
}

static  krb5_error_code
krb5_krcc_parse_addrs(krb5_context context, krb5_ccache id,
		      krb5_address *** addrs, krb5_krcc_bc * bc)
{
    krb5_error_code kret;
    krb5_int32 length;
    size_t  msize;
    int     i;

    *addrs = 0;

    /* Read the number of components */
    kret = krb5_krcc_parse_int32(context, id, &length, bc);
    CHECK(kret);

    /*
     * Make *addrs able to hold length pointers to krb5_address structs
     * Add one extra for a null-terminated list
     */
    msize = length;
    msize += 1;
    if (msize == 0 || msize - 1 != length || length < 0)
	return KRB5_CC_NOMEM;
    *addrs = ALLOC(msize, krb5_address *);
    if (*addrs == NULL)
	return KRB5_CC_NOMEM;

    for (i = 0; i < length; i++) {
	(*addrs)[i] = (krb5_address *) malloc(sizeof(krb5_address));
	if ((*addrs)[i] == NULL) {
	    krb5_free_addresses(context, *addrs);
	    return KRB5_CC_NOMEM;
	}
	kret = krb5_krcc_parse_addr(context, id, (*addrs)[i], bc);
	CHECK(kret);
    }

    return KRB5_OK;
  errout:
    if (*addrs)
	krb5_free_addresses(context, *addrs);
    return kret;
}

static  krb5_error_code
krb5_krcc_parse_addr(krb5_context context, krb5_ccache id, krb5_address * addr,
		     krb5_krcc_bc * bc)
{
    krb5_error_code kret;
    krb5_ui_2 ui2;
    krb5_int32 int32;

    addr->magic = KV5M_ADDRESS;
    addr->contents = 0;

    kret = krb5_krcc_parse_ui_2(context, id, &ui2, bc);
    CHECK(kret);
    addr->addrtype = ui2;

    kret = krb5_krcc_parse_int32(context, id, &int32, bc);
    CHECK(kret);
    if ((int32 & VALID_INT_BITS) != int32)	/* Overflow int??? */
	return KRB5_CC_NOMEM;
    addr->length = int32;
    /*
     * Length field is "unsigned int", which may be smaller
     * than 32 bits.
     */
    if (addr->length != int32)
	return KRB5_CC_NOMEM;	/* XXX */

    if (addr->length == 0)
	return KRB5_OK;

    addr->contents = (krb5_octet *) malloc(addr->length);
    if (addr->contents == NULL)
	return KRB5_CC_NOMEM;

    kret = krb5_krcc_parse(context, id, addr->contents, addr->length, bc);
    CHECK(kret);

    return KRB5_OK;
  errout:
    if (addr->contents)
	krb5_xfree(addr->contents);
    return kret;
}

static  krb5_error_code
krb5_krcc_parse_authdata(krb5_context context, krb5_ccache id,
			 krb5_authdata *** a, krb5_krcc_bc * bc)
{
    krb5_error_code kret;
    krb5_int32 length;
    size_t  msize;
    int     i;

    *a = 0;

    /* Read the number of components */
    kret = krb5_krcc_parse_int32(context, id, &length, bc);
    CHECK(kret);

    if (length == 0)
	return KRB5_OK;

    /*
     * Make *a able to hold length pointers to krb5_authdata structs
     * Add one extra for a null-terminated list
     */
    msize = length;
    msize += 1;
    if (msize == 0 || msize - 1 != length || length < 0)
	return KRB5_CC_NOMEM;
    *a = ALLOC(msize, krb5_authdata *);
    if (*a == NULL)
	return KRB5_CC_NOMEM;

    for (i = 0; i < length; i++) {
	(*a)[i] = (krb5_authdata *) malloc(sizeof(krb5_authdata));
	if ((*a)[i] == NULL) {
	    krb5_free_authdata(context, *a);
	    return KRB5_CC_NOMEM;
	}
	kret = krb5_krcc_parse_authdatum(context, id, (*a)[i], bc);
	CHECK(kret);
    }

    return KRB5_OK;
  errout:
    if (*a)
	krb5_free_authdata(context, *a);
    return kret;
}

static  krb5_error_code
krb5_krcc_parse_authdatum(krb5_context context, krb5_ccache id,
			  krb5_authdata * a, krb5_krcc_bc * bc)
{
    krb5_error_code kret;
    krb5_int32 int32;
    krb5_ui_2 ui2;

    a->magic = KV5M_AUTHDATA;
    a->contents = NULL;

    kret = krb5_krcc_parse_ui_2(context, id, &ui2, bc);
    CHECK(kret);
    a->ad_type = (krb5_authdatatype) ui2;
    kret = krb5_krcc_parse_int32(context, id, &int32, bc);
    CHECK(kret);
    if ((int32 & VALID_INT_BITS) != int32)	/* Overflow int??? */
	return KRB5_CC_NOMEM;
    a->length = int32;
    /*
     * Value could have gotten truncated if int is
     * smaller than 32 bits.
     */
    if (a->length != int32)
	return KRB5_CC_NOMEM;	/* XXX */

    if (a->length == 0)
	return KRB5_OK;

    a->contents = (krb5_octet *) malloc(a->length);
    if (a->contents == NULL)
	return KRB5_CC_NOMEM;

    kret = krb5_krcc_parse(context, id, a->contents, a->length, bc);
    CHECK(kret);

    return KRB5_OK;
  errout:
    if (a->contents)
	krb5_xfree(a->contents);
    return kret;

}

static  krb5_error_code
krb5_krcc_parse_ui_2(krb5_context context, krb5_ccache id, krb5_ui_2 * i,
		     krb5_krcc_bc * bc)
{
    krb5_error_code kret;
    unsigned char buf[2];

    kret = krb5_krcc_parse(context, id, buf, 2, bc);
    if (kret)
	return kret;
    *i = (buf[0] << 8) + buf[1];
    return 0;
}

/*
 * Requires:
 * locked mutex
 *
 * Effects:
 * Copies len bytes from buf into the payload buffer (bc->bpp).
 * Detects attempts to write past end of the payload buffer.
 * Updates payload buffer pointer accordingly.
 *
 * Errors:
 * system errors
 */
static  krb5_error_code
krb5_krcc_unparse(krb5_context context, krb5_ccache id, krb5_pointer buf,
		  unsigned int len, krb5_krcc_bc * bc)
{
    if (bc->bpp + len > bc->endp)
	return KRB5_CC_WRITE;

    memcpy(bc->bpp, buf, len);
    bc->bpp += len;

    return KRB5_OK;
}

static  krb5_error_code
krb5_krcc_unparse_principal(krb5_context context, krb5_ccache id,
			    krb5_principal princ, krb5_krcc_bc * bc)
{
    krb5_error_code kret;
    krb5_int32 i, length, tmp, type;

    type = krb5_princ_type(context, princ);
    tmp = length = krb5_princ_size(context, princ);

    kret = krb5_krcc_unparse_int32(context, id, type, bc);
    CHECK_OUT(kret);

    kret = krb5_krcc_unparse_int32(context, id, tmp, bc);
    CHECK_OUT(kret);

    kret = krb5_krcc_unparse_krb5data(context, id,
				      krb5_princ_realm(context, princ), bc);
    CHECK_OUT(kret);

    for (i = 0; i < length; i++) {
	kret = krb5_krcc_unparse_krb5data(context, id,
					  krb5_princ_component(context, princ,
							       i), bc);
	CHECK_OUT(kret);
    }

    return KRB5_OK;
}

static  krb5_error_code
krb5_krcc_unparse_keyblock(krb5_context context, krb5_ccache id,
			   krb5_keyblock * keyblock, krb5_krcc_bc * bc)
{
    krb5_error_code kret;

    kret = krb5_krcc_unparse_ui_2(context, id, keyblock->enctype, bc);
    CHECK_OUT(kret);
    kret = krb5_krcc_unparse_ui_4(context, id, keyblock->length, bc);
    CHECK_OUT(kret);
    return krb5_krcc_unparse(context, id, (char *) keyblock->contents,
			     keyblock->length, bc);
}

static  krb5_error_code
krb5_krcc_unparse_times(krb5_context context, krb5_ccache id,
			krb5_ticket_times * t, krb5_krcc_bc * bc)
{
    krb5_error_code kret;

    kret = krb5_krcc_unparse_int32(context, id, t->authtime, bc);
    CHECK_OUT(kret);
    kret = krb5_krcc_unparse_int32(context, id, t->starttime, bc);
    CHECK_OUT(kret);
    kret = krb5_krcc_unparse_int32(context, id, t->endtime, bc);
    CHECK_OUT(kret);
    kret = krb5_krcc_unparse_int32(context, id, t->renew_till, bc);
    CHECK_OUT(kret);
    return 0;
}

static  krb5_error_code
krb5_krcc_unparse_krb5data(krb5_context context, krb5_ccache id,
			   krb5_data * data, krb5_krcc_bc * bc)
{
    krb5_error_code kret;

    kret = krb5_krcc_unparse_ui_4(context, id, data->length, bc);
    CHECK_OUT(kret);
    return krb5_krcc_unparse(context, id, data->data, data->length, bc);
}

static  krb5_error_code
krb5_krcc_unparse_int32(krb5_context context, krb5_ccache id, krb5_int32 i,
			krb5_krcc_bc * bc)
{
    unsigned char buf[4];

    buf[3] = (unsigned char) (i & 0xFF);
    i >>= 8;
    buf[2] = (unsigned char) (i & 0xFF);
    i >>= 8;
    buf[1] = (unsigned char) (i & 0xFF);
    i >>= 8;
    buf[0] = (unsigned char) (i & 0xFF);
    return krb5_krcc_unparse(context, id, buf, 4, bc);
}

static  krb5_error_code
krb5_krcc_unparse_octet(krb5_context context, krb5_ccache id, krb5_int32 i,
			krb5_krcc_bc * bc)
{
    krb5_octet ibuf;

    ibuf = (krb5_octet) i;
    return krb5_krcc_unparse(context, id, (char *) &ibuf, 1, bc);
}

static  krb5_error_code
krb5_krcc_unparse_addrs(krb5_context context, krb5_ccache id,
			krb5_address ** addrs, krb5_krcc_bc * bc)
{
    krb5_error_code kret;
    krb5_address **temp;
    krb5_int32 i, length = 0;

    /* Count the number of components */
    if (addrs) {
	temp = addrs;
	while (*temp++)
	    length += 1;
    }

    kret = krb5_krcc_unparse_int32(context, id, length, bc);
    CHECK_OUT(kret);
    for (i = 0; i < length; i++) {
	kret = krb5_krcc_unparse_addr(context, id, addrs[i], bc);
	CHECK_OUT(kret);
    }

    return KRB5_OK;
}

static  krb5_error_code
krb5_krcc_unparse_addr(krb5_context context, krb5_ccache id,
		       krb5_address * addr, krb5_krcc_bc * bc)
{
    krb5_error_code kret;

    kret = krb5_krcc_unparse_ui_2(context, id, addr->addrtype, bc);
    CHECK_OUT(kret);
    kret = krb5_krcc_unparse_ui_4(context, id, addr->length, bc);
    CHECK_OUT(kret);
    return krb5_krcc_unparse(context, id, (char *) addr->contents,
			     addr->length, bc);
}

static  krb5_error_code
krb5_krcc_unparse_authdata(krb5_context context, krb5_ccache id,
			   krb5_authdata ** a, krb5_krcc_bc * bc)
{
    krb5_error_code kret;
    krb5_authdata **temp;
    krb5_int32 i, length = 0;

    if (a != NULL) {
	for (temp = a; *temp; temp++)
	    length++;
    }

    kret = krb5_krcc_unparse_int32(context, id, length, bc);
    CHECK_OUT(kret);
    for (i = 0; i < length; i++) {
	kret = krb5_krcc_unparse_authdatum(context, id, a[i], bc);
	CHECK_OUT(kret);
    }
    return KRB5_OK;
}

static  krb5_error_code
krb5_krcc_unparse_authdatum(krb5_context context, krb5_ccache id,
			    krb5_authdata * a, krb5_krcc_bc * bc)
{
    krb5_error_code kret;

    kret = krb5_krcc_unparse_ui_2(context, id, a->ad_type, bc);
    CHECK_OUT(kret);
    kret = krb5_krcc_unparse_ui_4(context, id, a->length, bc);
    CHECK_OUT(kret);
    return krb5_krcc_unparse(context, id, (krb5_pointer) a->contents,
			     a->length, bc);
}

static  krb5_error_code
krb5_krcc_unparse_ui_4(krb5_context context, krb5_ccache id, krb5_ui_4 i,
		       krb5_krcc_bc * bc)
{
    unsigned char buf[4];

    buf[3] = (unsigned char) (i & 0xFF);
    i >>= 8;
    buf[2] = (unsigned char) (i & 0xFF);
    i >>= 8;
    buf[1] = (unsigned char) (i & 0xFF);
    i >>= 8;
    buf[0] = (unsigned char) (i & 0xFF);
    return krb5_krcc_unparse(context, id, buf, 4, bc);
}

static  krb5_error_code
krb5_krcc_unparse_ui_2(krb5_context context, krb5_ccache id, krb5_int32 i,
		       krb5_krcc_bc * bc)
{
    unsigned char buf[2];

    buf[1] = (unsigned char) (i & 0xFF);
    i >>= 8;
    buf[0] = (unsigned char) (i & 0xFF);
    return krb5_krcc_unparse(context, id, buf, 2, bc);
}

/*
 * ===============================================================
 * INTERNAL functions to unparse a credential into a key payload
 * (Borrowed heavily from krb5_fcc_{read|store}_ funtions.)
 * ===============================================================
 */

/*
 * Take a credential structure and unparse it (serialize)
 * for storage into a keyring key entry.
 * Caller is responsible for freeing returned buffer.
 */
static  krb5_error_code
krb5_krcc_unparse_cred(krb5_context context, krb5_ccache id,
		       krb5_creds * creds, char **datapp, unsigned int *lenptr)
{
    krb5_error_code kret;
    char   *buf;
    krb5_krcc_bc bc;

    if (!creds || !datapp || !lenptr)
	return EINVAL;

    *datapp = NULL;
    *lenptr = 0;

    buf = malloc(GUESS_CRED_SIZE);
    if (buf == NULL)
	return KRB5_CC_NOMEM;

    bc.bpp = buf;
    bc.endp = buf + GUESS_CRED_SIZE;

    kret = krb5_krcc_unparse_principal(context, id, creds->client, &bc);
    CHECK_N_GO(kret, errout);

    kret = krb5_krcc_unparse_principal(context, id, creds->server, &bc);
    CHECK_N_GO(kret, errout);

    kret = krb5_krcc_unparse_keyblock(context, id, &creds->keyblock, &bc);
    CHECK_N_GO(kret, errout);

    kret = krb5_krcc_unparse_times(context, id, &creds->times, &bc);
    CHECK_N_GO(kret, errout);

    kret = krb5_krcc_unparse_octet(context, id, (krb5_int32) creds->is_skey,
				   &bc);
    CHECK_N_GO(kret, errout);

    kret = krb5_krcc_unparse_int32(context, id, creds->ticket_flags, &bc);
    CHECK_N_GO(kret, errout);

    kret = krb5_krcc_unparse_addrs(context, id, creds->addresses, &bc);
    CHECK_N_GO(kret, errout);

    kret = krb5_krcc_unparse_authdata(context, id, creds->authdata, &bc);
    CHECK_N_GO(kret, errout);

    kret = krb5_krcc_unparse_krb5data(context, id, &creds->ticket, &bc);
    CHECK_N_GO(kret, errout);

    kret = krb5_krcc_unparse_krb5data(context, id, &creds->second_ticket, &bc);
    CHECK_N_GO(kret, errout);

    /* Success! */
    *datapp = buf;
    *lenptr = bc.bpp - buf;
    kret = KRB5_OK;

  errout:
    return kret;
}

/*
 * ccache implementation storing credentials in the Linux keyring facility
 * The default is to put them at the session keyring level.
 * If "KEYRING:process:" or "KEYRING:thread:" is specified, then they will
 * be stored at the process or thread level respectively.
 */
const krb5_cc_ops krb5_krcc_ops = {
    0,
    "KEYRING",
    krb5_krcc_get_name,
    krb5_krcc_resolve,
    krb5_krcc_generate_new,
    krb5_krcc_initialize,
    krb5_krcc_destroy,
    krb5_krcc_close,
    krb5_krcc_store,
    krb5_krcc_retrieve,
    krb5_krcc_get_principal,
    krb5_krcc_start_seq_get,
    krb5_krcc_next_cred,
    krb5_krcc_end_seq_get,
    krb5_krcc_remove_cred,
    krb5_krcc_set_flags,
    krb5_krcc_get_flags		/* added after 1.4 release */
};

#else /* !USE_KEYRING_CCACHE */

/*
 * Export this, but it shouldn't be used.
 */
const krb5_cc_ops krb5_krcc_ops = {
    0,
    "KEYRING",
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL			/* added after 1.4 release */
};
#endif	/* USE_KEYRING_CCACHE */
