/* klog.c: description
 *
 * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
 * Written by David Howells (dhowells@redhat.com)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version
 * 2 of the License, or (at your option) any later version.
 *
 * Based on code:
 *
 * Copyright (c) 1995 - 2000 Kungliga Tekniska Högskolan
 * (Royal Institute of Technology, Stockholm, Sweden).
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#define _XOPEN_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <ctype.h>
#include <time.h>
#include <signal.h>
#include <errno.h>
#include <termios.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <keyutils.h>
//#include <rpc/xdr.h>
#include <kerberosIV/des.h>

#define KA_SERVICE 731
#define KA_TICKET_GRANTING_SERVICE 732

#define OSERROR(X, Y) do { if ((long)(X) == -1) { perror(Y); exit(1); } } while(0)

struct sockaddr_rxrpc {
	sa_family_t	srx_family;	/* address family */
	unsigned short	srx_service;	/* service desired */
	unsigned short	transport_type;	/* type of transport socket (SOCK_DGRAM) */
	unsigned short	transport_len;	/* length of transport address */
	union {
		sa_family_t family;		/* transport address family */
		struct sockaddr_in sin;		/* IPv4 transport address */
		struct sockaddr_in6 sin6;	/* IPv6 transport address */
	} transport;
};

#define AF_RXRPC		33
#define PF_RXRPC		AF_RXRPC
#define SOL_RXRPC		272
#define RXRPC_USER_CALL_ID	1	/* User call ID specifier */
#define RXRPC_ABORT		2	/* Abort request / notification */
#define RXRPC_ACK		3	/* [Server] RPC op final ACK received */
#define RXRPC_RESPONSE		4	/* [Server] security response received */
#define RXRPC_NET_ERROR		5	/* network error received */
#define RXRPC_BUSY		6	/* server busy received */
#define RXRPC_LOCAL_ERROR	7	/* local error generated */
#define RXRPC_PREPARE_CALL_SLOT	8	/* Propose user call ID specifier for next call */
#define RXRPC_SECURITY_KEY		1	/* [clnt] set client security key */
#define RXRPC_SECURITY_KEYRING		2	/* [srvr] set ring of server security keys */
#define RXRPC_EXCLUSIVE_CONNECTION	3	/* [clnt] use exclusive RxRPC connection */
#define RXRPC_MIN_SECURITY_LEVEL	4	/* minimum security level */

#define OSERROR(X, Y) do { if ((long)(X) == -1) { perror(Y); exit(1); } } while(0)

static const unsigned char local_addr[4] = { 0, 0, 0, 0 };
static const unsigned char remote_addr[4] = { 172, 16, 18, 91 };

#define RXRPC_ADD_CALLID(control, ctrllen, id)				\
do {									\
	struct cmsghdr *__cmsg;						\
	__cmsg = (void *)(control) + (ctrllen);				\
	__cmsg->cmsg_len	= CMSG_LEN(sizeof(unsigned long));	\
	__cmsg->cmsg_level	= SOL_RXRPC;				\
	__cmsg->cmsg_type	= RXRPC_USER_CALL_ID;			\
	*(unsigned long *)CMSG_DATA(__cmsg) = (id);			\
	(ctrllen) += __cmsg->cmsg_len;					\
									\
} while (0)

#define RXRPC_ADD_ABORT(control, ctrllen, abort_code)			\
do {									\
	struct cmsghdr *__cmsg;						\
	__cmsg = (void *)(control) + (ctrllen);				\
	__cmsg->cmsg_len	= CMSG_LEN(sizeof(unsigned long));	\
	__cmsg->cmsg_level	= SOL_RXRPC;				\
	__cmsg->cmsg_type	= RXRPC_ABORT;				\
	*(unsigned long *)CMSG_DATA(__cmsg) = (abort_code);		\
	(ctrllen) += __cmsg->cmsg_len;					\
									\
} while (0)

