char *Version = 
"tircproxy v0.4.5, Copyleft 2000, Bjarni R. Einarsson <bre@netverjar.is>" ;

/*****************************************************************************\
 * 
 * This is an IRC proxy server. It can be started from inetd to service
 * IRC requests, or run as a stand-alone daemon.  It can operate with
 * or without the Linux kernel's transparent proxy feature (or the IPF 
 * filters for *BSD systems).   DCC requests are transparently accepted, 
 * rewritten and proxied for the clients.
 *
 * Thanks go to the author of transproxy.c, this program is based on his
 * code - and without it I would have written a much messier program or
 * none at all.  This code is distributed under the GPL.
 *
 * See the files README, CHANGELOG.txt and BUGS.txt for more info!
 * Don't forget to edit tircproxy.h to suit your tastes!
 * 
 * ************************************************************************* *
 * 
 * Note to hackers, porters, etc.
 * 
 *   Please, please let me know if you find bugs in my code!
 * 
\*****************************************************************************/

/* The following is an example of a useful broadcast file.. the format for
 * /etc/motd.irc is the same (raw IRC server output, that is).
 * 
 * Keep things short, people have flood protection out there.. and others
 * are "suppressing" the MOTD!

:admin@isp.net 999 * :This is a fake server message!
:bofh!admin@isp.net PRIVMSG #notice :The proxy server is about to be shut down!
:bofh!admin@isp.net PRIVMSG #notice :Be very afraid!!!

 */

#include <config.h>

#include <unistd.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/types.h>
#include <syslog.h>
#include <signal.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>

#include <tircproxy.h>

/*  "Inheritance" of config options.
*/
#ifdef PARANOID
# ifndef MIRC_DCC_KLUDGE
#  define MIRC_DCC_KLUDGE 1
# endif
# ifndef NICK_LOG
#  define NICK_LOG
# endif
#endif

#ifdef MIRC_DCC_KLUDGE
# warning MIRC DCC kludge active
#endif
#ifdef NICK_LOG
# warning Nickname logging active
#endif

#ifdef CDIR
# ifndef IP_TO_UID_PREFIX
#  define IP_TO_UID_PREFIX	CDIR CDIR_MAP "-"
# endif
#endif

/* Autoconf stuff
*/

#ifdef HAVE_CRYPT_H
# include <crypt.h>
#endif

#if (HAVE_UDB_H && USE_UDB)
# warning Using UDB style IPC for identd etc.
# include <udb.h>
# undef CDIR
# define CDIR 1
# undef CDIR_IDENT
# define CDIR_IDENT 1
# undef IP_TO_UID_PREFIX
#else
# undef USE_UDB
#endif

#ifndef INADDR_NONE
# define INADDR_NONE -1
#endif

#if !HAVE_LIBWRAP
# undef TCP_WRAPPERS
# warning Configuration via /etc/hosts.* not available
#else
# warning Configuration via /etc/hosts.* available
#endif

#ifndef IPF
# define IPF 0
#endif
#undef TRANS

#if IPF
# if HAVE_NETINET_IP_NAT_H
#  include <sys/ioctl.h>
#  include <net/if.h>
#  include <netinet/in_systm.h>
#  include <netinet/tcp.h>
#  if HAVE_NETINET_IP_FIL_COMPAT_H
#	include <netinet/ip_fil_compat.h>
#  else	
#	include <netinet/ip_compat.h>
#  endif
#  include <netinet/ip_fil.h>
#  include <netinet/ip_nat.h>
#  include <netinet/ip_state.h>
#  include <netinet/ip_proxy.h>
#  include <netinet/ip_nat.h>
#  include <netinet/ipl.h>
#  include <osreldate.h>
#  define TRANS 1
#  ifndef IPL_NAT
#  define IPL_NAT IPNAT_NAME
#  endif
#  warning IPF transparent proxying available
# else
#  undef IPF
#  define IPF 0
#  warning IPF support not available
# endif
#endif

#ifndef LINUX
# define LINUX 0
#endif

#if LINUX
# ifndef HAVE_LINUX
#  undef LINUX
#  define LINUX 0
#  warning Linux support not available
# else
#  warning Linux transparent proxying available (depends on kernel)
#  define TRANS 1
# endif
#endif

#if QUIZ_MODE
# warning Quiz mode available
#else
# warning Quiz mode not available
#endif

/* Some useful definitions, which we might get from the system.
*/
#ifndef MAXLOGNAME
# define MAXLOGNAME 64
#endif

#ifndef PATH_MAX
# define PATH_MAX 256
#endif

/* Do we want to use the TCP wrappers for access control?
*/
#ifdef TCP_WRAPPERS
#include "tcpd.h"

int allow_severity	= 0;
int deny_severity	= 0;
int hosts_ctl(char *daemon, 
	      char *client_name, 
	      char *client_addr, 
	      char *client_user);

#endif


/* Typedefs.
*/
typedef unsigned long	ipaddr_t;


/* Macros & definitions.
*/
#define FD_MAX(a,b)		((a) > (b) ? (a) : (b))
#define MINUTE			60

#define DEBUG_FEATURES		1
#define DEBUG_BASIC		2
#define DEBUG_TRIVIA		8
#define DEBUG_NOFORK		9


/* Function prototypes.
*/
static void usage		(char *prog, char *opt);
static short get_port_num	(char *portnum);
static ipaddr_t get_ip_addr	(char *ipaddr);
static uid_t get_user_id	(char *user);
static uid_t get_group_id	(uid_t uid);
static int bind_to_port		(ipaddr_t bind_ip, short bind_port, 
				 int backlog, int dlev);
static int connect_to_server	(struct sockaddr_in addr);
static void lookup_hostname	(struct sockaddr_in *addr, char *hostname,
				 int hostlen, int needed);
static void server_main_loop	(int sock);
static void trans_proxy		(int sock, struct sockaddr_in *addr);
static int  copy_loop		(int sock, struct sockaddr_in *from_addr, 
		      		 struct sockaddr_in *to_addr,
		      		 int is_server_connection);
static int  copy_once		(int rsock, char *rbuffer, int rbufsize, 
				 char *rname,
		      		 int wsock, char *wlbuffer, int *wlbufpos, 
				 char *wname,
		     		 int is_server_connection, int r_is_client,
		     		 int *read_len);
static int filtered_write	(int to_sock, char *buff, int blen, 
			       	 char *leftover, int *llen,
			   	 int isclient);
static int scan_line		(char *line, int isclient);
static int dcc_mangle_filename	(char *filename);
static int dcc_resume		(int destport, int resume); 
static char *proxy_dcc		(int destaddr, int destport, int incoming);
static void get_user_name	(struct sockaddr_in *addr);
static void change_uid		(void);
#ifdef CDIR
static void tell_identd		(struct sockaddr_in *, struct sockaddr_in *);
static void cleanup_identd	(void);
#endif
static void broadcast		(int sock, const char *filename);
static void alarm_signal	(int sig);
static void hup_signal		(int sig);
static void chld_signal		(int sig);
static void debug_msg		(int lev, int pri, const char *format, ...);
#ifdef QUIZ_MODE
static void quiz_delay_line	(char *line, char **first, char ***bl);
static void quiz_dump_lines	(int sock, char **first, char ***bl);
static void quiz_check_auth	(char *pass);
static void quiz_msg		(char *message);
static void quiz_greet		(void);
#endif

/* Taken from the RFC 1459 ..
*/
#define LENGTH_NICKNAME		9
#define LENGTH_SERVERDATA	512

/* These make useful global variables..
*/
char		user_nick[LENGTH_NICKNAME * 2];
char            user_name[MAXLOGNAME];
char		user_ident_file[PATH_MAX];
char		server_tag[64], alarm_in[64];
ipaddr_t	clients_ip = INADDR_NONE;
char		clients_ip_s[64];
int		motd_is_done = 0;

/* We need to remember some stuff to support DCC RESUME.
** See: http://www.mirc.co.uk/help/dccresum.txt 
*/
#define MAX_DCC_SESSIONS 10
time_t	dcc_session_time  [MAX_DCC_SESSIONS];
int	dcc_original_port [MAX_DCC_SESSIONS];
int	dcc_proxied_port  [MAX_DCC_SESSIONS];

/* Quid mode - require a password or an answer to some silly
** question before sending the users' output to the server.
*/
#ifdef QUIZ_MODE
# define QUIZ_OFF	0
# define QUIZ_ON	1
# define QUIZ_READY	2
# define QUIZ_FLUSH	3
# define QUIZ_S		&from_cli_first, &from_cli_last
# define QUIZ_C		&to_cli_first, &to_cli_last
char			*from_cli_first = NULL;
char			**from_cli_last = NULL;
char			*to_cli_first = NULL;
char			**to_cli_last = NULL;
char			quizfile[PATH_MAX], quiz[512];
int			use_unix_passwd = QUIZ_OFF;
int			use_quiz_mode = QUIZ_OFF;
#endif

/*  Configuration variables..
*/
ipaddr_t	bind_ip = INADDR_ANY;
ipaddr_t	server_ip = INADDR_NONE;
ipaddr_t	visible_ip_i = INADDR_ANY;
char		visible_ip_i_s[64];
ipaddr_t	visible_ip_o = INADDR_ANY;
char		visible_ip_o_s[64];
int		throttle_seconds = -1;
int		allow_dcc_send = 1;
int		allow_dcc_chat = 1;
int		allow_dcc_unknown = 1;
short		server_port = -1;
int		broadcast_flag = 0;
int		not_an_irc_proxy = 0;
int		use_dcc_mangling = 1;
int		use_syslog = 1;
int		debug_level = 0;
int		use_anonymity = 0;
int		anon_notval = 0;
#ifdef PARANOID
int		use_paranoid = 1;
#endif
#ifdef MIRC_DCC_KLUDGE
int		use_mirc_dcc_kludge = 1;
#endif
#ifdef NICK_LOG
int		use_nick_log = 1;
#endif
#ifdef TCP_WRAPPERS
int		use_tcp_wrappers = 1;
#endif
#ifdef CDIR
int	   	use_cdir = 1;
#endif
#ifdef USE_UDB
struct udb_connection conn;
#endif


