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

#include <stdio.h>
#include <sys/types.h>
#include <sys/file.h>
#include <signal.h>
#if defined(ISC22) || defined(SVR4) || defined(_IBMR2) || defined(POSIX)
# include <unistd.h>
#endif
#include "irc.h"
#include "crypt.h"
#include "vars.h"
#include "ircaux.h"
#include "list.h"
#include "ctcp.h"
#include "output.h"

#define CRYPT_BUFFER_SIZE (IRCD_BUFFER_SIZE - 50)	/* Make this less than
							 * the trasmittable
							 * 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 = null(Crypt *);

/*
 * 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. 
 */
static	void add_to_crypt(nick, key)
char	*nick;
char	*key;
{
	Crypt	*new;

	if (new = (Crypt *) remove_from_list(&crypt_list, nick))
	{
		new_free(&(new->nick));
		new_free(&(new->key));
		new_free(&new);
	}
	new = (Crypt *) new_malloc(sizeof(Crypt));
	new->nick = null(char *);
	new->key = null(char *);
	malloc_strcpy(&(new->nick), nick);
	malloc_strcpy(&(new->key), key);
	/* upper(new->nick); */
	add_to_list(&crypt_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) 
 */
static	int	remove_crypt(nick)
char	*nick;
{
	Crypt	*tmp;

	if (tmp = (Crypt *) list_lookup(&crypt_list, nick, !USE_WILDCARDS,
			REMOVE_FROM_LIST))
	{
		new_free(&(tmp->nick));
		new_free(&(tmp->key));
		new_free(&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. 
 */
char	*is_crypted(nick)
char	*nick;
{
	Crypt	*tmp;

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

/*
 * encrypt_cmd: the ENCRYPT command.  Adds the given nickname and key to the
 * encrypt list, or removes it, or list the list, you know. 
 */
/*ARGSUSED*/
void encrypt_cmd(command, args)
char	*command,
*args;
{
	char	*nick,
	*key;

	if (nick = next_arg(args, &args))
	{
		if (key = next_arg(args, &args))
		{
			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");
	}
}

static	void encrypt(str, len, key)
char	*str,
*key;
int	len;
{
	int	key_len,
	key_pos,
	i;
	char	mix,
	tmp;

	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] = null(char);
}

static	void decrypt(str, len, key)
char	*str,
*key;
int	len;
{
	int	key_len,
	key_pos,
	i;
	char	mix,
	tmp;

	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] = null(char);
}

static	char	*do_crypt(str, key, flag)
char	*str,
*key;
int	flag;
{
	int	in[2],
	out[2],
	c;
	char	buffer[CRYPT_BUFFER_SIZE + 1];
	char	*ptr = null(char *),
	*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 (null(char *));
		}
		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 (null(char *));
		case 0:
			signal(SIGINT, SIG_IGN);
			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] = null(char);
			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)
		{
			encrypt(str, c, key);
			ptr = ctcp_quote_it(str, c);
		}
		else
		{
			ptr = ctcp_unquote_it(str, &c);
			decrypt(ptr, c, key);
		}
	}
	return (ptr);
}

/*
 * crypt_msg: Executes the encryption program on the given string with the
 * given key.  If flag is true, the string is encrypted and the returned
 * string is ready to be sent over irc.  If flag is false, the string is
 * decrypted and the returned string should be readable 
 */
char	*crypt_msg(str, key, flag)
char	*str,
	*key;
int	flag;
{
	static	char	buffer[CRYPT_BUFFER_SIZE + 1];
	char	thing[6];
	char	*ptr,
		*rest;
	int	on = 1;

	if (flag)
	{
		sprintf(thing, "%cSED ", CTCP_DELIM_CHAR);
		*buffer = null(char);
		while (rest = index(str, '\005'))
		{
			*(rest++) = null(char);
			if (on && *str && (ptr = do_crypt(str, key, flag)))
			{
				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, flag)))
		{
			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);
	}
	else
	{
		if (ptr = do_crypt(str, key, flag))
		{
			strmcpy(buffer, ptr, CRYPT_BUFFER_SIZE);
			new_free(&ptr);
		}
		else
			strmcat(buffer, str, CRYPT_BUFFER_SIZE);
	}
	return (buffer);
}