const char KA_GETTGT_REQ_LABEL[] = "gTGS";
const char KA_GETTGT_RPL_LABEL[] = "tgsT";
const char KA_GETTKT_RPL_LABEL[] = "gtkt";
#define KAA_Authenticate	21
#define KAA_AuthenticateV2	22
#define KAT_GetToken		23

struct ka_ticket {
	des_cblock	session_key;
	time_t		end_time;
	int		kvno;
	int		ticket_len;
	const char	*name;
	const char	*instance;
	const char	*cell;
	const char	*server_name;
	const char	*server_instance;
	const void	*ticket;
	char		_reply[0];
};

struct rxrpc_key_sec2_v1 {
	uint32_t	kver;			/* key payload interface version */
	uint16_t	security_index;		/* RxRPC header security index */
	uint16_t	ticket_length;		/* length of ticket[] */
	uint32_t	expiry;			/* time at which expires */
	uint32_t	kvno;			/* key version number */
	uint8_t		session_key[8];		/* DES session key */
	uint8_t		ticket[0];		/* the encrypted ticket */
};

/*****************************************************************************/
/*
 * dump the control messages
 */
static void dump_cmsg(struct msghdr *msg)
{
	struct cmsghdr *cmsg;
	unsigned long user_id;
	unsigned char *p;
	int abort_code;
	int n;

	for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
		n = cmsg->cmsg_len - CMSG_ALIGN(sizeof(*cmsg));
		p = CMSG_DATA(cmsg);

		printf("CMSG: %zu: ", cmsg->cmsg_len);

		if (cmsg->cmsg_level == SOL_RXRPC) {
			switch (cmsg->cmsg_type) {
			case RXRPC_USER_CALL_ID:
				printf("RXRPC_USER_CALL_ID: ");
				if (n != sizeof(user_id))
					goto dump_data;
				memcpy(&user_id, p, sizeof(user_id));
				printf("%lx\n", user_id);
				continue;

			case RXRPC_ABORT:
				printf("RXRPC_ABORT: ");
				if (n != sizeof(abort_code))
					goto dump_data;
				memcpy(&abort_code, p, sizeof(abort_code));
				printf("%d\n", abort_code);
				continue;

			case RXRPC_ACK:
				printf("RXRPC_ACK");
				if (n != 0)
					goto dump_data_colon;
				goto print_nl;

			case RXRPC_RESPONSE:
				printf("RXRPC_RESPONSE");
				if (n != 0)
					goto dump_data_colon;
				goto print_nl;

			case RXRPC_NET_ERROR:
				printf("RXRPC_NET_ERROR: ");
				if (n != sizeof(abort_code))
					goto dump_data;
				memcpy(&abort_code, p, sizeof(abort_code));
				printf("%s\n", strerror(abort_code));
				continue;

			case RXRPC_BUSY:
				printf("RXRPC_BUSY");
				if (n != 0)
					goto dump_data_colon;
				goto print_nl;

			case RXRPC_LOCAL_ERROR:
				printf("RXRPC_LOCAL_ERROR: ");
				if (n != sizeof(abort_code))
					goto dump_data;
				memcpy(&abort_code, p, sizeof(abort_code));
				printf("%s\n", strerror(abort_code));
				continue;

			default:
				break;
			}
		}

		printf("l=%d t=%d", cmsg->cmsg_level, cmsg->cmsg_type);

	dump_data_colon:
		printf(": ");
	dump_data:
		printf("{");
		for (; n > 0; n--, p++)
			printf("%02x", *p);

	print_nl:
		printf("}\n");
	}
}

