/*  ARCFour - a symmetric streaming cipher - Implimentation by Humble
 *
 *  This is a variable-key-length symmetric stream cipher developed in 1987
 *  by Ron Rivest (the R in RSA). It used to be proprietary but was reverse
 *  engineered and released publicly in September 1994. The cipher is now
 *  freely available but the name RC4 is a trademark of RSA Data Security
 *  Inc. This cipher is secure, proven, easy to impliment, and quite fast.
 */
#define IN_MODULE
#include "irc.h"
#include "struct.h"
#include "ircaux.h"
#include "ctcp.h"
#include "status.h"
#include "lastlog.h"
#include "server.h"
#include "screen.h"
#include "vars.h"
#include "misc.h"
#include "output.h"
#include "module.h"
#include "hash2.h"
#include "hook.h"
#include "dcc.h"
#include <stdlib.h>
#include <stdio.h>
#include <netinet/in.h>
#include <unistd.h>
#include "arcfour.h"
#include "md5.h"
#define INIT_MODULE
#include "modval.h"

typedef struct {
	int sock;
	char ukey[16];
	arckey *outbox;
	arckey *inbox;
} arclist;

/* from dcc.c. Why isnt this in dcc.h? */

typedef struct _DCC_List
{
	struct _DCC_List *next;
	char *nick;   /* do NOT free this. it's a shared pointer. */
	SocketList sock;
} DCC_List;
      

static arclist *keyboxes[16];

static const double arc_ver = 1.0;
static unsigned int typenum = 0;

/*
 * Initialize our box, and return the key used. It better be enough to hold
 * 16 chars + 1 null!
 */

static char *init_box(char *ukey, arckey *key)
{
	MD5_CTX md5context;
	char buf[256];
	int fd;

	fd = open("/dev/urandom", O_RDONLY);
	if (fd >= 0) {
		read(fd, buf, sizeof(buf));
		close(fd);
	}
	else
		return NULL;

	MD5Init(&md5context);
	MD5Update(&md5context, buf, sizeof(buf));
	MD5Final(&md5context);

	memcpy(ukey, buf, 16);
	ukey[16] = '\0';

	arcfourInit(key, md5context.digest, 16);
	return ukey;
}

static inline void arcfourInit(arckey *arc, char *userkey, unsigned short len)
{
	register arcword *S = arc->state, x = 0, y = 0, pos = 0, tmp;

	/* Seed the S-box linearly, then mix in the key while stiring briskly */
	arc->i = arc->j = 0;				 /* Initialize i and j to 0 */
	while(--x, (S[x] = x));				 /* Initialize S-box, backwards */

	/* Note: Some of these optimizations REQUIRE arcword to be 8-bit unsigned */
	do {						 /* Spread user key into real key */
		y += S[x] + userkey[pos];			 /* Keys, shaken, not stirred */
		tmp = S[x]; S[x] = S[y]; S[y] = tmp;  /* Swap S[i] and S[j] */
		if (++pos >= len)	pos = 0;		 /* Repeat user key to fill array */
	} while(++x);				 /* ++x is faster than x++ */
}

static inline char *arcfourCrypt(arckey *arc, char *data, int len)
{
	register arcword *S = arc->state, i = arc->i, j = arc->j, tmp;
	register int c = 0;

	do {
		j += S[++i];				/* Shake S-box, stir well */
		tmp = S[i]; S[i] = S[j]; S[j] = tmp; /* Swap S[i] and S[j] */
		data[c] ^= S[(S[i] + S[j])];		/* XOR the crypto into our data */
	} while (++c < len);				/* Neat, ++x is faster then x++ */

	arc->i = i;					/* Save modified i and j counters */
	arc->j = j;					/* Continue where we left off */
	return data;					/* Return pointer to ciphertext */
}

int Arcfour_Init(IrcCommandDll **intp, Function_ptr *global_table)
{
	initialize_module("arcfour");
	memset(keyboxes, 0, sizeof(keyboxes));
	typenum = add_dcc_bind("SCHAT", "schat", NULL, start_dcc_crypt, get_dcc_encrypt, send_dcc_encrypt, end_dcc_crypt);
	add_module_proc(DCC_PROC, "schat", "schat", "Secure DCC Chat", 0, 0, dcc_sdcc, NULL);
	return 0;
}

int Arcfour_Cleanup(IrcCommandDll **intp)
{
/*	remove_dcc_bind("SCHAT", typenum); */
	return 1;
}

static arclist *find_box(int sock)
{
	int i, tmp;
	tmp = sizeof(keyboxes)/sizeof(arclist *);
	for (i = 0; i < tmp; i++)
		if (keyboxes[i] && (keyboxes[i]->sock == sock))
			return keyboxes[i];
	return (arclist *)NULL;
}