/* Main!
*/
int main(int argc, char **argv)
{
	int					arg;
	int					run_as_server = 0;
	short					bind_port = -1;
	uid_t					run_uid = 0;
	gid_t					run_gid = 0;
	int					sock;
	struct sockaddr_in		    	addr;
	int					len;

	/* Parse the command line arguments.
	*/
	while ((arg = getopt(argc, argv, "ab:d:h?i:o:pq:r:s:t:CDHIKLMNOQRSU")) != EOF)
	{
	   	switch (arg)
		{

		case 'a':
                    	use_anonymity = 1;
			break;
		case 'b':
                    	bind_ip = get_ip_addr(optarg);
			break;
		case 'd':
#ifdef TIRC_DEBUG
		   	debug_level = atoi(optarg);
#else
		   	usage(argv[0],"Feature DEBUG not available.");
#endif
			break;
		case 'h':
		case '?':
			usage(argv[0], NULL);
			break;
		case 'i':
			visible_ip_i = get_ip_addr(optarg);
			break;
		case 'o':
			visible_ip_o = get_ip_addr(optarg);
			break;
		case 'p':
#ifdef QUIZ_MODE
		   	use_quiz_mode = use_unix_passwd = QUIZ_ON;
		   	if (getuid()) fprintf(stderr,
				"Warning: Not running as root, may not be able to check passwords!\n");
#else
		   	usage(argv[0],"Feature QUIZ_MODE not active.");
#endif
		   	break;
		case 'q':
#ifdef QUIZ_MODE
		   	use_quiz_mode = QUIZ_ON;
		   	strncpy(quizfile, optarg, PATH_MAX);
			quizfile[PATH_MAX-1] = '\0';
#else
		   	usage(argv[0],"Feature QUIZ_MODE not active.");
#endif
		   	break;
		case 'r':
			run_uid = get_user_id(optarg);
			run_gid = get_group_id(run_uid);
			break;
		case 's':
			run_as_server = 1;
			bind_port = get_port_num(optarg);
			break;

		case 't':
			throttle_seconds = atoi(optarg);
			break;
		
		case 'C':
			allow_dcc_chat = 0;
			break;
		
		case 'D':
#ifdef NICK_LOG		   
		   	use_nick_log = 0;
#else
		   	usage(argv[0],"Feature NICK_LOG not active.");
#endif
		   	break;
		case 'H':
#ifdef TCP_WRAPPERS
			use_tcp_wrappers = 0;
#else
		   	usage(argv[0],"Feature TCP_WRAPPERS not active.");
#endif
		   	break;
		case 'I':
		case 'O': /* backwards compatibility ... */
#ifdef CDIR
		   	use_cdir = 0;
#else
		   	usage(argv[0],"Neither CDIR nor USE_UDB compiled in, sorry.");
#endif
		   	break;
		case 'K':
#ifdef MIRC_DCC_KLUDGE
			use_mirc_dcc_kludge = 0;
#else
		   	usage(argv[0],"Feature MIRC_DCC_KLUDGE not active.");
#endif
		   	break;
		case 'L':
			use_syslog = 0;
			break;
		case 'M':
			use_dcc_mangling = 0;
			break;
		
		case 'N':
		   	not_an_irc_proxy = 1;
		   	break;
		case 'R':
#ifdef PARANOID
		   	use_paranoid = 0;
#else
		   	usage(argv[0],"Feature PARANOID not active.");
#endif
		   	break;
		case 'S':
		   	allow_dcc_send = 0;
		   	break;
		case 'U':
			allow_dcc_unknown = 0;
			break;
		}
	}
   
   	/* Set a few variables to 'default' values.
	*/
   	if ((bind_ip != INADDR_ANY) && (visible_ip_i == INADDR_ANY))
     		visible_ip_i = bind_ip;

	*user_ident_file = *user_name = *user_nick = '\0';
 
	/* Process the remaining command line arguments.
	*/
	for (; optind < argc; ++optind)
	{
		if (server_ip == INADDR_NONE)
		{
			server_ip = get_ip_addr(argv[optind]);
		}
		else if (server_port == -1)
		{
			server_port = get_port_num(argv[optind]);
		}
		else
		{
			usage(argv[0], "Extra arguments were specified.");
		}
	}

   	if (server_ip == INADDR_NONE)
	{
#ifndef TRANS
	   	usage(argv[0], "No remote server specified!");
#else
	   	if ((visible_ip_i == INADDR_ANY) &&
		    (allow_dcc_send | allow_dcc_chat | allow_dcc_unknown) &&
		    (!not_an_irc_proxy))
		{
		   	usage(argv[0], "No internal address specified, DCC would break!");
		}

	   	fprintf(stderr,
				"No remote server specified, transparent operation assumed.\n");
#endif
	}
   	else
	{
		if (server_port == -1)
		{
		   	server_port = 6667;
		   	fprintf(stderr,"No remote port specified, defaulting to 6667\n");
		}
	}
   
   	/* Create a "server tag" for this proxy.  This is used for preventing
	** loops, while allowing proxy-to-proxy operation.
	*/
	{
	   	char hostname[128];
	   	gethostname(hostname, 127);

		/* server_tag is 64 bytes - this is safe */
   		sprintf(server_tag, "X-tircproxy[%d/%.10s]\n", 
				getpid(), hostname );
	}
	
#ifdef USE_UDB	
	/* Aquire handles to the UDB shared memory.
	*/
	udb_init(UDB_ENV_BASE_KEY);
#endif
	
	/* See if we should run as a server.
	*/
	if (run_as_server)
	{
	   	/* Randomize the anonymouse userid.
		*/
	   	if (use_anonymity)
	     	{
		   	srand(time((time_t) NULL));
		   	anon_notval = rand();
		}

		/* Start by binding to the port, the child inherits this socket.
		*/
		sock = bind_to_port(bind_ip, bind_port, SOMAXCONN, 0);
		if (sock < 0)
		{
			usage(argv[0], "Failed to bind to port, is another tircproxy still running?");
		}

		/* Start a server process. When DEBUG is defined, and the
		** debug level is high, just run in the foreground.
		*/
#ifdef TIRC_DEBUG
		switch ((debug_level >= DEBUG_NOFORK) ? 0 : fork())
#else
		switch (fork())
#endif
		{
		case -1:
			perror("fork()");
			exit(1);

		case 0:
			/* Open syslog for logging errors.
			*/
		   	if ((use_syslog) && (debug_level < DEBUG_NOFORK))
			   	openlog((not_an_irc_proxy) ? "proxy" : "tircproxy",
					LOG_PID, LOG_DAEMON);

			/* Ignore some signals.
			*/
			signal(SIGHUP, SIG_IGN);
#ifdef TIRC_DEBUG
		   	if (debug_level < DEBUG_NOFORK)
#else
				signal(SIGINT, SIG_IGN);
#endif
			signal(SIGQUIT, SIG_IGN);
			signal(SIGTSTP, SIG_IGN);
			signal(SIGCONT, SIG_IGN);
			signal(SIGPIPE, SIG_IGN);
			signal(SIGALRM, alarm_signal);
		   
		   	if (run_gid)
		     	{
				/* Drop back to an untrusted user.
				*/
				setgid(run_gid);
				setuid(run_uid);

				/* Start a new session and group.
				*/
				setsid();
#ifdef HAVE_LINUX
				setpgrp();
#endif
			}

			/* Handle the server main loop.
			*/
			server_main_loop(sock);

			/* Should never exit.
			*/
			closelog();
			exit(1);
		}

		/* Parent exits at this stage.
		*/
		exit(0);
	}

	/* Open syslog for logging errors.
	*/
   	if ((use_syslog) && (debug_level < DEBUG_NOFORK))
	  	openlog((not_an_irc_proxy) ? "proxy" : "tircproxy", 
			LOG_PID, LOG_DAEMON);

	/* We are running from inetd so find the peer address.
	*/
	len = sizeof(addr);
	if (getpeername(STDIN_FILENO, (struct sockaddr *)&addr, &len) < 0)
	{
		debug_msg(0, LOG_ERR, "getpeername(): %.256s", strerror(errno));
		closelog();

		usage(argv[0], NULL);
	}
   
   	/* Randomize (well, not really..) the anonymouse userid.
	*/
   	if (use_anonymity) anon_notval = (time((time_t) NULL) >> 17);

   	/* Make note of the client's IP address.
	*/
        clients_ip = addr.sin_addr.s_addr;
       	lookup_hostname(&addr, clients_ip_s, sizeof(clients_ip_s), 0);

	/* Set the keepalive socket option to on.
	*/
	{
		int on = 1;
		setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, (char *)&on, sizeof(on));
	}

	/* We are running from inetd so process stdin.
	*/
	trans_proxy(STDIN_FILENO, &addr);
	closelog();

	return (0);
}


