/*
 * crypt.c: handles some encryption of messages stuff. 
 *
 * Written By Michael Sandrof
 *
 * Copyright(c) 1990, 1995 Michael Sandroff and others 
 *
 * See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT 
 */

#if 0
static	char	rcsid[] = "@(#)$Id: crypt.c,v 1.14 1994/07/02 02:32:13 mrg Exp $";
#endif

#include "irc.h"
#include "crypt.h"
#include "ctcp.h"
#include "ircaux.h"
#include "list.h"
#include "output.h"
#include "vars.h"

#define CRYPT_BUFFER_SIZE (IRCD_BUFFER_SIZE - 50)	/* Make this less than
							 * the transmittable
							 * buffer */

/*
 * Crypt: the crypt list structure,  consists of the nickname, and the
 * encryption key 
 */
typedef struct	CryptStru
{
	struct	CryptStru *next;
	char	*nick;
	char	*key;
}	Crypt;

/* crypt_list: the list of nicknames and encryption keys */
static	Crypt	*crypt_list = (Crypt *) 0;

/*
 * add_to_crypt: adds the nickname and key pair to the crypt_list.  If the
 * nickname is already in the list, then the key is changed the the supplied
 * key. 
 */
#ifdef __STDC__
static	void	add_to_crypt(char *nick, char *key)
#else
static	void 	add_to_crypt(nick, key)
	char	*nick;
	char	*key;
#endif
{
	Crypt	*new;

	if ((new = (Crypt *) remove_from_list((List **)&crypt_list, nick)) != NULL)
	{
		new_free((char **)&(new->nick));
		new_free((char **)&(new->key));
		new_free((char **)&new);
	}
	new = (Crypt *) new_malloc(sizeof(Crypt));
	new->nick = (char *) 0;
	new->key = (char *) 0;
	malloc_strcpy(&(new->nick), nick);
	malloc_strcpy(&(new->key), key);
	add_to_list((List **)&crypt_list, (List *)new);
}

/*
 * remove_crypt: removes the given nickname from the crypt_list, returning 0
 * if successful, and 1 if not (because the nickname wasn't in the list) 
 */
#ifdef __STDC__
static	int	remove_crypt (char *nick)
#else
static	int 	remove_crypt(nick)
	char	*nick;
#endif
{
	Crypt	*tmp;

	if ((tmp = (Crypt *) list_lookup((List **)&crypt_list, nick, !USE_WILDCARDS,
			REMOVE_FROM_LIST)) != NULL)
	{
		new_free((char **)&(tmp->nick));
		new_free((char **)&(tmp->key));
		new_free((char **)&tmp);
		return (0);
	}
	return (1);
}

/*
 * is_crypted: looks up nick in the crypt_list and returns the encryption key
 * if found in the list.  If not found in the crypt_list, null is returned. 
 */
#ifdef __STDC__
extern char	*is_crypted (char *nick)
#else
extern char	*is_crypted(nick)
	char	*nick;
#endif
{
	Crypt	*tmp;

	if (!crypt_list)
		return NULL;
	if ((tmp = (Crypt *) list_lookup((List **)&crypt_list, nick,
			!USE_WILDCARDS, !REMOVE_FROM_LIST)) != NULL)
		return (tmp->key);
	return NULL;
}

/*
 * encrypt_cmd: the ENCRYPT command.  Adds the given nickname and key to the
 * encrypt list, or removes it, or list the list, you know. 
 */
#ifdef __STDC__
extern void	encrypt_cmd (char *command, char *args, char *subargs)
#else
extern void 	encrypt_cmd(command, args, subargs)
	char	*command,
		*args;
	char 	*subargs;
