/*
 * crypt.c: handles some encryption of messages stuff. 
 *
 * Written By Michael Sandrof
 *
 * Copyright (c) 1990 Michael Sandrof.
 * Copyright (c) 1991, 1992 Troy Rollo.
 * Copyright (c) 1992-2003 Matthew R. Green.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "irc.h"
IRCII_RCSID("@(#)$Id: crypt.c,v 1.14 2006-10-31 12:31:27 f Exp $");

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

#ifdef HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif /* HAVE_SYS_WAIT_H */

#ifndef LITE

/**************************** Patched by Flier ******************************/
#define my_strlen(x)    strlen((char *) x)
#define my_strcpy(x, y) strcpy((char *) x, (char *) y)
/****************************************************************************/

static	void	add_to_crypt _((u_char *, u_char *, CryptFunc, CryptFunc, u_char *));
static	int	remove_crypt _((u_char *));
static	u_char	*do_crypt _((u_char *, crypt_key *, int, u_char **));

#ifdef HAVE_DEV_RANDOM
static	int	crypt_dev_random_byte _((void));
#define GET_RANDOM_BYTE	crypt_dev_random_byte()
#else
/* gotta use the sucky one */
#define GET_RANDOM_BYTE	(random() & 255)
#endif

#include "cast.c"
#if 0
#include "rijndael.c"
#endif
#include "sed.c"

#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;
	u_char	*nick;
	crypt_key	*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. 
 */
static	void
add_to_crypt(nick, keystr, enc, dec, type)
	u_char	*nick;
	u_char	*keystr;
	CryptFunc enc;
	CryptFunc dec;
	u_char	*type;
{
	Crypt	*new;

	if ((new = (Crypt *) remove_from_list((List **) &crypt_list, nick)) != NULL)
	{
		new_free(&(new->nick));
		bzero(new->key->key, strlen((char *) new->key->key));		/* wipe it out */
		new_free(&(new->key->key));
		new_free(&(new->key->cookie));
		new_free(&(new->key));
		new_free(&new);
	}
	new = (Crypt *) new_malloc(sizeof(Crypt));
	new->key = (crypt_key *) new_malloc(sizeof(*new->key));
	new->nick = (u_char *) 0;
	new->key->key = (u_char *) 0;
	new->key->type = type;
	malloc_strcpy((char **) &(new->nick), nick);
	malloc_strcpy((char **) &(new->key->key), keystr);
	new->key->crypt = enc;
	new->key->decrypt = dec;
	new->key->cookie = NULL;
	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) 
 */