static char *dcc_crypt (int sock, char *buf, int len)
{
	arclist *box;
	if ((box = find_box(sock))) {
		arcfourCrypt(box->outbox, buf, len-2);
		return buf;
	}
	return NULL;
}

static int send_dcc_encrypt (int type, int sock, char *buf, int len)
{
	if (type == DCC_CHAT) {
		if (dcc_crypt(sock, buf, len)) {
			write(sock, buf, len);
			return 0;
		}
	}
	return -1;
}

static int get_dcc_encrypt (int type, int sock, char *buf, int parm, int len)
{
	if (type == DCC_CHAT) {
		if ((len = dgets(buf, sock, parm, BIG_BUFFER_SIZE, NULL)) > 0) {
			buf[len-1] = '\0';
			dcc_crypt(sock, buf, len);
			if (buf[len])
				buf[len] = '\0';
		}
	}
	return len;
}

/* Here we initialize the cryptography. Send the other end our key, and read
 * in theirs. The socket "s" won't have a crypt box unless it is supposed to
 * an encrypted connection.
 */


static int start_dcc_crypt (int s, int type, unsigned long d_addr, int d_port)
{
	arclist *tmpbox;
	put_it("start_dcc_crypt");
	if ((tmpbox = find_box(s))) {
		int len;
		char randkey[17];
		char buf[BIG_BUFFER_SIZE+1];
		memset(randkey, 0, sizeof(randkey));
		memset(buf, 0, sizeof(buf));
		tmpbox->outbox = (arckey *)new_malloc(sizeof(arckey));
		if (init_box(randkey, tmpbox->outbox) == NULL)
		{
			new_free(&tmpbox->outbox);
			return -1;
		}
		snprintf(buf, BIG_BUFFER_SIZE, "SecureDCC %s\r\n%n", randkey, &len);
		write(s, buf, len);
		memset(buf, 0, sizeof(buf));
		if ((len = dgets(buf, s, 1, BIG_BUFFER_SIZE, NULL)) > 0) {
			if (!my_strnicmp("SecureDCC", buf, 9)) {
				tmpbox->inbox = (arckey *)new_malloc(sizeof(arckey));
       	arcfourInit(tmpbox->inbox, next_arg(buf, &buf), 16);
			}
		}
		return 0;
	}
	return -1;
}

static int end_dcc_crypt (int s, unsigned long d_addr, int d_port)
{
	int i;
	for(i = 0; i < 16; i++) {	
		if (keyboxes[i] && (keyboxes[i]->sock == s)) {
			new_free(&(keyboxes[i]->inbox));
			new_free(&(keyboxes[i]->outbox));
			new_free(&keyboxes[i]);
			return 0;
		}
	}
	return -1;
}


static void start_dcc_chat(int s)
{
struct	sockaddr_in	remaddr;
socklen_t	sra;
int	type;
int	new_s = -1;
char	*nick = NULL;	
unsigned long flags;
DCC_int *n = NULL;
SocketList *sa;

	sa = get_socket(s);
	flags = sa->flags;
	nick = sa->server;
	sra = sizeof(struct sockaddr_in);
	new_s = accept(s, (struct sockaddr *) &remaddr, &sra);
	type = flags & DCC_TYPES;
	n = get_socketinfo(s);
	if ((add_socketread(new_s, ntohs(remaddr.sin_port), flags, nick, get_dcc_encrypt, NULL)) < 0)
	{
		erase_dcc_info(s, 0, "%s", convert_output_format("$G %RDCC error: accept() failed. punt!!", NULL, NULL));
		close_socketread(s);
		return;
	}
	flags &= ~DCC_WAIT;
	flags |= DCC_ACTIVE;
	set_socketflags(new_s, flags);
	set_socketinfo(new_s, n);

/*
	put_it("%s", convert_output_format(fget_string_var(FORMAT_DCC_CONNECT_FSET), 
		"%s %s %s %s %s %d", update_clock(GET_TIME),
		dcc_types[type],
		nick, n->userhost?n->userhost:"u@h",
		inet_ntoa(remaddr.sin_addr),ntohs(remaddr.sin_port)));
*/
	get_time(&n->starttime);
	close_socketread(s);
	start_dcc_crypt(new_s, type, n->remote.s_addr, ntohs(remaddr.sin_port));
}

/* set up the dcc hooks */

void dcc_sdcc (char *name, char *args)
{
	char *p;
	if (!my_stricmp(name, "schat") && (strlen(args) > 0)) {
		if (*args == ' ')
			new_next_arg(args, &args);
		else {
			p = strchr(args, ' ');
			if (p && *p)
				*p = 0;
		}
		dcc_create(args, "SCHAT", NULL, 0, 0, typenum, DCC_TWOCLIENTS, start_dcc_chat);
	}
}
