/* 
 *  Unix SMB/CIFS implementation.
 *  Generate AFS tickets
 *  Copyright (C) Volker Lendecke 2004
 *
 *  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 3 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

#include "includes.h"

#ifdef WITH_FAKE_KASERVER

#define NO_ASN1_TYPEDEFS 1

#include <afs/stds.h>
#include <afs/afs.h>
#include <afs/auth.h>
#include <afs/venus.h>
#include <asm/unistd.h>
#include <openssl/des.h>
#include <sys/syscall.h>

int afs_syscall( int subcall,
	  char * path,
	  int cmd,
	  char * cmarg,
	  int follow)
{
	return( syscall( SYS_afs_syscall, subcall, path, cmd, cmarg, follow));
}

struct ClearToken {
	uint32 AuthHandle;
	char HandShakeKey[8];
	uint32 ViceId;
	uint32 BeginTimestamp;
	uint32 EndTimestamp;
};

static bool afs_decode_token(const char *string, char **cell,
			     DATA_BLOB *ticket, struct ClearToken *ct)
{
	DATA_BLOB blob;
	struct ClearToken result_ct;

	char *s = SMB_STRDUP(string);

	char *t;

	if ((t = strtok(s, "\n")) == NULL) {
		DEBUG(10, ("strtok failed\n"));
		return False;
	}

	*cell = SMB_STRDUP(t);

	if ((t = strtok(NULL, "\n")) == NULL) {
		DEBUG(10, ("strtok failed\n"));
		return False;
	}

	if (sscanf(t, "%u", &result_ct.AuthHandle) != 1) {
		DEBUG(10, ("sscanf AuthHandle failed\n"));
		return False;
	}
		
	if ((t = strtok(NULL, "\n")) == NULL) {
		DEBUG(10, ("strtok failed\n"));
		return False;
	}

	blob = base64_decode_data_blob(t);

	if ( (blob.data == NULL) ||
	     (blob.length != sizeof(result_ct.HandShakeKey) )) {
		DEBUG(10, ("invalid key: %x/%d\n", (uint32)blob.data,
			   blob.length));
		return False;
	}

	memcpy(result_ct.HandShakeKey, blob.data, blob.length);

	data_blob_free(&blob);

	if ((t = strtok(NULL, "\n")) == NULL) {
		DEBUG(10, ("strtok failed\n"));
		return False;
	}

	if (sscanf(t, "%u", &result_ct.ViceId) != 1) {
		DEBUG(10, ("sscanf ViceId failed\n"));
		return False;
	}
		
	if ((t = strtok(NULL, "\n")) == NULL) {
		DEBUG(10, ("strtok failed\n"));
		return False;
	}

	if (sscanf(t, "%u", &result_ct.BeginTimestamp) != 1) {
		DEBUG(10, ("sscanf BeginTimestamp failed\n"));
		return False;
	}
		
	if ((t = strtok(NULL, "\n")) == NULL) {
		DEBUG(10, ("strtok failed\n"));
		return False;
	}

	if (sscanf(t, "%u", &result_ct.EndTimestamp) != 1) {
		DEBUG(10, ("sscanf EndTimestamp failed\n"));
		return False;
	}
		
	if ((t = strtok(NULL, "\n")) == NULL) {
		DEBUG(10, ("strtok failed\n"));
		return False;
	}

	blob = base64_decode_data_blob(t);

	if (blob.data == NULL) {
		DEBUG(10, ("Could not get ticket\n"));
		return False;
	}

	*ticket = blob;
	*ct = result_ct;

	return True;
}

/*
  Put an AFS token into the Kernel so that it can authenticate against
  the AFS server. This assumes correct local uid settings.

  This is currently highly Linux and OpenAFS-specific. The correct API
  call for this would be ktc_SetToken. But to do that we would have to
  import a REALLY big bunch of libraries which I would currently like
  to avoid. 
*/

static bool afs_settoken(const char *cell,
			 const struct ClearToken *ctok,
			 DATA_BLOB ticket)
{
	int ret;
	struct {
		char *in, *out;
		uint16 in_size, out_size;
	} iob;

	char buf[1024];
	char *p = buf;
	int tmp;

	memcpy(p, &ticket.length, sizeof(uint32));
	p += sizeof(uint32);
	memcpy(p, ticket.data, ticket.length);
	p += ticket.length;

	tmp = sizeof(struct ClearToken);
	memcpy(p, &tmp, sizeof(uint32));
	p += sizeof(uint32);
	memcpy(p, ctok, tmp);
	p += tmp;

	tmp = 0;

	memcpy(p, &tmp, sizeof(uint32));
	p += sizeof(uint32);

	tmp = strlen(cell);
	if (tmp >= MAXKTCREALMLEN) {
		DEBUG(1, ("Realm too long\n"));
		return False;
	}

	strncpy(p, cell, tmp);
	p += tmp;
	*p = 0;
	p +=1;

	iob.in = buf;
	iob.in_size = PTR_DIFF(p,buf);
	iob.out = buf;
	iob.out_size = sizeof(buf);

#if 0
	file_save("/tmp/ioctlbuf", iob.in, iob.in_size);
#endif

	ret = afs_syscall(AFSCALL_PIOCTL, 0, VIOCSETTOK, (char *)&iob, 0);

	DEBUG(10, ("afs VIOCSETTOK returned %d\n", ret));
	return (ret == 0);
}

bool afs_settoken_str(const char *token_string)
{
	DATA_BLOB ticket;
	struct ClearToken ct;
	bool result;
	char *cell;

	if (!afs_decode_token(token_string, &cell, &ticket, &ct))
		return False;

	if (geteuid() != 0)
		ct.ViceId = getuid();

	result = afs_settoken(cell, &ct, ticket);

	SAFE_FREE(cell);
	data_blob_free(&ticket);

	return result;
}

#else

bool afs_settoken_str(const char *token_string)
{
	return False;
}

#endif