/* key_afs.c: AFS filesystem keys
 *
 * 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 <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/key.h>
#include <linux/seq_file.h>
#include <asm/errno.h>
#include "cell.h"
#include "internal.h"

static int afs_key_init(struct key *key, const char *desc,
			size_t datalen, const char *data);
static int afs_key_match(const struct key *key, const void *desc);
static void afs_key_clear(struct key *key);
static void afs_key_describe(const struct key *keyring, struct seq_file *m);

struct afs_key_data {
	uint16_t	session_key_size;
	uint16_t	ticket_size;
	int32_t		kvno;
	time_t		expiry;
	uint8_t		data[0];
};

/* AFS Kerberos ticket
 * - the description must be the name of the cell to which applicable
 * - the data is a struct afs_key_data
 */
struct key_type key_type_afs = {
	.name		= "afs",
	.link		= LIST_HEAD_INIT(key_type_afs.link),
	.init		= afs_key_init,
	.match		= afs_key_match,
	.clear		= afs_key_clear,
	.describe	= afs_key_describe,
};

static int afs_key_init(struct key *key, const char *desc,
			size_t datalen, const char *data)
{
	struct afs_key_data *keydata = (void *) data;
	size_t dlen;

	kenter("{%u},%s,%zu,{sk=%hu,tkt=%hu,v=%d,xp=%x}",
	       key->serial, desc, datalen,
	       keydata->session_key_size,
	       keydata->ticket_size,
	       keydata->kvno,
	       (int) keydata->expiry);

	dlen = strlen(desc) + 1;
	key->description.data = kmalloc(dlen, GFP_KERNEL);
	if (!key->description.data) {
		kleave(" = -ENOMEM");
		return -ENOMEM;
	}
	memcpy(key->description.data, desc, dlen);

	key->payload.data = kmalloc(datalen, GFP_KERNEL);
	if (!key->payload.data) {
		kleave(" = -ENOMEM");
		return -ENOMEM;
	}

	key->datalen = datalen;
	memcpy(key->payload.data, data, datalen);

	kleave(" = 0");
	return 0;
}

static int afs_key_match(const struct key *key, const void *desc)
{
	if (!key->description.data)
		return 0;

	return strcmp(key->description.data, desc) == 0 ? 1 : 0;
}

static void afs_key_clear(struct key *key)
{
	if (key->description.data)
		kfree(key->description.data);
	if (key->payload.data)
		kfree(key->payload.data);
}

static void afs_key_describe(const struct key *key, struct seq_file *m)
{
	struct afs_key_data *keydata;

	if (!key->description.data) {
		seq_puts(m, "[anon]");
		return;
	}

	keydata = key->payload.data;

	seq_printf(m, "%s => { s=%hu t=%hu v=%d x=%lx }",
		   (char *) key->description.data,
		   keydata->session_key_size,
		   keydata->ticket_size,
		   keydata->kvno,
		   (int) keydata->expiry - CURRENT_TIME.tv_sec);
}

int __init afs_key_register(void)
{
	return register_key_type(&key_type_afs);
}

void __exit afs_key_unregister(void)
{
	unregister_key_type(&key_type_afs);
}