#endif
{
	char	*nick,
	*key;

	if ((nick = next_arg(args, &args)) != NULL)
	{
		if ((key = next_arg(args, &args)) != NULL)
		{
			add_to_crypt(nick, key);
			say("%s added to the crypt with key %s", nick, key);
		}
		else
		{
			if (remove_crypt(nick))
				say("No such nickname in the crypt: %s", nick);
			else
				say("%s removed from the crypt", nick);
		}
	}
	else
	{
		if (crypt_list)
		{
			Crypt	*tmp;

			say("The crypt:");
			for (tmp = crypt_list; tmp; tmp = tmp->next)
				put_it("%s with key %s", tmp->nick, tmp->key);
		}
		else
			say("The crypt is empty");
	}
}

#ifdef __STDC__
extern 	void 	my_encrypt (char *str, int len, char *key)
#else
extern	void 	my_encrypt(str, len, key)
	char	*str,
		*key;
	int	len;
#endif
{
	int	key_len,
		key_pos,
		i;
	char	mix,
		tmp;

	if (!key)
		return;			/* no encryption */

	key_len = strlen(key);
	key_pos = 0;
	/*    mix = key[key_len-1]; */
	mix = 0;
	for (i = 0; i < len; i++)
	{
		tmp = str[i];
		str[i] = mix ^ tmp ^ key[key_pos];
		mix ^= tmp;
		key_pos = (key_pos + 1) % key_len;
	}
	str[i] = (char) 0;
}

#ifdef __STDC__
extern	void 	my_decrypt (char *str, int len, char *key)
#else
extern	void	my_decrypt(str, len, key)
	char	*str,
		*key;
	int	len;
#endif
{
	int	key_len,
		key_pos,
		i;
	char	mix,
		tmp;

	if (!key)
		return;			/* no decryption */

	key_len = strlen(key);
	key_pos = 0;
	/*    mix = key[key_len-1]; */
	mix = 0;
	for (i = 0; i < len; i++)
	{
		tmp = mix ^ str[i] ^ key[key_pos];
		str[i] = tmp;
		mix ^= tmp;
		key_pos = (key_pos + 1) % key_len;
	}
	str[i] = (char) 0;
}

/*
 * This wrapper function decides whether to run an external encryption
 * program or whether to us the internal encryption scheme.  The usefulness
 * of an external encryption program is highly suspect given the need to
 * fork(), write(), and read() for each privmsg, which makes it extremely
 * expensive, and supporting it is not ideal.  Look for it to go away
 * in future revisions.
 */
#ifdef __STDC__
static 	char *	do_crypt (char *str, char *key, int flag)
#else
static	char *	do_crypt(str, key, flag)
	char	*str,
		*key;
	int	flag;
#endif
{
	int	in[2],
		out[2],
		c;
	char	buffer[CRYPT_BUFFER_SIZE + 1];
	char	*ptr = (char *) 0,
		*encrypt_program;

	if ((encrypt_program = get_string_var(ENCRYPT_PROGRAM_VAR)))
	{
		in[0] = in[1] = -1;
		out[0] = out[1] = -1;

		if (access(encrypt_program, X_OK))
		{
			say("Unable to execute encryption program: %s", encrypt_program);
			return ((char *) 0);
		}

		if (!flag)
		{
			c = strlen(str);
			ptr = ctcp_unquote_it(str, &c);
		}
		else
			malloc_strcpy(&ptr, str);

		if (pipe(in) || pipe(out))
		{
			say("Unable to start encryption process: %s", sys_errlist[errno]);
			if (in[0] != -1)
			{
				close(in[0]);
				close(in[1]);
			}
			if (out[0] != -1)
			{
				close(out[0]);
				close(out[1]);
			}
		}
		switch (fork())
		{
			case -1:
				say("Unable to start encryption process: %s", sys_errlist[errno]);
				return ((char *) 0);
			case 0:
				MY_SIGNAL(SIGINT, SIG_IGN, 0);
				dup2(out[1], 1);
				dup2(in[0], 0);
				close(out[0]);
				close(in[1]);
				setuid(getuid());
				setgid(getgid());
				execl(encrypt_program, encrypt_program, key, NULL);
				exit(0);
			default:
				close(out[1]);
				close(in[0]);
				write(in[1], ptr, strlen(ptr));
				close(in[1]);
				c = read(out[0], buffer, CRYPT_BUFFER_SIZE);
				buffer[c] = (char) 0;
				close(out[0]);
				break;
		}
		new_free(&ptr);
		if (flag)
			ptr = ctcp_quote_it(buffer, strlen(buffer));
		else
			malloc_strcpy(&ptr, buffer);
	}
	else
	{
		c = strlen(str);
		if (flag)
		{
			my_encrypt(str, c, key);
			ptr = ctcp_quote_it(str, c);
		}
		else
		{
			ptr = ctcp_unquote_it(str, &c);
			my_decrypt(ptr, c, key);
		}
	}
	return (ptr);
}