void dump_data(void *p, size_t len)
{
#define INT_PER_LINE (8 * 4)
	size_t loop;
	unsigned char buf[41], *b, ch, yoffs;

	yoffs = 0;
	b = buf;
	for (loop = 0; loop < len; loop++) {
		if (!yoffs) {
			printf("%08zx: ", loop);
			yoffs = 1;
		}
		ch = *(const char *) p;
		printf("%02x", ch);
		*b++ = isprint(ch) ? ch : '.';
		p++;
		if (loop % INT_PER_LINE == INT_PER_LINE - 1) {
			*b = 0;
			printf(" %s\n", buf);
			b = buf;
			yoffs = 0;
		} else if (loop % 4 == 3) {
			putchar(' ');
		}
	}

	if (loop % INT_PER_LINE > 0) {
		for (loop %= INT_PER_LINE; loop < INT_PER_LINE; loop++) {
			printf("  ");
			if (loop % 4 == 3)
				putchar(' ');
		}
		*b = 0;
		printf("%s\n", buf);
	}
}

/*****************************************************************************/
/*
 *
 */
int ka_RPC(int service, const void *request, size_t reqlen, void *reply, size_t *_replen)
{
	struct sockaddr_rxrpc srx;
	struct msghdr msg;
	struct iovec iov[1];
	size_t ctrllen, replen;
	unsigned char control[4096];
	void *preply;
	int client, ret;

	client = socket(AF_RXRPC, SOCK_DGRAM, PF_INET);
	OSERROR(client, "socket");

	/* the authentication is associated with the virtual
	 * connection, so we need to open an exclusive channel */
	ret = setsockopt(client, SOL_RXRPC, RXRPC_EXCLUSIVE_CONNECTION,
			 NULL, 0);
	OSERROR(ret, "setsockopt");

	/* bind an address to the local endpoint */
	srx.srx_family = AF_RXRPC;
	srx.srx_service = 0; /* it's a client */
	srx.transport_type = SOCK_DGRAM;
	srx.transport_len = sizeof(srx.transport.sin);
	srx.transport.sin.sin_family = AF_INET;
	srx.transport.sin.sin_port = htons(7001);
	memcpy(&srx.transport.sin.sin_addr, &local_addr, 4);

	ret = bind(client, (struct sockaddr *) &srx, sizeof(srx));
	OSERROR(ret, "bind");

	/* connect to the remote server */
	srx.srx_family = AF_RXRPC;
	srx.srx_service = service;
	srx.transport_type = SOCK_DGRAM;
	srx.transport_len = sizeof(srx.transport.sin);
	srx.transport.sin.sin_family = AF_INET;
	srx.transport.sin.sin_port = htons(7004);
	memcpy(&srx.transport.sin.sin_addr, &remote_addr, 4);

	ret = connect(client, (struct sockaddr *) &srx, sizeof(srx));
	OSERROR(ret, "connect");

	/* request an operation */
	ctrllen = 0;
	RXRPC_ADD_CALLID(control, ctrllen, 0x12345);

	iov[0].iov_base = (void *) request;
	iov[0].iov_len = reqlen;

	msg.msg_name		= NULL;
	msg.msg_namelen		= 0;
	msg.msg_iov		= iov;
	msg.msg_iovlen		= 1;
	msg.msg_control		= control;
	msg.msg_controllen	= ctrllen;
	msg.msg_flags		= 0;

	ret = sendmsg(client, &msg, 0);
	if (ret == -1 && (errno == ENOANO || errno == ECONNABORTED))
		perror("sendmsg/data");
	else
		OSERROR(ret, "sendmsg/data");

	/* wait for a reply */
	preply = reply;
	replen = 0;
	while (replen < *_replen) {
		iov[0].iov_base = preply;
		iov[0].iov_len = *_replen - replen;

		msg.msg_name	= NULL;
		msg.msg_namelen	= 0;
		msg.msg_iov	= iov;
		msg.msg_iovlen	= 1;
		msg.msg_control	= control;
		msg.msg_controllen = sizeof(control);
		msg.msg_flags	= 0;

		ret = recvmsg(client, &msg, 0);
		OSERROR(ret, "recvmsg");

		printf("RECV: %d [fl:%d]\n", ret, msg.msg_flags);
		printf("CMSG: %zu\n", msg.msg_controllen);
		printf("IOV: %zu [0]=%zu\n", msg.msg_iovlen, iov[0].iov_len);
		dump_cmsg(&msg);

		preply += ret;
		replen += ret;

		if (msg.msg_flags & MSG_EOR)
			break;
	}

	*_replen = replen;
	close(client);
	return 0;
}