/* Print some basic help information.
*/
static void usage(char *prog, char *opt)
{
	fprintf(stderr,"%s\n\n\
Usage: %s [switches..] [options..] [ircserver [ircport]]\n\
\n\
Options:\n",Version,prog);
#ifdef TIRC_DEBUG
	fprintf(stderr,
"   -d level    Set debug level (0=nothing .. 8=everything, 9=foreground).\n");
#endif
	fprintf(stderr,"\
   -s port     Run as a server bound to the specified port.\n\
   -b ipaddr   Bind to the specified ipaddr in server mode.\n\
   -i ipaddr   Internal IP, needed for incoming DCCs (when transparent).\n\
   -o ipaddr   Visible IP, used for connecting to the IRC server (and others).\n");
#ifdef QUIZ_MODE
	fprintf(stderr,"\
   -p          Require a valid Unix password for access.\n\
   -q file     Read a list of 'quizzes' from the named file.\n");
#endif
	fprintf(stderr,"\
   -a          Anonymous mode, hide as much info about the user as possible.\n\
   -r user     Run as the specified user in server mode.\n\
   -t n	       Force a sleep(1) between connections under n seconds apart.\n\
Switches:\n\
   -M          Disable DCC SEND filename mangling/censorship.\n\
   -C          Disallow DCC CHAT.\n\
   -S          Disallow DCC file transmissions (SEND, TSEND, etc.).\n\
   -U          Disallow unknown DCC requests.\n\
   -L          Log to stderr instead of syslog.\n\
   -N          Not an IRC proxy, disable all neat-o IRC stuff\n");
#ifdef CDIR
	fprintf(stderr,"\
   -I          Do not attempt to communicate with the identd.\n");
#endif
#ifdef PARANOID
	fprintf(stderr,"\
   -R          Relaxed, allows proxy service to IPs we can't map to a UID.\n");
#endif
#ifdef NICK_LOG
	fprintf(stderr,"\
   -D          Don't log clients' nicknames in syslog.\n");
#endif
#ifdef MIRC_DCC_KLUDGE
	fprintf(stderr,"\
   -K          Disable the \"mIRC DCC kludge\" (read the documentation).\n");
#endif
#ifdef TCP_WRAPPERS
	fprintf(stderr,"\
   -H          Ignore /etc/hosts.allow and /etc/hosts.deny.\n");
#endif
	fprintf(stderr,"\n\
By default all features enabled at compile-time are active.  In server mode\n\
the -s flag is mandatory.\n");

#ifdef TRANS
 	fprintf(stderr,"\n\
If no IRC server/port pair is specified the proxy assumes it is running in\n\
transparent mode.  This requires support from the Linux kernel or a working\n\
ipf filter installation!\n");
#endif

	if (opt)
 	{
		fprintf(stderr,"\nERROR:  %s\n", opt);
	}
 
	exit(1);
}


/* Return the port number, in network order, of the specified service.
*/
static short get_port_num(char *portnum)
{
	char			*digits = portnum;
	struct servent		*serv;
	short			port;

	for (port = 0; isdigit(*digits); ++digits)
	{
		port = (port * 10) + (*digits - '0');
	}

	if ((*digits != '\0') || (port <= 0))
	{
		if ((serv = getservbyname(portnum, "tcp")) != NULL)
		{
			port = ntohs(serv->s_port);
		}
		else
		{
			port = -1;
		}
		endservent();
	}

#ifdef TIRC_DEBUG
   	debug_msg(DEBUG_TRIVIA, LOG_DEBUG,
		  "Port lookup %s -> %hd\n", portnum, port);
#endif

	return (port);
}


/* Return the IP address of the specified host.
*/
static ipaddr_t get_ip_addr(char *ipaddr)
{
	struct hostent	*host;
	ipaddr_t		ip;

	if (((ip = inet_addr(ipaddr)) == INADDR_NONE) && 
	    (strcmp(ipaddr, "255.255.255.255") != 0))
	{
		if ((host = gethostbyname(ipaddr)) != NULL)
		{
			memcpy(&ip, host->h_addr, sizeof(ip));
		}
		endhostent();
	}

#ifdef TIRC_DEBUG
   	debug_msg(DEBUG_BASIC, LOG_DEBUG, 
		  "IP lookup %s -> 0x%08lx\n", ipaddr, ip);
#endif

	return (ip);
}


/* Find the userid of the specified user.
*/
static uid_t get_user_id(char *user)
{
	struct passwd	*pw;
	uid_t			uid;

	if ((pw = getpwnam(user)) != NULL)
	{
		uid = pw->pw_uid;
	}
	else if (*user == '#')
	{
		uid = (uid_t)atoi(&user[1]);
	}
	else
	{
		uid = -1;
	}

#ifdef TIRC_DEBUG
   	debug_msg(DEBUG_BASIC, LOG_DEBUG,
	    "User lookup %s -> %d\n", user, uid);
#endif

	endpwent();

	return (uid);
}


/* Find the groupid of the specified user.
*/
static uid_t get_group_id(uid_t uid)
{
	struct passwd	*pw;
	gid_t			gid;

	if ((pw = getpwuid(uid)) != NULL)
	{
		gid = pw->pw_gid;
	}
	else
	{
		gid = -1;
	}

#ifdef TIRC_DEBUG
   	debug_msg(DEBUG_TRIVIA, LOG_DEBUG, 
		  "Group lookup %d -> %d\n", uid, gid);
#endif

	endpwent();

	return (gid);
}


/* Bind to the specified ip and port.
*/
static int bind_to_port(ipaddr_t bind_ip, short bind_port, int backlog, int dlev)
{
	struct sockaddr_in	addr;
	int			sock;

	/* Allocate a socket.
	*/
	if ((sock = socket(AF_INET, SOCK_STREAM, 
			getprotobyname("tcp")->p_proto)) < 0)
	{
     		debug_msg(dlev, LOG_WARNING, "socket(): %d - %.256s", errno, strerror(errno));
	   	return(-1);
	}

#ifdef TIRC_DEBUG
	/* Set the SO_REUSEADDR option for debugging.
	*/
	if (debug_level >= DEBUG_NOFORK) {
	 	int on = 1;

		setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on));
	}
#endif

	/* Set the address to listen to.
	*/
   	memset(&addr, '\0', sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(bind_port);
	addr.sin_addr.s_addr = bind_ip;

	/* Bind our socket to the above address.
	*/
	if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0)
	{
		debug_msg(dlev, LOG_WARNING, "bind(): %d - %.256s", errno, strerror(errno));
	   	return(-1);
	}

	/* Establish a large listen backlog.
	*/
	if (listen(sock, backlog) < 0)
	{
		debug_msg(dlev, LOG_WARNING, "listen(): %d - %.256s", errno, strerror(errno));
	   	return(-1);
	}

	return (sock);
}


/* Connect to the a server.
*/
static int connect_to_server(struct sockaddr_in addr)
{
   	struct sockaddr_in my_addr;
	int	sock;

	/* Allocate a socket.
	*/
	if ((sock = socket(AF_INET, SOCK_STREAM, 
					   getprotobyname("tcp")->p_proto)) < 0)
	{
		debug_msg(0, LOG_WARNING, "socket(): .%256s", strerror(errno));
		return (-1);
	}
	
	/* Set the address to connect to, and the address to connect from.
	*/
	addr.sin_family = AF_INET;

   	memset(&my_addr, '\0', sizeof(my_addr));
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = 0;
	my_addr.sin_addr.s_addr = visible_ip_o;
	
   	/* Bind to the address we want to use.
	*/
   	if (bind(sock, (struct sockaddr *)&my_addr, sizeof(my_addr)) < 0)
	  debug_msg(0, LOG_WARNING, "bind(): %d - %.256s", 
				errno, strerror(errno));

   	/* Connect our socket to the above address.
	*/
	if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0)
	{
		debug_msg(0, LOG_WARNING, "connect(): %.256s", strerror(errno));
		return (-1);
	}

	/* Set the keepalive socket option to on.
	*/
	{
		int on = 1;
		setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char *)&on, sizeof(on));
	}

	return (sock);
}


/* Translate a sockaddr_in structure into a usable ASCII hostname.
*/
static void lookup_hostname(struct sockaddr_in *addr, char *hostname,
						int hostlen, int needed)
{
	struct hostent *host;

   	if (needed)
     	{
		/*  Attempt a DNS lookup..
		*/
	   	if ((host = gethostbyaddr((char *)&addr->sin_addr,
					 	sizeof(addr->sin_addr),
					  	AF_INET)) != NULL) 
	     	{
			strncpy(hostname, host->h_name, hostlen);
		   	hostname[hostlen - 1] = '\0';
		   	return;
		}
	}

	/* Get the hostname IP in dotted decimal in case the lookup fails.
	*/
	strncpy(hostname, inet_ntoa(addr->sin_addr), hostlen);
	hostname[hostlen - 1] = '\0';
}


/* This is the main loop when running as a server.
*/
static void server_main_loop(int sock)
{
	int					new_sock;
	struct sockaddr_in			addr;
	int					len;
   	time_t					last_time, this_time;
 	int				  	delay;

	/* Call cleanup func on SIGCHLD.
	*/
	signal(SIGCHLD, chld_signal);
   
   	last_time = (time_t) 0;
   	delay = 0;

	for (;;)
	{
		/* Accept an incoming connection.
		*/
		len = sizeof(addr);
		while ((new_sock = 
			accept(sock, (struct sockaddr *)&addr, &len)) < 0)
		{
			/* Connection resets are common enough to log them as debug only.
			*/
			debug_msg(0, (errno == ECONNRESET ? LOG_DEBUG : LOG_ERR), "accept(): %.256s", strerror(errno));
		}

	   	/* At most send the server one connection every 
		** throttle_seconds seconds.
		*/
	   	this_time = time((time_t) NULL);
	   	if (this_time <= last_time+delay+throttle_seconds) 
	     		delay++;
		else	delay = 0;
	   	last_time = this_time;

	   	/* Make note of the client's IP address.
		*/
	        clients_ip = addr.sin_addr.s_addr;
	       	lookup_hostname(&addr, clients_ip_s, sizeof(clients_ip_s), 0);

		/* Create a new process to handle the connection.
		*/
		switch (fork())
		{
		case -1:
			/* Under load conditions just ignore new connections.
			*/
			break;

		case 0:
			/* Start the proxy work in the new socket.
			*/
		   	broadcast_flag = 0;
       		  	if (delay) {
				debug_msg(0, LOG_DEBUG,"Delaying connection %d seconds..",delay);
				sleep(delay);
			}
			trans_proxy(new_sock, &addr);
			close(new_sock);
			closelog();
			exit(0);
		}

		/* Close the socket as the child does the handling.
		*/
		close(new_sock);
	}
}