/*
 * crypt_msg: Given plaintext 'str', constructs a body suitable for sending
 * via PRIVMSG or DCC CHAT.  The ^E extended syntax is still supported, 
 * though ive never really understood what it was useful for.  I suppose
 * at one time, it was required to isolate CTCP requests, but now that
 * CTCPs can be encrypted, this makes its usefulness suspect (and its
 * implementation cost high)
 */
#ifdef __STDC__
extern 	char 	*crypt_msg (char *str, char *key)
#else
extern 	char 	*crypt_msg(str, key)
	char	*str,
		*key;
#endif
{
	char	buffer[CRYPT_BUFFER_SIZE + 1];
	char	thing[6];
	char	*ptr,
		*rest;
	int	on = 1;

	sprintf(thing, "%cSED ", CTCP_DELIM_CHAR);
	*buffer = (char) 0;
	while ((rest = index(str, '\005')) != NULL)
	{
		*rest++ = (char) 0;
		if (on && *str && (ptr = do_crypt(str, key, 1)))
		{
			strmcat(buffer, thing, CRYPT_BUFFER_SIZE);
			strmcat(buffer, ptr, CRYPT_BUFFER_SIZE);
			strmcat(buffer, CTCP_DELIM_STR, CRYPT_BUFFER_SIZE);
			new_free(&ptr);
		}
		else
			strmcat(buffer, str, CRYPT_BUFFER_SIZE);
		on = !on;
		str = rest;
	}
	if (on && (ptr = do_crypt(str, key, 1)))
	{
		strmcat(buffer, thing, CRYPT_BUFFER_SIZE);
		strmcat(buffer, ptr, CRYPT_BUFFER_SIZE);
		strmcat(buffer, CTCP_DELIM_STR, CRYPT_BUFFER_SIZE);
		new_free(&ptr);
	}
	else
		strmcat(buffer, str, CRYPT_BUFFER_SIZE);

	return m_strdup(buffer);
}

/*
 * Given a CTCP SED argument 'str', it attempts to unscramble the text
 * into something more sane.  If the 'key' is not the one used to scramble
 * the text, the results are unpredictable.  This is probably the point.
 *
 * Note that the retval MUST be at least 'BIG_BUFFER_SIZE + 1'.  This is
 * not an oversight -- the retval is passed is to do_ctcp() which requires
 * a big buffer to scratch around (The decrypted text could be a CTCP UTC
 * which could expand to a larger string of text.)
 */ 
#ifdef __STDC__
extern 	char 	*decrypt_msg (char *str, char *key)
#else
extern 	char 	*decrypt_msg(str, key)
	char	*str,
		*key;
#endif
{
	char	*buffer = (char *)new_malloc(BIG_BUFFER_SIZE + 1);
	char	*ptr;

	if ((ptr = do_crypt(str, key, 0)) != NULL)
	{
		strmcpy(buffer, ptr, CRYPT_BUFFER_SIZE);
		new_free(&ptr);
	}
	else
		strmcat(buffer, str, CRYPT_BUFFER_SIZE);

	return buffer;
}