/*****************************************************************************/
/*
 * authenticate this RxRPC virtual connection with the KA server
 */
int ka_Authenticate(const char *principal,
		    const char *realm,
		    int service,
		    des_cblock _key,
		    time_t start_time,
		    time_t end_time,
		    struct ka_ticket **_tgt)
{
	struct ka_ticket *tgt;
	des_key_schedule sched;
	char buffer[16384] __attribute__((aligned(4)));
	char data[8], *p, *end, *q;
	unsigned int tmp;
	des_cblock key;
	size_t len;
	time_t challtime;
	int ret;

	memcpy(key, _key, sizeof(key));
	ret = des_key_sched(key, sched);
	OSERROR(ret, "des_key_sched");

	tmp = htonl(start_time);
	memcpy(data, &tmp, 4);
	memcpy(data + 4, KA_GETTGT_REQ_LABEL, 4);

	des_pcbc_encrypt((void *) data, (void *) data,
			 sizeof(data), sched, &key, DES_ENCRYPT);

	/* marshall the arguments */
	p = buffer;
	*(unsigned int *)p = htonl(KAA_AuthenticateV2);
	p += 4;

	len = strlen(principal);
	*(unsigned int *)p = htonl(len);
	p += 4;
	memcpy(p, principal, len);
	p += len;
	while ((unsigned long) p & 3)
		*p++ = 0;

	realm = NULL;
	if (realm) {
		len = strlen(realm);
		*(unsigned int *)p = htonl(len);
		p += 4;
		memcpy(p, realm, len);
		p += len;
		while ((unsigned long) p & 3)
			*p++ = 0;
	} else {
		*(unsigned int *)p = htonl(0);
		p += 4;
	}

	*(unsigned int *)p = htonl(start_time);
	p += 4;
	*(unsigned int *)p = htonl(end_time);
	p += 4;

	len = sizeof(data);
	*(unsigned int *)p = htonl(len);
	p += 4;
	memcpy(p, data, len);
	p += len;
	while ((unsigned long) p & 3)
		*p++ = 0;

	*(unsigned int *)p = htonl(0x3044);
	p += 4;
	*(unsigned int *)p = htonl(0);
	p += 4;

	/* make the call */
	len = sizeof(buffer);
	ret = ka_RPC(KA_SERVICE, buffer, p - buffer, buffer, &len);
	OSERROR(buffer, "ka_RPC");

	/* unmarshall the reply */
	p = buffer;
	if (len < 8)
		goto bad_reply;

	if (ntohl(*(unsigned int *)p) != 0x3044)
		goto bad_reply;
	p += 4;

	len -= 8;
	tmp = ntohl(*(unsigned int *)p);
	p += 4;

	if (((tmp + 3) & ~3) != len)
		goto bad_reply;

	if (len < 32 + 5 + 8)
		goto bad_reply;

	tgt = malloc(sizeof(struct ka_ticket) + len);
	if (!tgt) {
		perror("malloc");
		exit(1);
	}

	des_pcbc_encrypt((void *) p, (void *) tgt->_reply, len, sched, &key, DES_DECRYPT);
	dump_data(tgt->_reply, len);
	end = tgt->_reply + len;

