/* This file is part of the Project Athena Zephyr Notification System.
 * It contains source for the session dump and restore function.
 *
 *	Created by:	David Benjamin
 *
 *	$Id: 01c4444c7593a09e6568a3b2849858571c822bc5 $
 *
 *	Copyright (c) 2013 by the Massachusetts Institute of Technology.
 *	For copying and distribution information, see the file
 *	"mit-copyright.h".
 */

#ifndef lint
static const char rcsid_ZDumpSession_c[] = "$Id: 01c4444c7593a09e6568a3b2849858571c822bc5 $";
#endif

#include <internal.h>

#define SESSION_VERSION 1

Code_t
ZDumpSession(char **buffer,
             int *ret_len)
{
#ifdef HAVE_KRB5
    struct _Z_SessionKey *key;
    uint32_t num_keys = 0;
#endif
    char *ptr;
    int len;

    /*
     * We serialize the port number and all keys. All numbers are
     * stored in big-endian. Byte strings are prefixed with a 32-bit
     * length. First field is 16-bit version number. Keys are stored
     * in reverse.
     */

    len = 2 + 2;  /* version, port number */
#ifdef HAVE_KRB5
    len += 4;  /* num_keys */
    for (key = Z_keys_head; key != NULL; key = key->next) {
	num_keys++;
	len += 4 + 4;  /* enctype, length */
	len += key->keyblock->length;  /* contents */
    }
#endif

    *ret_len = len;
    if (!(*buffer = (char *) malloc((unsigned)*ret_len)))
	return (ENOMEM);

    ptr = *buffer;
    *((uint16_t*) ptr) = htons(SESSION_VERSION); ptr += 2;
    *((uint16_t*) ptr) = htons(__Zephyr_port); ptr += 2;
#ifdef HAVE_KRB5
    *((uint32_t *)ptr) = htonl(num_keys); ptr += 4;
    for (key = Z_keys_tail; key != NULL; key = key->prev) {
	*((uint32_t*) ptr) = htonl(key->keyblock->enctype); ptr += 4;
	*((uint32_t*) ptr) = htonl(key->keyblock->length); ptr += 4;
	memcpy(ptr, key->keyblock->contents, key->keyblock->length);
	ptr += key->keyblock->length;
    }
#endif

    return (ZERR_NONE);
}

Code_t
ZLoadSession(char *buffer, int len)
{
#ifdef HAVE_KRB5
    struct _Z_SessionKey *key;
    uint32_t num_keys, keylength;
    krb5_enctype enctype;
    int i;
#endif
    Code_t ret;
    uint16_t version, port;

    if (len < 2) return (EINVAL);
    version = ntohs(*((uint16_t *) buffer)); buffer += 2; len -= 2;
    if (version != SESSION_VERSION)
	return (EINVAL);

    if (len < 2) return (EINVAL);
    port = ntohs(*((uint16_t *) buffer)); buffer += 2; len -= 2;
    if ((ret = ZOpenPort(&port)) != ZERR_NONE)
	return ret;

#ifdef HAVE_KRB5
    if (len < 4) return (EINVAL);
    num_keys = ntohl(*((uint32_t *) buffer)); buffer += 4; len -= 4;

    for (i = 0; i < num_keys; i++) {
	key = (struct _Z_SessionKey *)malloc(sizeof(struct _Z_SessionKey));
	if (!key)
	    return (ENOMEM);
	if (len < 4) {
	    free(key);
	    return (EINVAL);
	}
	enctype = ntohl(*((uint32_t *) buffer)); buffer += 4; len -= 4;
	if (len < 4) {
	    free(key);
	    return (EINVAL);
	}
	keylength = ntohl(*((uint32_t *) buffer)); buffer += 4; len -= 4;
	if (len < keylength) {
	    free(key);
	    return (EINVAL);
	}
	ret = krb5_init_keyblock(Z_krb5_ctx, enctype, keylength, &key->keyblock);
	if (ret) {
	    free(key);
	    return ret;
	}
	memcpy((char *)key->keyblock->contents, buffer, keylength);
	buffer += keylength; len -= keylength;
	/* Just set recent times. It means we might not be able to
	   retire the keys, but that's fine. */
	key->send_time = time(NULL);
	key->first_use = time(NULL);
	/* Prepend to the key list. */
	key->prev = NULL;
	key->next = Z_keys_head;
	if (Z_keys_head)
	    Z_keys_head->prev = key;
	Z_keys_head = key;
	if (!Z_keys_tail)
	    Z_keys_tail = key;
    }
#endif

    if (len)
	return (EINVAL);
    return (ZERR_NONE);
}