/* Handle connections transparently or otherwise..
*/
static void trans_proxy(int sock, struct sockaddr_in *from_addr)
{
	struct sockaddr_in			to_addr;
	int					to_len;
#if IPF
#if __FreeBSD_version >=600024
	ipfobj_t        obj;
#endif
	struct sockaddr_in                      socketin, sloc;
	natlookup_t				natlook;
	natlookup_t                             *natlookp = &natlook;
	int                                     fd;
#endif
   
   	/* Give this thing 10 minutes to get started (paranoia).
	*/
	signal(SIGALRM, alarm_signal);
   	alarm(10*MINUTE);
   	strcpy(alarm_in, "trans_proxy");

   	/*  Check who the client is.
	*/
   	get_user_name(from_addr);

#ifdef TCP_WRAPPERS
	if (use_tcp_wrappers)
     	{
		char host_ip[64], host_name[64];

   		/* Lookup an ASCII representation of the host's IP address,
		** and attempt to look up it's FQDN as well (DNS).
       		*/
       		lookup_hostname(from_addr, host_ip, sizeof(host_ip), 0);
       		lookup_hostname(from_addr, host_name, sizeof(host_name), 1);

   		/*  Check if the calling host is allowed to use the proxy.
		*/   
       		if (!hosts_ctl((not_an_irc_proxy) ? "proxy" : "tircproxy",
			       host_name, host_ip, user_name))
	     	{
		   	debug_msg(0, LOG_INFO,
			       	"Denied access to: %.32s@%.128s [%.96s]", 
			       		user_name, host_name, host_ip);
		     	return;
		}
	}
#endif

   	/* Are we running in transparent mode?
	*/
   	if (server_ip == INADDR_NONE) 
     	{
	        /* There are two completely different ways to do this, one 
		** based on linux transparent proxying the other based on 
		** ipf transparent proyxing, only one is used 
		*/
#if LINUX
                /* The Linux method:
		** 
		** The first thing we do is get the IP address that the client was
		** trying to connected to. Here lies part of the magic. Normally
		** getsockname returns our address, but not with transparent proxying.
		*/
		to_len = sizeof(to_addr);
		if (getsockname(sock, (struct sockaddr *)&to_addr, &to_len) < 0)
		{
			debug_msg(0, LOG_ERR, "getsockname(): %.256s", strerror(errno));
			return;
		}
#else
# if IPF
	   	/* This is the ipf method 
		*/
                to_len = sizeof(socketin);
                if (getpeername(sock, (struct sockaddr *)&socketin, &to_len) == -1)
	     	{
                	perror("getpeername");
                	exit(-1);
                }

                to_len = sizeof(socketin);
                if (getsockname(sock, (struct sockaddr *)&sloc, &to_len) == -1) 
	     	{
                	perror("getsockname");
                	exit(-1);
                }

#if __FreeBSD_version >=600024
		bzero(&obj, sizeof(obj));
		obj.ipfo_rev = IPFILTER_VERSION;
		obj.ipfo_size = sizeof(natlook);
		obj.ipfo_ptr = &natlook;
		obj.ipfo_type = IPFOBJ_NATLOOKUP;
#endif
                bzero((char *)&natlook, sizeof(natlook));
                natlook.nl_outip = socketin.sin_addr;
                natlook.nl_inip = sloc.sin_addr;
                natlook.nl_flags = IPN_TCP;
                natlook.nl_outport = socketin.sin_port;
                natlook.nl_inport = sloc.sin_port;

                fd = open(IPL_NAT, O_RDONLY);
#if __FreeBSD_version >=600024
		if (ioctl(fd, SIOCGNATL, &obj) == -1)
#else
		if (ioctl(fd, SIOCGNATL, &natlookp) == -1)
#endif
	     	{
                	perror("ioctl(SIOCGNATL)");
                	exit(-1);
                }
                close(fd);
 
	   	/* [ The following is WEIRD.  Why the htons(ntohs()) ??
		**   And why the getip..(inet_ntoa()) ??? ]
		*/
	   	memset(&to_addr, '\0', sizeof(to_addr));
                to_addr.sin_family = AF_INET;
                to_addr.sin_port = htons(ntohs(natlook.nl_realport));
                to_addr.sin_addr.s_addr = get_ip_addr(inet_ntoa(natlook.nl_realip));
# endif /* IFP */
#endif /* LINUX */
	}
   	else
     	{
	   	/* Ok, not transparent, we'll connect to the specified server.
		*/
	   	memset(&to_addr, '\0', sizeof(to_addr));
		to_addr.sin_family = AF_INET;
		to_addr.sin_port = htons(server_port);
		to_addr.sin_addr.s_addr = server_ip;
	}

   	/*  Drop root privs if we have them.
	*/
   	change_uid();

        signal(SIGHUP, hup_signal);

   	debug_msg(0, LOG_DEBUG,
	       	"Copied %d bytes, exiting.",
	       	copy_loop(sock, from_addr, &to_addr, !not_an_irc_proxy));
   
#ifdef CDIR_IDENT
   	if (use_cdir)
     		cleanup_identd();
#endif
}


/* Perform the proxy activity itself.
*/
static int copy_loop(int sock, struct sockaddr_in *from_addr, 
		      		struct sockaddr_in *to_addr,
		      		int is_server_connection)
{
	int					server,junk;
   	struct sockaddr_in			ourend, ourend_i;
   	char					blurb[64];
	char					from_host[64];
	char					to_host[64];
	static char				headers[16384 + 1];
	static char				ser_left[16384 + 1];
	static char				cli_left[16384 + 1];
   	int					ser_ln = 0;
     	int					cli_ln = 0;
   	int					select_loop;
	int					max_fd;
	fd_set					read_fd;
	int					read_len;
   	int					motd_sent = 0;
   	int					copied_bytes = 0;

	/* Allow 3 minutes for things to get properly started.
	*/
	signal(SIGALRM, alarm_signal);
   	alarm(3*MINUTE);
	strcpy(alarm_in, "copy_loop: starting");

   	/* Lookup an ASCII representation of the host's IP address.
    */
	lookup_hostname(from_addr, from_host, sizeof(from_host), 0);
	lookup_hostname(to_addr, to_host, sizeof(to_host), 0);

	/* Read the first couple of lines from the client, take a 
	** peek.  Check if the server is looping.
	*/
   	if (is_server_connection)
	{
#ifdef TIRC_DEBUG
		debug_msg(DEBUG_BASIC, LOG_DEBUG, 
				  "Checking if this is a loopy server..");
#endif
	   	server = read_len = -1;
		copy_once(sock, headers, sizeof(headers), "client",
				  server, ser_left, &ser_ln, "server",
				  is_server_connection, 1,
				  &read_len);
			
		if (read_len <= 0) return 0;
	   
	   	strncpy(blurb,headers,63);
	   	if (strstr(blurb,server_tag) != NULL)
	     	{
		   	debug_msg(0, LOG_INFO, "Looping detected, terminating!");
		   	return 0;
		}
	}
   
	/* Connect to the server (or die).
	*/
	if ((server = connect_to_server(*to_addr)) < 0) return 0;
   	junk = sizeof(ourend);
	strcpy(alarm_in, "copy_loop: getsockname 1");
   	getsockname(server, (struct sockaddr *) &ourend, &junk);

#ifdef CDIR_IDENT
	/* Tell the ident server which user this connection really belongs to.
	** 
	** Note: This may be a race between us and the remote IRC server, 
	**       depending on the implementation of the ident server.
	*/
   	if (use_cdir && is_server_connection) tell_identd(&ourend, to_addr);
#endif

	strcpy(alarm_in, "copy_loop: getsockname 2");
   	getsockname(sock, (struct sockaddr *) &ourend_i, &junk);

   	/* Record what IP addresses we're using.
	*/
	visible_ip_o = ourend.sin_addr.s_addr;
	lookup_hostname(&ourend, visible_ip_o_s, sizeof(visible_ip_o_s), 0);
	
   	if (visible_ip_i == INADDR_ANY) 
	{
		visible_ip_i = ourend_i.sin_addr.s_addr;
		lookup_hostname(&ourend_i, visible_ip_i_s, sizeof(visible_ip_i_s), 0);
	}
	
	/* Log the facts about the connection.
	*/
	debug_msg(0, LOG_INFO, "%.64s -%.64s:%d-> %.64s:%d",
			  from_host, user_name, ntohs(ourend.sin_port), 
			  to_host, ntohs(to_addr->sin_port));
	
	/* Pass the data we read just now on to the server..
	*/
   	if (is_server_connection)
	{
		junk = strlen(server_tag);
		copy_once(sock, server_tag, 0, "tircproxy",
				  server, ser_left, &ser_ln, "server",
				  is_server_connection, 1, &junk);
		copy_once(sock, headers, sizeof(headers), "client",
				  server, ser_left, &ser_ln, "server",
				  is_server_connection, 1, &read_len);
		if (read_len <= 0) return 0;
	}
   
	/* Continue by passing data back and forth between the 
	** client and the server (or remote end of DCC).
	*/
	for (;;)
	{
		/* Construct a select read mask from both file descriptors.
		*/
		FD_ZERO(&read_fd);
		FD_SET(sock, &read_fd);
		FD_SET(server, &read_fd);
		max_fd = FD_MAX(sock, server);

		/* Allow 30 minutes for the loop to complete.
		*/
		signal(SIGALRM, alarm_signal);
		alarm(30*MINUTE);
	   	strcpy(alarm_in, "copy_loop: waiting");
#ifdef TIRC_DEBUG
	   	debug_msg(DEBUG_TRIVIA, LOG_DEBUG,"copy_loop: waiting");
#endif
		/* Wait for some data to be read.
		*/
	   	select_loop = 1;
	   	while (select_loop)
			if (select(max_fd + 1, &read_fd, NULL, NULL, NULL) < 0)
			{
				if (errno != EINTR) {
#ifdef TIRC_DEBUG
					debug_msg(DEBUG_BASIC, LOG_ERR, 
						  "select(): %.256s", 
						  strerror(errno));
#endif
				   	close(server);
					return copied_bytes;
				}
			} else
	    			select_loop = 0;
	   
	   	/* We aren't waiting anymore..
		*/
	   	strcpy(alarm_in, "copy_loop: copying");
#ifdef TIRC_DEBUG
	   	debug_msg(DEBUG_TRIVIA, LOG_DEBUG,"copy_loop: copying");
#endif

	   	if (is_server_connection)
	     	{
			/* Send the user a message, if requested by admin.
			*/
#ifdef BROADCAST_FILE		   
		   	if (broadcast_flag) broadcast(sock, BROADCAST_FILE);
#endif
#ifdef IRC_MOTD_FILE
			/* Send the user the MOTD.
			*/
			if ((!motd_sent) &&
			    (motd_is_done) && 
			    (is_server_connection))
		     	{
			   	broadcast(sock, IRC_MOTD_FILE);
			   	motd_sent = 1;
	  		}
#endif
		}
		   
		/* See if any data can be read from the client.
		*/
		if (FD_ISSET(sock, &read_fd))
		{
		   	read_len = 0;
			copy_once(sock, headers, sizeof(headers), "client",
				  server, ser_left, &ser_ln, "server",
				  is_server_connection, 1,
				  &read_len);
			if (read_len < 0) return copied_bytes;
		   	copied_bytes += read_len;
		}
	   
		/* See if any data can be read from the server.
		*/
		if (FD_ISSET(server, &read_fd))
		{
		   	read_len = 0;
			copy_once(server, headers, sizeof(headers), "server",
				  sock, cli_left, &cli_ln, "client",
				  is_server_connection, 0,
				  &read_len);
			if (read_len < 0) return copied_bytes;
		   	copied_bytes += read_len;
		}

#ifdef QUIZ_MODE
	   	/* Dump queued data meant for the client.
		*/
	   	if (use_quiz_mode && motd_is_done)
			quiz_dump_lines(sock, QUIZ_C);

	   	/* Dump queued data, stop quizzing if quiz mode is "flush".
		*/
	   	if ((use_quiz_mode == QUIZ_FLUSH) && (copied_bytes > 128))
	     	{
		   	quiz_dump_lines(server, QUIZ_S);
		   	use_quiz_mode = QUIZ_OFF;
		}
#endif
	}
}