	/* decode the reply */
	memcpy(&tgt->session_key, tgt->_reply + 8, 8);
	challtime	= ntohl(*(unsigned *)(tgt->_reply + 4));
	tgt->end_time	= ntohl(*(unsigned *)(tgt->_reply + 20));
	tgt->kvno	= ntohl(*(unsigned *)(tgt->_reply + 24));
	tgt->ticket_len	= ntohl(*(unsigned *)(tgt->_reply + 28));

	p = tgt->_reply + 32;

	tgt->name = p;
	q = memchr(p, 0, end - p);
	if (!q)
		goto bad_reply;
	p = q + 1;

	tgt->instance = p;
	q = memchr(p, 0, end - p);
	if (!q)
		goto bad_reply;
	p = q + 1;

	tgt->cell = p;
	q = memchr(p, 0, end - p);
	if (!q)
		goto bad_reply;
	p = q + 1;

	tgt->server_name = p;
	q = memchr(p, 0, end - p);
	if (!q)
		goto bad_reply;
	p = q + 1;

	tgt->server_instance = p;
	q = memchr(p, 0, end - p);
	if (!q)
		goto bad_reply;
	p = q + 1;

	tgt->ticket = p;
	if (tgt->ticket_len > end - p)
		goto bad_reply;
	p += tgt->ticket_len;

	/* validate the reply */
	if (memcmp(p, KA_GETTGT_RPL_LABEL, sizeof(KA_GETTGT_RPL_LABEL) - 1) != 0)
		goto bad_reply;

	if (challtime != start_time + 1)
		goto bad_reply;

	printf("SKEY: %02x%02x%02x%02x%02x%02x%02x%02x\n",
	       tgt->session_key[0], tgt->session_key[1], tgt->session_key[2], tgt->session_key[3],
	       tgt->session_key[4], tgt->session_key[5], tgt->session_key[6], tgt->session_key[7]);
	printf("KVNO: %u\n", tgt->kvno);
	printf("TLEN: %u\n", tgt->ticket_len);
	printf("NAME: %s\n", tgt->name);
	printf("INST: %s\n", tgt->instance);
	printf("CELL: %s\n", tgt->cell);
	printf("SNAM: %s\n", tgt->server_name);
	printf("SINS: %s\n", tgt->server_instance);

	*_tgt = tgt;
	return 0;

bad_reply:
	fprintf(stderr, "Unmarshalling failure\n");
	errno = EBADMSG;
	return -1;
}

/*****************************************************************************/
/*
 *
 */
int ka_GetToken(const char *name,
		const char *instance,
		time_t start_time,
		time_t end_time,
		const struct ka_ticket *tgt,
		const char *auth_domain,
		struct ka_ticket **_ticket)
{
	struct ka_ticket *ticket;
	des_key_schedule sched;
	unsigned int tmp;
	des_cblock key;
	char buffer[16384] __attribute__((aligned(4)));
	char tdata[8], data[8], *p, *end, *q;
	size_t len;
	int ret;

	memcpy(key, tgt->session_key, sizeof(key));
	dump_data(key, 8);
	ret = des_key_sched(key, sched);
	OSERROR(ret, "des_key_sched");

	tmp = htonl(start_time);
	memcpy(tdata, &tmp, 4);
	tmp = htonl(tgt->end_time);
	memcpy(tdata + 4, &tmp, 4);

	dump_data(tdata, 8);
	des_ecb_encrypt((void *) tdata, (void *) data, sched, DES_ENCRYPT);
	dump_data(data, 8);
	memset(tdata, 0xff, 8);
	ret = des_key_sched(key, sched);
	des_pcbc_encrypt((void *) data, (void *) tdata, sizeof(data),
			 sched, &key, DES_DECRYPT);
	dump_data(tdata, 8);

	/* marshall the arguments */
	p = buffer;
	*(unsigned int *)p = htonl(KAT_GetToken);
	p += 4;
	*(unsigned int *)p = htonl(tgt->kvno);
	p += 4;