static	int
remove_crypt(nick)
	u_char	*nick;
{
	Crypt	*tmp;

	if ((tmp = (Crypt *) list_lookup((List **) &crypt_list, nick, !USE_WILDCARDS, REMOVE_FROM_LIST)) != NULL)
	{
		new_free(&tmp->nick);
		bzero(tmp->key->key, strlen((char *) tmp->key->key));		/* wipe it out */
		new_free(&tmp->key->key);
		new_free(&tmp->key->cookie);
		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. 
 */
crypt_key *
is_crypted(nick)
	u_char	*nick;
{
	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. 
 */
/*ARGSUSED*/
void
encrypt_cmd(command, args, subargs)
	u_char	*command,
		*args,
		*subargs;
{
	/* XXX this is getting really, really gross */
	CryptFunc enc = DEFAULT_CRYPTER, dec = DEFAULT_DECRYPTER;
	u_char	*type = DEFAULT_CRYPTYPE;
	u_char	*nick;
	int	showkeys = 0;

restart:
	if ((nick = next_arg((char *) args, (char **) &args)) != NULL)
	{
 		size_t len = strlen((char *) nick);

 		if (my_strnicmp((char *) nick, "-showkeys", len) == 0)
 		{
 			showkeys = 1;
 			goto restart;
 		}
 		else if (my_strnicmp((char *) nick, "-cast", len) == 0)
		{
			enc = cast_encrypt_str;
			dec = cast_decrypt_str;
			type = CAST_STRING;
			goto restart;
		}
#if 0
		else if (my_strnicmp(nick, UP("-rijndael"), len) == 0)
		{
			enc = rijndael_encrypt_str;
			dec = rijndael_decrypt_str;
			type = RIJNDAEL_STRING;
			goto restart;
		}
#endif
		else if (my_strnicmp((char *) nick, "-sed", len) == 0)
		{
			enc = sed_encrypt_str;
			dec = sed_decrypt_str;
			type = SED_STRING;
			goto restart;
		}

		if (args && *args)
		{
			add_to_crypt(nick, args, enc, dec, type);
			say("%s added to the %s crypt", nick, type);
		}
		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)
				if (showkeys)
					put_it("%s with key \"%s\" type %s", tmp->nick, tmp->key->key, tmp->key->type);
				else
					put_it("%s type %s", tmp->nick, tmp->key->type);
		}
		else
			say("The crypt is empty");
	}
}

static	u_char	*
do_crypt(str, key, flag, type)
	u_char	*str;
	crypt_key *key;
	int	flag;
	u_char	**type;
{
	int	in[2],
		out[2];
	size_t	c;
	u_char	lbuf[CRYPT_BUFFER_SIZE + 1];
	u_char	*ptr = (char *) 0,
		*crypt_program,
		*encrypt_program,
		*decrypt_program,
		*crypt_str;

	encrypt_program = get_string_var(ENCRYPT_PROGRAM_VAR);
	decrypt_program = get_string_var(DECRYPT_PROGRAM_VAR);
	if ((flag && encrypt_program) || (!flag && decrypt_program))
	{
#ifdef DAEMON_UID
		if (DAEMON_UID == getuid())
		{
			say("ENCRYPT_PROGRAM not available from daemon mode");
			return (u_char *) 0;
		}
#endif /* DAEMON_ID */
		in[0] = in[1] = -1;
		out[0] = out[1] = -1;
		if (flag)
		{
			crypt_str = CP("encryption");
			crypt_program = encrypt_program;
		}
		else
		{
			crypt_str = CP("decryption");
			crypt_program = decrypt_program;
		}
		if (access(CP(crypt_program), X_OK))
		{
			say("Unable to execute %s program: %s", crypt_str, crypt_program);
			return ((u_char *) 0);
		}
		c = strlen((char *) str);
		if (!flag)
			ptr = ctcp_unquote_it(str, &c);
		else 
			malloc_strcpy((char **) &ptr, str);
		if (pipe(in) || pipe(out))
		{
			say("Unable to start %s process: %s", crypt_str, strerror(errno));
			if (in[0] != -1)
			{
				new_close(in[0]);
				new_close(in[1]);
			}
			if (out[0] != -1)
			{
				new_close(out[0]);
				new_close(out[1]);
			}
		}
		switch (fork())
		{
		case -1:
			say("Unable to start %s process: %s", crypt_str, strerror(errno));
			return ((u_char *) 0);
		case 0:
			MY_SIGNAL(SIGINT, (sigfunc *) SIG_IGN, 0);
			dup2(out[1], 1);
			dup2(in[0], 0);
			new_close(out[0]);
			new_close(out[1]);
			new_close(in[0]);
			new_close(in[1]);
			setgid(getgid());
			setuid(getuid());
			if (get_int_var(OLD_ENCRYPT_PROGRAM_VAR))
				execl(CP(crypt_program), CP(crypt_program), key->key, NULL);
			else
				execl(CP(crypt_program), CP(crypt_program), NULL);
			exit(0);
		default:
			new_close(out[1]);
			new_close(in[0]);
			if (get_int_var(OLD_ENCRYPT_PROGRAM_VAR) == 0)
			{
				write(in[1], key->key, strlen((char *) key->key));
				write(in[1], "\n", 1);
			}
			write(in[1], ptr, c);
			new_close(in[1]);
			c = read(out[0], lbuf, CRYPT_BUFFER_SIZE);
			wait(NULL);
			lbuf[c] = (u_char) 0;
			new_close(out[0]);
			break;
		}
		new_free(&ptr);
		if (flag)
			ptr = ctcp_quote_it(lbuf, strlen((char *) lbuf));
		else
			malloc_strcpy((char **) &ptr, lbuf);
	}
	else
	{
		c = strlen((char *) str);
		if (flag)
		{
			if (key->crypt(key, &str, (int *)&c) != 0)
			{
				yell("--- do_crypt(): crypto encrypt failed");
				return 0;
			}
			ptr = ctcp_quote_it(str, c);
		}
		else
		{
			ptr = ctcp_unquote_it(str, &c);
			if (key->decrypt(key, &ptr, (int *)&c) != 0)
			{
				yell("--- do_crypt(): crypto decrypt failed");
				return 0;
			}
		}
	}
	if (type)
		*type = key->type;
	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.
 */
u_char	*
crypt_msg(str, key, flag)
	u_char	*str;
	crypt_key *key;
	int	flag;
{
	static	u_char	lbuf[CRYPT_BUFFER_SIZE + 1];
	u_char	*ptr,
		*rest,
		*type;
	int	on = 1;

	if (flag)
	{
		*lbuf = (u_char) 0;
		while ((rest = index((char *) str, '\005')) != NULL)
		{
			*(rest++) = (u_char) 0;
			if (on && *str && (ptr = do_crypt(str, key, flag, &type)))
			{
				snprintf(CP(lbuf), sizeof lbuf, "%c%.30s ", CTCP_DELIM_CHAR, type);
				strmcat(lbuf, ptr, CRYPT_BUFFER_SIZE);
				strmcat(lbuf, CTCP_DELIM_STR, CRYPT_BUFFER_SIZE);
				new_free(&ptr);
			}
			else
				strmcat(lbuf, str, CRYPT_BUFFER_SIZE);
			on = !on;
			str = rest;
		}
		if (on && (ptr = do_crypt(str, key, flag, &type)))
		{
			snprintf(CP(lbuf), sizeof lbuf, "%c%.30s ", CTCP_DELIM_CHAR, type);
			strmcat(lbuf, ptr, CRYPT_BUFFER_SIZE);
			strmcat(lbuf, CTCP_DELIM_STR, CRYPT_BUFFER_SIZE);
			new_free(&ptr);
		}
		else
			strmcat(lbuf, str, CRYPT_BUFFER_SIZE);
	}
	else
	{
		if ((ptr = do_crypt(str, key, flag, &type)) != NULL)
		{
			strmcpy(lbuf, ptr, CRYPT_BUFFER_SIZE);
			new_free(&ptr);
		}
		else
			strmcat(lbuf, str, CRYPT_BUFFER_SIZE);
	}
	return (lbuf);
}

#ifdef HAVE_DEV_RANDOM
static RETSIGTYPE alarmer _((void));

static RETSIGTYPE
alarmer()
{

}

static int
crypt_dev_random_byte()
{
	static	int	devrndfd = -1;
	u_char	c;

	if (devrndfd == -1)
	{
		devrndfd = open(DEV_RANDOM_PATH, O_RDONLY);

		if (devrndfd == -1)
		{
			yell("--- HELP!!!!rypt_dev_random_byte: can not open %s: %s",
			    DEV_RANDOM_PATH, strerror(errno));
			yell("--- using random()");
			devrndfd = -2;
		}
	}
	if (devrndfd == -2)
		goto do_random_instead;

	alarm(2);
	(void)MY_SIGNAL(SIGALRM, (sigfunc *) alarmer, 0);
	if (read(devrndfd, &c, 1) != 1)
	{
		alarm(0);
		(void)MY_SIGNAL(SIGALRM, (sigfunc *) SIG_DFL, 0);
		/* if we were just interrupted, don't bail on /dev/random */
		if (errno == EINTR)
		{
			yell("--- crypt_dev_random_byte: timeout, using random()");
			goto do_random_instead;
		}
		yell("--- HELP!  crypt_dev_random_byte: read of one byte on %s failed: %s",
		    DEV_RANDOM_PATH, strerror(errno));
		yell("--- using random()");
		devrndfd = -2;
		goto do_random_instead;
	}
	alarm(0);
	(void)MY_SIGNAL(SIGALRM, (sigfunc *) SIG_DFL, 0);
	return ((int)c);

do_random_instead:
	return (random() & 255);
}
#endif

#endif /* LITE */