/* This function handles a single copy cycle from one socket to another.
** If *read_len <= 0, then
**	It reads at most rbufsize bytes from rsock, into rbuffer.
** If *read_len >= 0, then
** 	It writes the contents of rbuffer to wsock.
** 
** Writes are filtered if the is_server_connection is true.
** The function returns -1 or -2 on errors, or the number of read bytes on
** success.
*/
static int  copy_once(int rsock, char *rbuffer, int rbufsize, char *rname,
		      int wsock, char *wlbuffer, int *wlbufpos, char *wname,
		      int is_server_connection, int r_is_client,
		      int *read_len)
{ 
   	int rlen;
   	
#ifdef TIRC_DEBUG
	debug_msg(DEBUG_TRIVIA, LOG_DEBUG, 
			  "Entering copy_once (reading %s, writing %s)",
			  rname, wname);
#endif

   	rlen = *read_len;
   	if (rlen <= 0) rlen = read(rsock, rbuffer, rbufsize - 1);

	switch (rlen)
	{
	  case -1:
#ifdef TIRC_DEBUG
		debug_msg(DEBUG_BASIC, 
				  (errno == ECONNRESET ? LOG_DEBUG : LOG_WARNING),
				  "read(%.32s) failed: %.256s", rname, strerror(errno));	
#endif
		if (errno != EINTR)
		{
		   	close(wsock);
			return -2;
		}
	   	return 0;
	  case 0:
#ifdef TIRC_DEBUG
		debug_msg(DEBUG_TRIVIA, LOG_DEBUG, "EOF!");
#endif
		close(rsock);
		close(wsock);
		return -1;
	  default:
		if (*read_len >= 0)
		{
			if (is_server_connection)
			{
				if (!filtered_write(wsock, rbuffer, rlen,
									wlbuffer, wlbufpos, r_is_client)) 
				{
#ifdef TIRC_DEBUG
					debug_msg(DEBUG_BASIC, LOG_WARNING, 
							  "write(%.32s) failed: %.256s",
							  wname, strerror(errno));
#endif
				   	close(rsock);
				   	return 0;
				}
			}
	   		else
			{
				while (write(wsock, rbuffer, rlen) < 0)
				  if (errno != EINTR)
				{
#ifdef TIRC_DEBUG
					debug_msg(DEBUG_BASIC, LOG_WARNING, 
							  "write(%.32s) failed: %.256s",
							  wname, strerror(errno));
#endif
					close(rsock);
					return 0;
				}
			}
			break;
		}
	}
#ifdef TIRC_DEBUG
	debug_msg(DEBUG_TRIVIA, LOG_DEBUG, 
			  "Leaving copy_once (rlen = %d)", rlen);
#endif

   	return (*read_len = rlen);
}


/* Line buffered write routine.. only dumps whole lines, and only 
** after submitting them to the CTCP police.
*/
static int filtered_write(int to_sock, char *buff, int blen, 
			   	       	char *leftover, int *llen,
			   		int isclient)
{
   	char	*nl, *ol, *lp;
  	int	l,silent;

#ifdef TIRC_DEBUG
	debug_msg(DEBUG_TRIVIA, LOG_DEBUG, "Entering filtered_write");
#endif
	
   	buff[blen] = '\0';	/* Null terminate buffer for strchr */

	/* Break the input into lines, parse each one and send them one at
	** a time to the client.
	*/
   	nl = ol = buff;
   	lp = leftover + *llen;
   	while ((nl = strchr(ol,'\n')) != NULL)
	{
	   	/* nl points to the first '\n' in the input.
		** ol points to the beginning of the unscanned input.
	   	** lp points to the end of the contents of 'leftover'
		**    which is the the fragment of a line we couldn't finish
		**    processing last time.
		*/

	   	nl++;			/* Move nl past the '\n'		*/
		l = nl-ol;		/* l = the length of this line		*/
	   	if (l > 1500) l = 1500;	/* Arbitrary limits == bad!  FIXME!!		*/
	   	memmove(lp, ol, l);	/* Append new line to leftover		*/
	   	lp[l] = '\0';		/* Null terminate leftover		*/

	   	/* Okay, now we have a complete line of data in the leftover 
		** buffer - scan it for interesting bits, and send it if it 
		** isn't deemed completely obscene. :)
		*/
	   	if ((silent = scan_line(leftover, isclient)) > 0)
		{
			while (write(to_sock, leftover, 
						 strlen(leftover)) < 0)
			  if (errno != EINTR)
			{
#ifdef TIRC_DEBUG
				debug_msg(DEBUG_BASIC, LOG_WARNING, 
						  "write(%s) failed: %.256s",
						  isclient ? "client" : "server",
						  strerror(errno));
#endif
				close(to_sock);
				return 0;
			}
		}
#ifdef TIRC_DEBUG
			else if (silent >= 0)
			  debug_msg(DEBUG_FEATURES, LOG_DEBUG,
						"Squelched: %.256s", leftover);
#endif

   		/* Maintain loop integrity..
		*/
		ol = nl;
		lp = leftover;
	}
   	/* Loop has terminated - if anything is left in ol, then it isn't
	** a complete line we can scan, it's only a fragment.
	**
	** .. so we save it for next time, and return 1 for success.
	*/
	*llen = blen + (buff - ol);
	memmove(leftover, ol, *llen);
	
	return 1;
}