	len = strlen(auth_domain);
	*(unsigned int *)p = htonl(len);
	p += 4;
	memcpy(p, auth_domain, len);
	p += len;
	while ((unsigned long) p & 3)
		*p++ = 0;

	len = tgt->ticket_len;
	*(unsigned int *)p = htonl(len);
	p += 4;
	memcpy(p, tgt->ticket, len);
	p += len;
	while ((unsigned long) p & 3)
		*p++ = 0;

	len = strlen("afs"); // tgt->name
	*(unsigned int *)p = htonl(len);
	p += 4;
	memcpy(p, "afs", len);
	p += len;
	while ((unsigned long) p & 3)
		*p++ = 0;

	len = strlen(tgt->instance);
	*(unsigned int *)p = htonl(len);
	p += 4;
	memcpy(p, tgt->instance, len);
	p += len;
	while ((unsigned long) p & 3)
		*p++ = 0;

	len = sizeof(data);
	*(unsigned int *)p = htonl(len);
	p += 4;
	memcpy(p, data, len);
	p += len;

	*(unsigned int *)p = htonl(0x3044);
	p += 4;
	*(unsigned int *)p = htonl(0);
	p += 4;

	/* make the call */
	len = sizeof(buffer);
	ret = ka_RPC(KA_TICKET_GRANTING_SERVICE,
		     buffer, p - buffer, buffer, &len);
	OSERROR(buffer, "ka_RPC");

	/* unmarshall the reply */
	p = buffer;
	if (len < 8)
		goto bad_reply;

	if (ntohl(*(unsigned int *)p) != 0x3044)
		goto bad_reply;
	p += 4;

	len -= 8;
	tmp = ntohl(*(unsigned int *)p);
	p += 4;

	if (((tmp + 3) & ~3) != len)
		goto bad_reply;

	if (len < 32 + 5 + 8)
		goto bad_reply;

	ticket = malloc(sizeof(struct ka_ticket) + len);
	if (!ticket) {
		perror("malloc");
		exit(1);
	}

	des_pcbc_encrypt((void *) p, (void *) ticket->_reply, len, sched, &key, DES_DECRYPT);
	dump_data(ticket->_reply, len);
	end = ticket->_reply + len;

	/* decode the reply */
	memcpy(&ticket->session_key, ticket->_reply + 8, 8);
	ticket->end_time	= ntohl(*(unsigned *)(ticket->_reply + 20));
	ticket->kvno		= ntohl(*(unsigned *)(ticket->_reply + 24));
	ticket->ticket_len	= ntohl(*(unsigned *)(ticket->_reply + 28));

	p = ticket->_reply + 32;

	ticket->name = p;
	q = memchr(p, 0, end - p);
	if (!q)
		goto bad_reply;
	p = q + 1;

	ticket->instance = p;
	q = memchr(p, 0, end - p);
	if (!q)
		goto bad_reply;
	p = q + 1;

	ticket->cell = p;
	q = memchr(p, 0, end - p);
	if (!q)
		goto bad_reply;
	p = q + 1;

	ticket->server_name = p;
	q = memchr(p, 0, end - p);
	if (!q)
		goto bad_reply;
	p = q + 1;

	ticket->server_instance = p;
	q = memchr(p, 0, end - p);
	if (!q)
		goto bad_reply;
	p = q + 1;

	ticket->ticket = p;
	if (ticket->ticket_len > end - p)
		goto bad_reply;
	p += ticket->ticket_len;

	/* validate the reply */
	if (memcmp(p, KA_GETTKT_RPL_LABEL, sizeof(KA_GETTKT_RPL_LABEL) - 1) != 0)
		goto bad_reply;

