/* aklog.c: request a Kerberos ticket grant for an AFS cell
 *
 * Copyright (C) 2003 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.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <krb5.h>
#include <com_err.h>
#include <kerberosIV/krb.h>
#include <afsutil.h>

extern int krb524_convert_creds_kdc(krb5_context, krb5_creds *, CREDENTIALS *);

char *realm = "CAMBRIDGE.REDHAT.COM"; //NULL;
char *cell = "cambridge.redhat.com"; //NULL;
int debug = 0;

krb5_principal	princ;
krb5_context	krb5context;
krb5_ccache	tktcache;

static void format(void) __attribute__((noreturn));
static void format(void)
{
	fprintf(stderr,
		"aklog [-d] [-c] [<cell>] [-k <realm>]\n");

	exit(2);
}

#define KRBERR(X,M) do { if ((X) != KSUCCESS) krberr((X),(M)); } while(0)

void krberr(errcode_t kerr, const char *where) __attribute__((noreturn));
void krberr(errcode_t kerr, const char *where)
{
	fprintf(stderr, "%s: %s\n", where, error_message(kerr));
	exit(1);
}

/*****************************************************************************/
/*
 * parse the argument list
 */
void parse_args(char **argv)
{
	if (!*argv)
		return;

	if (strcmp(argv[0], "-help") == 0)
		format();

	if (strcmp(argv[0], "-d") == 0) {
		debug++;
		argv++;
	}

	if (strcmp(argv[0], "-c") == 0)
		argv++;

	if (!*argv)
		format();

	if (argv[0][0] == '-')
		format();
	cell = *argv;
	argv++;

	if (!*argv)
		return;

	if (strcmp(argv[0], "-k") != 0)
		format();
	argv++;

	if (!*argv)
		return;

	if (argv[0][0] == '-')
		format();
	realm = *argv;
	argv++;

	if (*argv)
		format();

} /* end parse_args() */

/*****************************************************************************/
/*
 * try to obtain a kerberos ticket
 */
int obtain_ticket(krb5_creds **creds, const char *service, const char *cell)
{
	krb5_error_code kerr;
	krb5_creds request;

	/* set up a description of what we actually want */
	memset(&request, 0, sizeof(request));

	kerr = krb5_build_principal(krb5context, &request.server,
				    strlen(realm), realm,
				    service, cell,
				    NULL);
	KRBERR(kerr, "failed to construct request");

	request.client			= princ;
	request.times.endtime		= 0;
	request.keyblock.enctype	= ENCTYPE_DES_CBC_CRC;

	/* go and prod the Kerberos servers */
	kerr = krb5_get_credentials(krb5context, 0, tktcache, &request, creds);
	if (kerr != KSUCCESS && kerr != KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN)
		KRBERR(kerr, "error talking to KDC");

	return kerr == KSUCCESS;
} /* end obtain_ticket() */

/*****************************************************************************/
/*
 *
 */
int main(int argc, char *argv[])
{
	krb5_error_code kerr;
	unsigned char *cp;
	krb5_creds *creds5;
	CREDENTIALS creds4;
	time_t time;
	char buf1[100];
	int loop;

	parse_args(argv + 1);

	kerr = krb5_init_context(&krb5context);
	KRBERR(kerr, "failed to initialise the Kerberos5 context");

	/* may need to find the default realm */
	if (!realm) {
		kerr = krb5_get_default_realm(krb5context, &realm);
		KRBERR(kerr, "failed to get the default realm");
	}

	/* may need to find the default AFS cell */
	if (!cell) {
	}

	/* open the appropriate Kerberos ticket cache */
	kerr = krb5_cc_default(krb5context, &tktcache);
	KRBERR(kerr, "unable to resolve default cred cache");

	kerr = krb5_cc_get_principal(krb5context, tktcache, &princ);
	KRBERR(kerr, "unable to extract the principal from the cache");

	/* ask the KDC to give us an AFS ticket */
	if (!cell || !obtain_ticket(&creds5, "afs", cell)) {
		if (!obtain_ticket(&creds5, "afs", NULL)) {
			fprintf(stderr, "couldn't obtain AFS ticket\n");
			exit(2);
		}
	}

	kerr = krb5_cc_close(krb5context, tktcache);
	KRBERR(kerr, "error closing cache");

	/* ask the KDC to turn the Kerberos 5 ticket into a Kerberos 4
	 * ticket */
	kerr = krb524_convert_creds_kdc(krb5context, creds5, &creds4);
	KRBERR(kerr, "unable to convert to a kerberos V4 ticket");

	/* dump the credential data obtained */
	if (debug) {
		printf("SERVICE  : %s%s%s@%s\n",
		       creds4.service,
		       creds4.instance[0] ? "/" : "",
		       creds4.instance,
		       creds4.realm);
		printf("PRINCIPAL: %s%s%s@%s\n",
		       creds4.pname,
		       creds4.pinst[0] ? "/" : "", creds4.pinst,
		       creds4.realm);

		time = creds4.issue_date;
		printf("ISSUED   : %s", ctime_r(&time, buf1));

		time = creds5->times.endtime;
		printf("EXPIRES  : %s", ctime_r(&time, buf1));

		printf("SESSION  : key=[");
		cp = (unsigned char *) &creds4.session;
		for (loop = 0; loop < sizeof(creds4.session); loop++)
			printf("%02x", *cp++);
		printf("]\n");

		printf("TICKET   : version %d, length %d:",
		       creds4.kvno, creds4.ticket_st.length);

		cp = (unsigned char *) &creds4.ticket_st.dat;
		for (loop = 0; loop < creds4.ticket_st.length; loop++) {
			if (loop % (76 / 2) == 0)
				printf("\n    ");
			printf("%02x", *cp++);
		}
		printf("\n");
	}

	/* pass the ticket to the AFS filesystem */
	if (afsutil_authorise_krb5(cell,
				   realm,
				   creds5->times.endtime,
				   sizeof(creds4.session),
				   &creds4.session,
				   creds4.kvno,
				   creds4.ticket_st.length,
				   creds4.ticket_st.dat) < 0
	    ) {
		fprintf(stderr, "unable to pass token to kernel: %m\n");
		exit(1);
	}

	krb5_free_creds(krb5context, creds5);

	krb5_free_context(krb5context);

	return 0;
} /* end main() */