/* This function scans a single line of data for DCC constructs and other
** information we need to keep track of.  The line is rewritten if necissary.
** Returns 1 if the line is "okay for writing", 0 if it is to be suppressed,
** -1 if it is to be suppressed silently.
*/
#define CTRL_A	('A'&31)
static int scan_line(char *line, int isclient)
{
	char *done, *ctcp_begin, *ctcp_end, *p;
   	char replace[LENGTH_SERVERDATA * 2];

   	/* Sanity check..
	*/
   	if (strlen(line) > LENGTH_SERVERDATA) {
	   	debug_msg(0, LOG_ERR, "Input to scan_line() of illegal length - input not scanned!");
	       	return(1);
	}

   	/* Scan all CTCPs (it IS legal to embed more than one in the same,
	** line, but most clients don't handle this correctly).
	*/
   	done = line;
   	while (((ctcp_begin = strchr(done, CTRL_A)) != NULL) &&
	       ((ctcp_end = strchr(ctcp_begin + 1, CTRL_A)) != NULL))
     	{
	   	char ctcp_type[32];

	   	strncpy(ctcp_type, ctcp_begin + 1, sizeof(ctcp_type));
	   	ctcp_type[sizeof(ctcp_type) - 1] = '\0';
	   	if ((p = strchr(ctcp_type, ' ')) != NULL) *p = '\0';

	        /* It's a CTCP.. but is it DCC?
		*/
	   	if (!strcasecmp(ctcp_type, "DCC"))
		{
		   	/* Okay, it's DCC.. rewrite it!
			*/
		   	int		squelch = 0;
		   	char		type[32];
		   	char		arg_1[256];
		   	char		*rest;
		   	unsigned int	arg_2, arg_3, scanned;

		   	*replace = *ctcp_begin = *ctcp_end = '\0';  /* Chop! */
		   	ctcp_begin++;

		   	if (sscanf(ctcp_begin, "%4s %31s %255s %u%u%n",
			   	ctcp_type, type,
			   	arg_1, &arg_2, &arg_3,
			   	&scanned) >= 5)
		     	{
#ifdef TIRC_DEBUG
			   	debug_msg(DEBUG_FEATURES, LOG_DEBUG,
				       	"Caught: %.256s", ctcp_begin);
#endif
				rest = ctcp_begin + scanned;

       			   	if (!strcasecmp(type, "CHAT"))
			     	{
       					if (!allow_dcc_chat)
#ifdef DISALLOW_DCC_CHAT
					        sprintf(replace, 
							DISALLOW_DCC_CHAT, 
							type, arg_1);
#else
						squelch = 1;
#endif
       				}
       			   	else if (!strcasecmp(type, "SEND") ||
				    	 !strcasecmp(type, "TSEND") ||
					 !strcasecmp(type, "RESEND") ||
					 !strcasecmp(type, "TRESEND"))
			     	{
			     		if (!allow_dcc_send)
#ifdef DISALLOW_DCC_SEND
					        sprintf(replace, 
							DISALLOW_DCC_SEND,
							type, arg_1);
#else
						squelch = 1;
#endif
				   	else if (!dcc_mangle_filename(arg_1))
#ifdef MANGLE_DCC_SEND
					        sprintf(replace, 
							MANGLE_DCC_SEND,
							type, arg_1);
#else
				 		squelch = 1;
#endif
			     	}
			   	else if (!strcasecmp(type, "RESUME"))
			     	{
				        sprintf(replace, 
						"%cDCC %s %s %d %d%s%c",
						CTRL_A, 
						type, arg_1,
				   		dcc_resume(arg_2, 1),
			   			arg_3, rest, CTRL_A);
       				}
       			   	else if (!strcasecmp(type, "ACCEPT"))
			     	{
				        sprintf(replace, 
						"%cDCC %s %s %d %d%s%c",
       						CTRL_A, 
						type, arg_1,
				   		dcc_resume(arg_2, 0),
				   		arg_3, rest, CTRL_A);
       				}
			   	/* Insert new DCC protocols here :-) 
				*/
				else if (!allow_dcc_unknown)
#ifdef DISALLOW_DCC_SEND
				        sprintf(replace, 
						DISALLOW_DCC_FUNK, 
						type, arg_1);
#else
			   		squelch = 1;
#endif

       			   	if (!*replace && !squelch)
				{
				   	/* No replacement has been set, so
					** proxy this connection.  :-)
					*/
				        sprintf(replace, 
						"%c%s %s %s %s%s%c",
       						CTRL_A, ctcp_type,
						type, arg_1,
					    proxy_dcc(arg_2, arg_3, isclient),
       						rest, CTRL_A);
				}
			}

		   	/* Alternate (!= DCC) protocols go here.. */

		   	/* Are we changing anything?
			*/
		   	ctcp_begin--;
		   	if (*replace)
		     	{
#ifdef TIRC_DEBUG
			   	debug_msg(DEBUG_FEATURES, LOG_DEBUG,
					  "Sent: %.256s", replace);
#endif
			   	done = ctcp_begin + strlen(replace); /* Mark */
			   	strcat(replace, ctcp_end + 1);
			   	strcpy(ctcp_begin, replace);	  /* Rewrite */
			} 
		   	else
		     	{
			   	*ctcp_begin = *ctcp_end = CTRL_A;  /* Unchop */
			   	done = ctcp_end + 1;		    /* Mark */

			   	if (squelch)
			     		return(0);
			}
		} /* is DCC */ 
	   	else if ((use_anonymity) && 
			 (isclient) && 
			 ((!strcasecmp(ctcp_type, "VERSION")) ||
			  (!strcasecmp(ctcp_type, "FINGER")) ||
			  (!strcasecmp(ctcp_type, "USERINFO")) ||
			  (!strcasecmp(ctcp_type, "CLIENTINFO"))) &&
			  (!strncasecmp(line, "NOTICE", 6)))
     		{
		   	strcpy(replace, CTCP_NOT_ALLOWED);
		   	done = ctcp_begin + strlen(replace) + 2; /* Mark */
		   	strcat(replace, ctcp_end);
		   	strcpy(ctcp_begin + 1, replace);       /* Rewrite */
		}
	   	else if (isclient)
	     	{
		   	/* Squelch other outgoing CTCPs containing the
			** client's IP address.
			*/
		   	if (strstr(ctcp_begin, clients_ip_s) != NULL) 
		     		return (0);
	     		done = ctcp_end + 1;
		}
	   	else
	     		done = ctcp_end + 1;
     	}
   
	/* Scan server input for the client's nickname.
	*/
   	if (!isclient)
     	{
		char type[128];
	   	char arg[32];
		char nuh[512];

       	   	if (sscanf(line, ":%511s %127s %31s", nuh, type, arg) == 3)
		{
			if ((isdigit(*type) ||
			    (!strcasecmp(type, "PRIVMSG"))) && 
			    ((*arg != '&') && (*arg != '#') &&
			     (*arg != '%') &&
			     (*arg != '!') && (*arg != '*')))
			{
#ifdef NICK_LOG
			   	if (use_nick_log && 
				    strcasecmp(user_nick, arg))
			     		debug_msg(0, LOG_INFO, "Nick: %.256s", arg);
#endif
			   	strncpy(user_nick, arg, sizeof(user_nick));
			   	user_nick[sizeof(user_nick) - 1] = '\0';
			}
		   	if ((!motd_is_done) && 
			    ((!strcasecmp(type,"376")) || 
			     (!strcasecmp(type,"421"))))
		     		motd_is_done = 1;
   		}
	   	/* The server shouldn't start PINGing us until well
		** after the end of the MOTD.. 
		*/
	   	if ((!motd_is_done) && (!strncasecmp(line,"PING",4)))
	     		motd_is_done = 1;
     	}
   	else if (use_anonymity) 	/* Data is from client, anonymize */
     	{
	   	if (!strncasecmp(line, "NOTICE", 6))
	     	{
		   	if (strstr(line, clients_ip_s) != NULL) 
		     		return (0);
		}
	   	else if (!strncasecmp(line, "USER", 4))
	     	{
	   		int cip = ANON_USERID;
	   		sprintf(line, 
				"USER t%x anon anon :%s\n", 
				cip, ANON_IRCNAME);
		}
	}
   	else if (!strncasecmp(line, "NOTICE", 6))	/* Fix mIRC junk */
     	{
	   	char *p;
	   	while ((p = strstr(line, clients_ip_s)) != NULL) 
	     	{
		   	*p = '\0';
		   	strcpy(replace, p+strlen(clients_ip_s));
		   	strcat(line, visible_ip_o_s);
		   	strcat(line, replace);
	     	}
	}

   	/* Other spying could be done here .. */

#ifdef QUIZ_MODE
   	if (use_quiz_mode) {
	   	if ((isclient) && (use_quiz_mode <= QUIZ_FLUSH)) 
	     	{
		   	char cmd[32];
		   	char rest[512], out[512], *r;
		   	int what, retv, fuid;

		   	what = 0;
			if (sscanf(line, "%31s %511s", cmd, rest) == 2) 
		     	{
		   		if (!strcasecmp(cmd, "NICK"))	 what = 1;
		   	   else if (!strcasecmp(cmd, "USER"))	 what = 2;
		   	   else if (!strcasecmp(cmd, "PASS"))	 what = 3;
		   	   else if (!strcasecmp(cmd, "PRIVMSG")) what = 4;
			   else if (!strncasecmp(line, server_tag, 6)) 
			     					 what = 2;

			   	r = line+strlen(cmd)+1;
		   		if (*r == ':') r++;
				strncpy(rest, r, sizeof(rest)-1);
				rest[511] = '\0' ;
			   	if ((r = strchr(rest,'\n')) != NULL) *r = '\0';
			}
		   	else if	(!strncasecmp(line, server_tag, 6)) what = 2;

		   	r = rest;
		   	retv = -1;
		   	switch (what)
		     	{
					/* Users get fake nicks until after 
					** authentication (anti-nick-kill).
					*/
			 case 1:	quiz_delay_line(line, QUIZ_S);
			   		fuid = getpid() * visible_ip_o;
			   		sprintf(user_nick, "}%x", fuid);
			   		sprintf(line, "NICK :%s\n", user_nick);
			   		sprintf(out, ":%.32s %.256s", r, line);
					quiz_delay_line(out, QUIZ_C);
			   		quiz_greet();
			   		retv = 1;
			   		break;

					/* Allow the USER command.
			     		*/
			 case 2:	retv = 1;
			   		strcat(line,"#PiNG#\n");
					break;

				 	/* Check the users' responses.
					*/
			 case 4:	if ((r = strchr(rest,' ')) != NULL)
			     		{
					   	*r++ = '\0';
					   	if (*r == ':') r++;
					}
		   			if (strcasecmp(rest, QUIZ_NICK))
		     			{
					   	quiz_delay_line(line, QUIZ_S);
					   	break;
			    		}
			 case 3:	quiz_check_auth(r);
		   			break;
			 default:	quiz_delay_line(line, QUIZ_S);
			   		break;
			}
		   
			return(retv);
		}
	}
#endif
	return(1);
}


/* Mangle the given filename.  Returns 1 if the filename
** is okay, 0 if not.
*/ 
static int dcc_mangle_filename(char *filename) 
{
   	struct dcc_mangle_struct *dmp;

   	/* Check the compiled-in rules first:
	*/
	dmp = dcc_mangle;
  	while (use_dcc_mangling && 
	       (dmp->offered != NULL))
     	{
	   	if (!strcasecmp(filename, dmp->offered))
     		{
     			if (dmp->replace != NULL)
				strcpy(filename, dmp->replace);
			else
		     		return(0);
		}
   		dmp++;
	}

#ifdef TCP_WRAPPERS
	/* Check if the offered file is on the
	** local blacklist.
	*/
	if (use_tcp_wrappers)
     	{
		if (!hosts_ctl("tircproxy_dcc_files",
		   	       "", "", filename))
	     		return(0);
	}
#endif
	return(1);
}