	printf("SKEY: %02x%02x%02x%02x%02x%02x%02x%02x\n",
	       ticket->session_key[0], ticket->session_key[1], ticket->session_key[2], ticket->session_key[3],
	       ticket->session_key[4], ticket->session_key[5], ticket->session_key[6], ticket->session_key[7]);
	printf("KVNO: %u\n", ticket->kvno);
	printf("TLEN: %u\n", ticket->ticket_len);
	printf("NAME: %s\n", ticket->name);
	printf("INST: %s\n", ticket->instance);
	printf("CELL: %s\n", ticket->cell);
	printf("SNAM: %s\n", ticket->server_name);
	printf("SINS: %s\n", ticket->server_instance);

	*_ticket = ticket;
	return 0;

bad_reply:
	fprintf(stderr, "Unmarshalling failure\n");
	errno = EBADMSG;
	return -1;
}

/*****************************************************************************/
/*
 *
 */
int ka_GetServerToken(const char *name,
		      const char *instance,
		      const char *cell,
		      unsigned lifetime,
		      struct ka_ticket *tgt,
		      struct ka_ticket **_ticket)
{
	struct timeval tv;
	int ret;

	ret = gettimeofday(&tv, NULL);
	OSERROR(ret, "gettimeofday");

	ret = ka_GetToken(name, instance,
			  tv.tv_sec, tv.tv_sec + lifetime, tgt,
			  "CAMBRIDGE.REDHAT.COM", /* auth domain */
			  _ticket);
	OSERROR(ret, "ka_GetToken");
	return ret;
}	      

/*****************************************************************************/
/*
 *
 */
int ka_UserAuthenticateGeneral(unsigned long flags,
			       const char *principal,
			       const char *instance,
			       const char *realm,
			       const char *password,
			       unsigned lifetime,
			       long spare1,
			       long spare2,
			       char **reason)
{
	struct rxrpc_key_sec2_v1 *payload;
	struct timeval tv;
	des_cblock key;
	struct ka_ticket *tgt, *ticket;
	char description[256];
	size_t plen;
	int ret;

	afs_string_to_key((char *) password, (char *) realm, key);

	ret = gettimeofday(&tv, NULL);
	OSERROR(ret, "gettimeofday");

	ret = ka_Authenticate(principal, instance, KA_TICKET_GRANTING_SERVICE,
			      key, tv.tv_sec, tv.tv_sec + lifetime, &tgt);
	OSERROR(ret, "ka_Authenticate");

	printf("\n----\n");
	ret = ka_GetServerToken(tgt->name, tgt->instance, tgt->cell, 3600 * 60 * 60,
				tgt, &ticket);
	OSERROR(ret, "ka_GetServerToken");

	plen = sizeof(*payload) + ticket->ticket_len;
	payload = calloc(1, plen + 4);
	if (!payload) {
		perror("calloc");
		exit(1);
	}

	/* use version 1 of the key data interface */
	payload->kver		= 1;
	payload->security_index	= 2;
	payload->ticket_length	= ticket->ticket_len;
	payload->expiry		= ticket->end_time;
	payload->kvno		= ticket->kvno;
	memcpy(payload->session_key, ticket->session_key, 8);
	memcpy(payload->ticket, ticket->ticket, ticket->ticket_len);

	sprintf(description, "afs@CAMBRIDGE.REDHAT.COM");
	ret = add_key("rxrpc", description, payload, plen, KEY_SPEC_SESSION_KEYRING);
	OSERROR(ret, "add_key");

	return 0;
}

/*****************************************************************************/
/*
 *
 */
int main(int argc, char *argv[])
{
	char password[100], *reason;
	int ret;

#if 0
	des_read_pw_string(password, sizeof(password), "Password: ", 0);
	printf("---->%s<---\n", password);
#endif
	strcpy(password, "custard2");

	ret = ka_UserAuthenticateGeneral(0,
					 "admin", /* principal/username */
					 "", /* instance */
					 "cambridge.redhat.com", /* realm */
					 password,
					 3600 * 60 * 60,
					 0,
					 0,
					 &reason);
	if (ret < 0) {
		perror(argv[0]);
		exit(1);
	}

	exit(0);
}