/* Looks up valid port numbers for proxying DCC RESUME requests.
** Set resume to 0 on "DCC RESUME", 1 on "DCC ACCEPT".
*/
static int dcc_resume(int destport, int resume) 
{
   	int i;

   	for (i = 0; i < MAX_DCC_SESSIONS; i++) 
     	{
	   	if ((resume) && (dcc_proxied_port[i] == destport)) 
	     	{
		   	return(dcc_original_port[i]);
		}
	   	else if ((!resume) && (dcc_original_port[i] == destport)) 
	     	{
		   	return(dcc_proxied_port[i]);
		}
	}
	return (0);
}

 
/* This prepares to proxy a DCC session.  Ut returns a pointer
** to a string containing the "<ip> <port>" reply to send to the
** client.
*/
static char *proxy_dcc(int destaddr, int destport, int incoming)
{
   	static char				retvalue[128];
	int					listen_sock;
	int					sock;
   	ipaddr_t				vip;
	struct sockaddr_in			addr;
	struct sockaddr_in			to_addr;
	int					len;

	/* Mangle DCC offers below port 1024.
	*/
   	if (destport < 1024)
     	{
	   	strcpy(retvalue,"junk junk");
	   	return retvalue;
	}

#ifdef MIRC_DCC_KLUDGE
   	if (incoming && use_mirc_dcc_kludge) destaddr = ntohl(clients_ip);
#endif

#ifdef TCP_WRAPPERS
     	if (use_tcp_wrappers)
     	{
		char host_ip[64], host_name[64];
	   	struct sockaddr_in addr;
	   	int okay = 1;

   		/* Lookup an ASCII representation of the host's IP address,
		** and attempt to look up it's FQDN as well (DNS).
       		*/
		addr.sin_addr.s_addr = htonl(destaddr);
       		lookup_hostname(&addr, host_ip, sizeof(host_ip), 0);
       		lookup_hostname(&addr, host_name, sizeof(host_name), 1);

   		/*  Check if the calling host is allowed to initiate DCC.
		*/
	   	if (!hosts_ctl((incoming) ? "tircproxy_dcc_out" : 
			       		    "tircproxy_dcc_in", 
			       host_name, host_ip, 
			       (incoming) ? "" : user_name))
	     	{
	     		okay = 0;
		}

	   	if (clients_ip != htonl(destaddr))
	     	{
			addr.sin_addr.s_addr = clients_ip;
	       		lookup_hostname(&addr, host_ip, sizeof(host_ip), 0);
	       		lookup_hostname(&addr, host_name, sizeof(host_name), 1);

	   		/*  Check if the client is allowed to use DCC.
			*/   
	       		if (!hosts_ctl("tircproxy_dcc_in", 
				       host_name, host_ip, 
				       user_name))
		     	{
		     		okay = 0;
			}
		}

	   	if (!okay) 
	     	{
		   	debug_msg(0, LOG_INFO,
			       	"Dropped illegal DCC from: %.128s (client %.32s@%.96s)",
			       		host_ip, user_name,
			       		inet_ntoa(addr.sin_addr));
		   	strcpy(retvalue,"junk junk");
		   	return(retvalue);
		}
	}
#endif

   	/* Choose a DCC port at random, to make hijacking harder.
	** 	(re: BUGTRAQ, 1998, December 22 & 23)
	*/
	len = 10;
	while ((listen_sock = bind_to_port((incoming) ? visible_ip_o : visible_ip_i, 
					   ((rand() + getpid()) % PPOOL) + 1025, 
					   1, DEBUG_TRIVIA)) < 0) 
     	{
	   	if (--len < 1) {
		   	debug_msg(0, LOG_ERR, "Failed to bind to port, dropping DCC!");

		   	strcpy(retvalue, "ack, pthpht!");
		   	return(retvalue);
		}
	}

	/* call cleanup func on SIGCHLD 
	*/
	signal(SIGCHLD, chld_signal);

   	if (fork())
     	{
	   	int i,n;
	   
       		len = sizeof(to_addr);
		if (getsockname(listen_sock, (struct sockaddr *)&to_addr, &len) < 0)
		{
			debug_msg(0, LOG_ERR, "getsockname(): %.256s", strerror(errno));
		   	*retvalue = '\0';
			return(retvalue);
		}
	       	/* Close the socket as the child does the handling.
	       	*/
	       	close(listen_sock);

	   	vip = to_addr.sin_addr.s_addr;

	   	/* Record the port info for this connection.
		*/
	   	for (n = i = 0; i < MAX_DCC_SESSIONS; i++) 
	     		if (dcc_session_time[i] < dcc_session_time[n]) n = i;

	   	dcc_session_time[n] = time(NULL);
	   	dcc_original_port[n] = destport;
	   	dcc_proxied_port[n] = ntohs(to_addr.sin_port);

#ifdef HAVE_LINUX
	   	sprintf(retvalue,"%lu %u",
			(unsigned long int) ntohl(vip), ntohs(to_addr.sin_port));
#else
	   	sprintf(retvalue,"%u %u",
			(unsigned int) ntohl(vip), ntohs(to_addr.sin_port));
#endif
	   	return(retvalue);
 	}
   
   	/* Give people five minutes to accept the call..
	*/
	signal(SIGALRM, alarm_signal);
   	alarm(5*MINUTE);
   	strcpy(alarm_in,"proxy_dcc: accept");

   	/* No silly broadcasts shall mess up our DCC stuff!
	*/
       	signal(SIGHUP, SIG_IGN);
   
       	/* Accept an incoming connection.
       	*/
       	len = sizeof(addr);
       	if ((sock = accept(listen_sock, (struct sockaddr *)&addr, &len)) < 0)
	{
       		/* Connection resets are common enough to log them as debug only.
       		*/
       		debug_msg(0, (errno == ECONNRESET ? LOG_DEBUG : LOG_ERR), "accept(): %.256s", strerror(errno));
	   	exit (0);
       	}
   	close(listen_sock);
   
	/* Ok, here we go!
	*/
   	memset(&to_addr, '\0', sizeof(to_addr));
	to_addr.sin_family = AF_INET;
	to_addr.sin_port = htons(destport);
	to_addr.sin_addr.s_addr = htonl(destaddr);

   	/*  Make sure at least *one* end of the connection
	**  is the original client.
	*/
	if ((to_addr.sin_addr.s_addr != clients_ip) &&
	    (addr.sin_addr.s_addr != clients_ip))
     	{
	   	debug_msg(0, LOG_WARNING, "Abuse: illegal DCC request %.128s <-> %.128s !",
		       	       	inet_ntoa(to_addr.sin_addr),
		       		inet_ntoa(addr.sin_addr));
     		exit(0);
	}

 	debug_msg(0, LOG_DEBUG, 
	       	"Copied %d bytes, exiting (DCC).",
	   	copy_loop(sock, &addr, &to_addr, 0));

#ifdef CDIR_IDENT
   	if (use_cdir)
     		cleanup_identd();
#endif   
       	close(sock);
       	closelog();
     	exit(0);
}


/* Read username from disk or shared memory, if possible.
*/
static void get_user_name(struct sockaddr_in *addr)
{
   	int	tries = 0;
#ifdef USE_UDB
	struct udb_ip_user buf;
	
	/* People neither using the CDIR stuff nor running as root probably 
	 * couldn't care less about ident problems.
	 */
	if (!use_cdir && getuid()) tries = 10;

   	/* Be stubborn, to decrease the chance of race conditions.
	 */
	while (!udb_ip_get(&(addr->sin_addr), &buf))
	{
		if (tries > 3)
		{
			debug_msg(0, LOG_WARNING, 
					  "Address not in UDB table, ident response will be wrong!");
			return;
		}
		else
		{
			tries++;
			sleep(1);
		}
	}
	
#ifdef TIRC_DEBUG
	debug_msg(DEBUG_TRIVIA, LOG_DEBUG, "Got username: %.256s", buf.username);
#endif

	strncpy(user_name, buf.username, MAXLOGNAME);
   	user_name[MAXLOGNAME-1] = '\0';
	return;

#endif /* USE_UDB */
#ifdef IP_TO_UID_PREFIX /* not USE_UDB */
   	int	 fd;
   	char ipfile[PATH_MAX];
   	char *cp;

	/* People neither using the CDIR stuff nor running as root probably 
	 * couldn't care less about ident problems.
	 */
#ifdef CDIR_IDENT
	if (!use_cdir && getuid()) tries = 10;
#endif

	/* FIXME: dangerous sprintfs */
#ifdef CDIR
	if (use_cdir)
# ifdef HAVE_SNPRINTF
	  snprintf(ipfile, sizeof(ipfile),
# else
	  sprintf(ipfile, 
# endif
			  "%s%s-%s", CDIR, CDIR_MAP, 
			  inet_ntoa(addr->sin_addr));
    else
#endif
# ifdef HAVE_SNPRINTF
	  snprintf(ipfile, sizeof(ipfile),
# else
	  sprintf(ipfile, 
# endif
			  "%s%s", IP_TO_UID_PREFIX, 
			  inet_ntoa(addr->sin_addr));

   	/*  Repeat until we lose the ident race.. :-)
	 */
	while ((fd = open(ipfile,O_RDONLY)) == -1)
	{
		if (tries > 5) {
			debug_msg(0, LOG_WARNING, 
					  "No %.256s file found, ident response will be wrong!", 
					  ipfile);
		   	return;
		} 
		else 
		{
			sleep(1);
		}
	   	tries++;
	}

   	user_name[0] = user_name[MAXLOGNAME-1] = '\0';
   	read(fd, user_name, MAXLOGNAME-1);
   	close(fd);

   	if ((cp = strchr(user_name,'\n'))) *cp = '\0';
	return;

#endif /* IP_TO_UID_PREFIX */
}
	
/* Change uid & gid!
*/
static void change_uid(void)
{
#if (defined IP_TO_UID_PREFIX || defined USE_UDB)
   	struct passwd *pw;

   	/* We aren't running as root, so there's nothing more we can do..
	*/
   	if (getuid()) return;

	if ((pw = getpwnam(user_name)) == NULL)
	{
		debug_msg(0, LOG_WARNING, 
				  "Invalid user: %.32s, didn't changed UID/GID.", 
				  user_name);
# ifdef PARANOID
	   	if (use_paranoid) exit(0);
# endif
	 	return;
	}

 	/* Change ids..
	*/
   	setgid(pw->pw_gid);
   	setuid(pw->pw_uid);

	/* Start a new session and group.
	*/
	setsid();
# ifdef HAVE_LINUX
   	setpgrp();
# endif
#endif /* IP_TO_UID_PREFIX */
   	return;
}


/* Tell the ident server which user this connection really belongs to.
*/
#ifdef CDIR
static void tell_identd(struct sockaddr_in *us, struct sockaddr_in *them)
{
   	char fakeid[64], *u;
# ifndef USE_UDB
   	int fd;
   	*user_ident_file = '\0';
	
	sprintf(user_ident_file, 
			"%s%s:%d-%s:%d", CDIR, CDIR_IDENT,
			ntohs(us->sin_port),
			inet_ntoa(them->sin_addr),
			ntohs(them->sin_port));
	
   	if ((fd = creat(user_ident_file, S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)) == -1)
	{
		debug_msg(0, LOG_WARNING, 
				  "Unable to create %s, ident response will be wrong!", 
				  user_ident_file);
	   	*user_ident_file = '\0';
	   	return;
	}
# endif

	if ((use_anonymity) || (!*user_name))
	{
	   	int cip = ANON_USERID;
		
	   	sprintf(fakeid, "t%x", cip);
		debug_msg(0, LOG_INFO, "Anonymized connection as %s", fakeid);
		
		u = fakeid;
	}
	else
	  u = user_name;
	
# ifdef USE_UDB
	memset(&conn, 0, sizeof(conn));
	conn.from.sin_family = us->sin_family;
	conn.from.sin_addr = us->sin_addr;
	conn.from.sin_port = us->sin_port;
	conn.to.sin_family = them->sin_family;
	conn.to.sin_addr = them->sin_addr;	
	conn.to.sin_port = them->sin_port;

	udb_conn_record(&conn, u, "", UDB_OVERWRITE_LRU, UDB_NOW);
	
# else	
   	if (write(fd, u, strlen(u)) < 0)
	  debug_msg(0, LOG_WARNING, 
		       	"Unable to write to %s, ident response will be wrong!", 
		       	user_ident_file);

   	close(fd);
# endif
}

/* Cleanup ..
*/
static void cleanup_identd(void)
{
# ifdef USE_UDB
	udb_conn_delete(&conn);
# else	
   	if (*user_ident_file)
     		unlink(user_ident_file);
# endif
}
#endif /* CDIR */


/* Broadcast a message from sysadmin to proxy user.. It's the 
** sysadmin's problem to format the file so the client will understand
** the message!
*/
static void broadcast(int sock, const char *filename)
{
   	int		fd,i;
   	char 		buff[2048], *p, *np;

#ifdef HAVE_LINUX
   	/* man signal:
	** 
	** 	Unlike BSD systems, signals under Linux are reset to their
	** 	default values when raised.
	*/ 
       	signal(SIGHUP, hup_signal);
#endif
      	broadcast_flag = 0;

   	if ((fd = open(filename,O_RDONLY)) == -1) return;
  	debug_msg(0, LOG_DEBUG, "Sending broadcast from: %.256s",filename);

   	while ((i = read(fd, buff, 2047)) > 0) 
     	{
	   	buff[2047] = '\0';
	   	np = p = buff;

	   	while ((np = strstr(p, "$N$")) != NULL) 
	     	{
		   	if (write(sock, p, np-p) < 0) 
		     		debug_msg(0, LOG_WARNING, "Error writing to socket!");
		   	p = np+3;
		   	write(sock, user_nick, strlen(user_nick));
		}
	   	if (write(sock, p, (buff+i)-p) < 0) 
	     		debug_msg(0, LOG_WARNING, "Error writing to socket!");
	}
   	close(fd);
}


/* Catch alarm signals and exit.
*/
static void alarm_signal(int sig)
{
	debug_msg(0, LOG_DEBUG, "Timeout in %.256s, exiting.", alarm_in);
#ifdef CDIR_IDENT
   	if (use_cdir)
     		cleanup_identd();
#endif
	exit(1);
}


/* Catch HUP signals and set broadcast flag.
*/
static void hup_signal(int sig)
{
	debug_msg(0, LOG_DEBUG, "HUP signal caught - broadcasting!");
   	broadcast_flag = 1;
}


/* SIGCHLD handler (reap zombies)
*/
static void chld_signal (sig)
{
	int status;
	while (waitpid(-1,&status,WNOHANG) > 0);
#ifdef HAVE_LINUX

   	/* Reset the signal handler.
	*/
	signal(SIGCHLD,chld_signal);
#endif   
}

/* Debug message handler
*/
static void debug_msg(int lev, int pri, const char *format, ...) {
	va_list ap;
   	char msg[512];
   
   	if (lev > debug_level) return;

     	va_start(ap,format);
#if HAVE_VSNPRINTF
	vsnprintf(msg, 511, format, ap);
#else
# if HAVE_VPRINTF
#  warning Using vsprintf instead of vsnprintf!
	vsprintf(msg, format, ap);
# else
#  error I need vsprintf or vsnprintf!
# endif
#endif

   	if ((use_syslog) && (debug_level < DEBUG_NOFORK))
     		syslog(pri, "%s", msg);
	else 	fprintf(stderr,"[%d]\t%s\n", getpid(), msg);

   	va_end(ap);
}

/* ************************************************************************* */

#ifdef QUIZ_MODE

# define QSTR(b)	(b+sizeof(char *))
# define QPTR(b)	*((char **) b)

/* Save a line that was destined for output for later..
*/
int quiz_delayed = 0;
static void quiz_delay_line(char *line, char **first, char ***bl)
{
   	char *buf;
	int linelen;
	
	quiz_delayed += linelen = strlen(line);
   
	if (quiz_delayed > QUIZ_MEMORY_HOG) exit(1);

	if (*bl == NULL) *bl = first;
	
   	**bl = buf = malloc(sizeof(char *)+linelen+1);
	strcpy(QSTR(buf), line);

	QPTR(buf) = NULL;
	*bl = &QPTR(buf);

#ifdef TIRC_DEBUG
	debug_msg(DEBUG_TRIVIA, LOG_DEBUG, "Buffered: %.256s", line);
#endif
}

/* Print a line that was destined for output for later..
*/
static void quiz_dump_lines(int sock, char **first, char ***bl)
{
   	char *p;

	if (*first == NULL) return;

	do {
		while (write(sock, QSTR(*first), 
			      strlen(QSTR(*first))) < 0)
			if (errno != EINTR)
		{
#ifdef TIRC_DEBUG
			debug_msg(DEBUG_BASIC, LOG_WARNING, 
			       "write(%s) failed in quiz_dump_lines: %.128s",
			       strerror(errno));
#endif
			return;
		}
#ifdef TIRC_DEBUG
		debug_msg(DEBUG_TRIVIA, LOG_DEBUG, 
			  "Flushed: %.256s", QSTR(*first));
#endif
		p = QPTR(*first);
		free(*first);
		*first = p;
	} while (*first != NULL);

   	*bl = first;
}

/* Check the username%password supplied by the user.
*/
int quiz_passwd_checks = 0;
static void quiz_check_auth(char *pass)
{ 
	if (quiz_passwd_checks++ > 3) exit(1);

   	if (use_unix_passwd)
     	{
	  	char *u, *p, *n, salt[4];
	   	struct passwd *pw;

		if (((p = strchr(pass,'%')) != NULL) ||
		    ((p = strchr(pass,' ')) != NULL))
	     	{
		   	u = pass;
		   	*p++ = '\0';
		} 
	   	else if (*user_name)
	     	{
		   	u = user_name;
			p = pass;
	  	}
	   	else
	     	{
		   	quiz_msg(QUIZ_PASSWD_BAD);
		   	return;
	     	}

		if ((n = strchr(p,'\n')) != NULL) *n = '\0';

		if ((pw = getpwnam(u)) != NULL) 
	     	{
		       	salt[0] = (pw->pw_passwd)[0];
		       	salt[1] = (pw->pw_passwd)[1];
		   	salt[2] = '\0';
		   	if (!strcmp(pw->pw_passwd, crypt(p,salt)))
		     	{
				quiz_msg(QUIZ_PASSWD_OK);
			   	use_quiz_mode = QUIZ_FLUSH;
			   	bzero(p,strlen(p));
			   	return;
			}
		}

#ifdef TIRC_DEBUG
		debug_msg(DEBUG_FEATURES, LOG_AUTH, 
			  "Incorrect password given for %s", u);
#endif
	   	bzero(p,strlen(p));
	   	sleep(quiz_passwd_checks*2);
		quiz_msg(QUIZ_PASSWD_BAD);
	}
 	else /* use_unix_passwd */
     	{
		char *b, *p, buf[512], ok;

		strcpy(buf,quiz);
	   	b = p = buf;
	   	ok = 0;

		while ((p = strchr(p,':')) != NULL)
	     	{
		   	*p++ = '\0';
		   	if (!strcasecmp(pass,b)) ok = 1;
			b = p;
		}
	   	if (!strcasecmp(pass,b)) ok = 1;

		if (ok)
		{
			quiz_msg(QUIZ_PASSWD_OK);
			use_quiz_mode = QUIZ_FLUSH;
		}
	   	else
	     	{
#ifdef TIRC_DEBUG
			debug_msg(DEBUG_TRIVIA, LOG_DEBUG, 
				  "Incorrect quiz response: %s", pass);
#endif
			quiz_msg(QUIZ_ANSWER_BAD);
		}

     	} /* else */
}

/* Send the user a message..
*/
static void quiz_msg(char *message)
{
   	char out[512];
	
   	sprintf(out,":%.32s!irc@proxy PRIVMSG %.32s :%.256s\n", 
			QUIZ_NICK, user_nick, message);

   	quiz_delay_line(out, QUIZ_C);
}

/* Greet the user when he connects..
*/
static void quiz_greet(void)
{
   	FILE  *fd;
   	int   i, r;
   	char  question[512], buff[512], *p;

	if (use_unix_passwd) 
	{
		quiz_msg(QUIZ_PASSWD_WAIT);
	   	return;
	}
   	
   	/* Not using /etc/passwd, select a question from the quizfile */
	
   	srand(time((time_t) NULL));

   	if ((fd = fopen(quizfile,"r")) == NULL)
	{
	  	debug_msg(0, LOG_DEBUG, 
				  "Error (%.128s) reading quiz-file: %.128s", 
				  strerror(errno), quizfile);
	   	quiz_msg("Error opening quiz file.  Notify the proxy administrator.");
	   	return;
	}
	
   	r = 0;
   	while (fgets(buff, 512, fd) != NULL) 
	  if (*buff == '!') 
		quiz_msg(buff+1);
	  else 
	    if (*buff != '#') 
	{
		i = rand();
		if ((i > r) || (!r)) 
		{
			r = i;
			strcpy(question,buff);
		}
	}
	
	fclose(fd);

	if ((p = strchr(question, ':')) != NULL)
	{
	   	*p++ = '\0';
	   	quiz_msg(question);
	   	strcpy(quiz,p);
	   	if ((p = strchr(quiz, '\n')) != NULL) *p = '\0';
	} 
   	else
	{
		debug_msg(0, LOG_DEBUG, "Invalid quiz: %.256s", question); 
		quiz_msg("Invalid quiz in file.  Notify the proxy administrator.");
 	}
}
#endif
		   

			   


syntax highlighted by Code2HTML, v. 0.9.1