/*
 * ircII: a new irc client.  I like it.  I hope you will too!
 *
 * Written By Michael Sandrof
 * 
 * Copyright (c) 1990 Michael Sandrof.
 * Copyright (c) 1991, 1992 Troy Rollo.
 * Copyright (c) 1992-2006 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.
 */

#define IRCII_VERSION	"20060725"	/* YYYYMMDD */

#include "irc.h"
IRCII_RCSID("@(#)$eterna: irc.c,v 1.298 2006/07/25 11:14:19 mrg Exp $");

#include <sys/stat.h>
#ifndef __MSDOS__
#include <pwd.h>
#endif /* __MSDOS__ */

#ifdef ISC22
# include <sys/bsdtypes.h>
#endif /* ISC22 */

#ifdef DO_USER2
# include <setjmp.h>
#endif /* DO_USER2 */

#include "status.h"
#include "dcc.h"
#include "names.h"
#include "vars.h"
#include "input.h"
#include "alias.h"
#include "output.h"
#include "ircterm.h"
#include "exec.h"
#include "screen.h"
#include "log.h"
#include "server.h"
#include "hook.h"
#include "keys.h"
#include "ircaux.h"
#include "edit.h"
#include "window.h"
#include "history.h"
#include "exec.h"
#include "notify.h"
#include "mail.h"
#include "debug.h"
#include "newio.h"
#include "ctcp.h"
#include "parse.h"
#include "strsep.h"

int	irc_port = IRC_PORT,			/* port of ircd */
	icb_port = ICB_PORT,			/* port of icbd */
	send_text_flag = -1,			/* used in the send_text()
						 * routine */
	use_flow_control = USE_FLOW_CONTROL,	/* true: ^Q/^S used for flow
						 * cntl */
	irc_io_loop = 1,			/* see irc_io below */
	break_io_processing = 0,		/* more forceful than
						 * irc_io_loop */
	current_numeric,			/* this is negative of the
						 * current numeric! */
	dumb = 0,				/* if true, IRCII is put in
						 * "dumb" mode */
	no_fork = 0,				/* if true, IRCII won't with
						 * -b or -e */
	use_input = 1,				/* if 0, stdin is never
						 * checked */
	waiting = 0,				/* used by /WAIT command */
	who_mask = 0,				/* keeps track of which /who
						 * switchs are set */
	cur_signal = -1,			/* head of below */
	occurred_signals[16];			/* got signal which may
						   require /ON action */

int	client_default_icb = 0;		/* default to irc server connections */

u_char	*zero	= UP("0");
u_char	*one	= UP("1");

u_char	oper_command;		/* true just after an oper() command is
				 * given.  Used to tell the difference
				 * between an incorrect password generated by
				 * an oper() command and one generated when
				 * connecting to a new server */
extern	u_char	*last_away_nick;

u_char	*invite_channel,		/* last channel of an INVITE */
	*ircrc_file,			/* full path .ircrc file */
	*ircquick_file,			/* full path .ircquick file */
	*my_path,			/* path to users home dir */
	*irc_path,			/* paths used by /load */
	*irc_lib,			/* path to the ircII library */
	*nickname,			/* users nickname */
	FAR hostname[NAME_LEN + 1],	/* name of current host */
	FAR realname[REALNAME_LEN + 1],	/* real name of user */
	FAR username[NAME_LEN + 1],	/* usernameof user */
	*source_host,			/* specify a specific source host
					 * for multi-homed machines */
	*dcc_source_host,		/* source host to use for DCC */
	*send_umode,			/* sent umode */
	*args_str,			/* list of command line args */
	empty_string[] = "",		/* just an empty string */
	*who_name,			/* extra /who switch info */
	*who_file,			/* extra /who switch info */
	*who_server,			/* extra /who switch info */
	*who_host,			/* extra /who switch info */
	*who_nick,			/* extra /who switch info */
	*who_real,			/* extra /who switch info */
	*cannot_open,			/* extra /who switch info */
	*cut_buffer,			/* global cut_buffer */
	*last_notify_nick;		/* last detected nickname */

int	away_set;			/* set if there is an away
					 * message anywhere */
int	qflag;				/* set if we ignore .ircrc */
int	bflag;				/* set if we load .ircrc before connecting */
int	tflag = 1;			/* don't use termcap ti/te sequences */
time_t	idle_time;			/* last time the user hit a key */
time_t  start_time;			/* epoch time we started */

static	RETSIGTYPE	cntl_c(int);
static	RETSIGTYPE	sig_user1(int);
static	RETSIGTYPE	sig_refresh_screen(int);
static	RETSIGTYPE	sig_on(int);
static	void		real_sig_user1(void);
static	int		do_sig_user1;
#ifdef	DO_USER2
static	RETSIGTYPE	sig_user2(int) ;
#endif /* DO_USER2 */
#ifdef	MUNIX
static	RETSIGTYPE	cntl_y(int);
#endif /* MUNIX */
#ifdef CORECATCH
static	RETSIGTYPE	coredump(int) ;
#endif /* CORECATCH */
static	int	irc_do_a_screen(Screen *, fd_set *, int);
static	void	TimerTimeout(struct timeval *tv);
static	void	quit_response(u_char *, u_char *);
static	void	show_version(void);
static	u_char	*get_arg(u_char *, u_char *, int *);
static	u_char	*parse_args(u_char **, int);

static	int	cntl_c_hit = 0;

#ifdef DO_USER2
	jmp_buf	outta_here;
#endif /* DO_USER2 */

	u_char	irc_version[] = IRCII_VERSION;

static	char	FAR switch_help[] =
"Usage: irc [switches] [nickname] [server list] \n\
  The [nickname] can be at most 9 characters long on some server\n\
  The [server list] is a whitespace separated list of server names\n\
  The [switches] may be any or all of the following:\n\
   -c <channel>\tjoins <channel> on startup\n\
   -p <port>\tdefault IRC server connection port (usually 6667)\n\
   -P <port>\tdefault ICB server connection port (usually 7326)\n\
   -f\t\tyour terminal uses flow control (^S/^Q), so IRCII shouldn't\n\
   -F\t\tyour terminal doesn't use flow control (default)\n\
   -r\t\treverse terminal colours (only if colours are activated)\n\
   -s\t\tdon't use separate server processes (ircio)\n\
   -S\t\tuse separate server processes (ircio)\n\
   -h <host>\tsource host, for multihomed machines\n\
   -H <host>\toriginating address for dcc requests (to work behind NATs etc)\n\
   -d\t\truns IRCII in \"dumb\" terminal mode\n\
   -q\t\tdoes not load .ircrc nor .ircquick\n\
   -a\t\tadds default servers and command line servers to server list\n\
   -b\t\tload .ircrc before connecting to a server\n\
   -l <file>\tloads <file> in place of your .ircrc\n\
   -I <file>\tloads <file> in place of your .ircquick\n\
   -t\t\tdo not use termcap ti and te sequences at startup\n\
   -T\t\tuse termcap ti and te sequences at startup (default)\n\
   -icb\t\tuse ICB connections by default\n\
   -irc\t\tuse IRC connections by default\n\
       icb [same switches]  (default to -icb)\n";

#ifdef _Windows
extern	unsigned int	hwndMain;
#endif /* _Windows */

/* irc_exit: cleans up and leaves */
RETSIGTYPE
irc_exit()
{
	do_hook(EXIT_LIST, "Exiting");
	close_server(-1, empty_string);
	logger(0);
	set_history_file((u_char *) 0);
#ifndef _Windows
	clean_up_processes();
#endif /* _Windows */
	if (!dumb)
	{
		cursor_to_input();	/* Needed so that ircII doesn't gobble
					 * the last line of the kill. */
		term_cr();
		if (term_clear_to_eol())
			term_space_erase(0);
		term_reset();
	}
#ifdef _Windows
   DestroyWindow(hwndMain);
#else
	exit(0);
#endif /* _Windows */
}

#ifdef CORECATCH
/* sigsegv: something to handle segfaults in a nice way */
/* this needs to be changed to *NOT* use printf(). */
RETSIGTYPE
coredump(signo)
	int	signo;
{
	printf("IRCII has been terminated by a SIG%s\n\r", signals[signo]);
	printf("Please inform the ircii bugs list (ircii-bugs@eterna.com.au) of this\n\r");
	printf("with as much detail as possible about what you were doing when it happened.\n\r");
	printf("Please include the version of IRCII (%s) and type of system in the report.\n\r", irc_version);
	fflush(stdout);
	irc_exit();
}
#endif /* CORECATCH */

/*
 * quit_response: Used by irc_io when called from irc_quit to see if we got
 * the right response to our question.  If the response was affirmative, the
 * user gets booted from irc.  Otherwise, life goes on. 
 */
static	void
quit_response(dummy, ptr)
	u_char	*dummy;
	u_char	*ptr;
{
	size_t	len;
	int	old_irc_io_loop;

	old_irc_io_loop = irc_io_loop;
	irc_io_loop = 0;
	if ((len = my_strlen(ptr)) != 0)
	{
		if (!my_strnicmp(ptr, UP("yes"), len))
		{
			send_to_server("QUIT");
			irc_exit();
		}
	}
	irc_io_loop = old_irc_io_loop;
}

/* irc_quit: prompts the user if they wish to exit, then does the right thing */
void
irc_quit(key, ptr)
	u_int	key;
	u_char	*ptr;
{
	static	int in_it = 0;

	if (in_it)
		return;
	in_it = 1;
	add_wait_prompt(UP("Do you really want to quit? "), quit_response,
		empty_string, WAIT_PROMPT_LINE);
	in_it = 0;
}

void
on_signal_occurred(signo)
	int signo;
{
	if (cur_signal > 16)
		return;
	occurred_signals[++cur_signal] = signo;
}

/*
 * cntl_c: emergency exit.... if somehow everything else freezes up, hitting
 * ^C five times should kill the program. 
 */
static	RETSIGTYPE
cntl_c(signo)
	int signo;
{
#ifdef SYSVSIGNALS
	(void) MY_SIGNAL(SIGINT, (sigfunc *) cntl_c, 0);
#endif /* SYSVSIGNALS */
	on_signal_occurred(signo);

	if (cntl_c_hit++ >= 4)
		irc_exit();
}

static RETSIGTYPE
sig_on(signo)
	int signo;
{
#ifdef SYSVSIGNALS
	(void) sigfunc *(signo, (sigfunc *) sig_on, 0);
#endif /* SYSVSIGNALS */
	on_signal_occurred(signo);
}

static RETSIGTYPE
sig_user1(signo)
	int signo;
{
#ifdef SYSVSIGNALS
	(void) sigfunc *(SIGUSR1, (sigfunc *) sig_user1, 0);
#endif /* SYSVSIGNALS */
	do_sig_user1++;
	on_signal_occurred(signo);
}

static void
real_sig_user1()
{
	say("Got SIGUSR1, closing DCC connections and EXECed processes");
	close_all_dcc();
#ifndef _Windows
	close_all_exec();
#endif /* _Windows */
}

#ifdef DO_USER2
static RETSIGTYPE
sig_user2(signo)
	int signo;
{
#ifdef SYSVSIGNALS
	(void) MY_SIGNAL(SIGUSR2, (sigfunc *) sig_user2, 0);
#endif /* SYSVSIGNALS */
	on_signal_occurred(signo);
	say("Got SIGUSR2, jumping to normal loop");	/* unsafe */
	longjmp(outta_here);
}
#endif 

#ifdef MUNIX
/* A characteristic of PCS MUNIX - Ctrl-Y produces a SIGQUIT */
static RETSIGTYPE
cntl_y(signo)
	int signo;
{
	(void) MY_SIGNAL(SIGQUIT, (sigfunc *) cntl_y, 0);
	on_signal_occurred(signo);
	edit_char(25); /* Ctrl-Y */	/* unsafe */
}
#endif /* MINUX */

static RETSIGTYPE
sig_refresh_screen(signo)
	int signo;
{
	on_signal_occurred(signo);
	do_refresh_screen++;
}

/* shows the version of irc */
static	void
show_version()
{
	printf("ircII version %s\n\r", irc_version);
	exit (0);
}

/* get_arg: used by parse_args() to get an argument after a switch */
static	u_char	*
get_arg(arg, next, ac)
	u_char	*arg;
	u_char	*next;
	int	*ac;
{
	(*ac)++;
	if (*arg)
		return (arg);
	else
	{
		if (next)
			return (next);
		fprintf(stderr, "irc: missing parameter\n");
		exit(1);
		return (0); /* cleans up a warning */
	}
}

/*
 * parse_args: parse command line arguments for irc, and sets all initial
 * flags, etc. 
 */
static	u_char	*
parse_args(argv, argc)
	u_char	**argv;
	int	argc;
{
	u_char	*arg,
		*ptr;
	int	ac;
	int	add_servers = 0;
	u_char	*channel = (u_char *) 0;
	struct	passwd	*entry;
	int	minus_minus = 0;
#ifdef _Windows
	u_char	buffer[BIG_BUFFER_SIZE];
#endif

	if (my_strncmp(argv[0], "icb", 3) == 0 || strstr(CP(argv[0]), "/icb") != 0)
		client_default_icb = 1;
	*realname = '\0';
	ac = 1;
	malloc_strcpy(&args_str, argv[0]);
	malloc_strcat(&args_str, UP(" "));
	while ((arg = argv[ac++]) != (u_char *) 0)
	{
		malloc_strcat(&args_str, argv[ac-1]);
		malloc_strcat(&args_str, UP(" "));
		if ((*arg == '-') != '\0')
		{
			++arg;
			while (*arg)
			{
				switch (*(arg++))
				{
				case 'v':
					show_version();
					break;
				case 'b':
					if (qflag)
					{
						fprintf(stderr, "Can not use -b with -q\n");
						exit(1);
					}
					bflag = 1;
					break;
				case 'c':
					malloc_strcpy(&channel, get_arg(arg,
							argv[ac], &ac));
					break;
				case 'p':
					irc_port = my_atoi(get_arg(arg, argv[ac],
							&ac));
					break;
				case 'P':
					icb_port = my_atoi(get_arg(arg, argv[ac],
							&ac));
					break;
				case 'f':
					use_flow_control = 1;
					break;
				case 'F':
					use_flow_control = 0;
					break;
				case 'd':
					dumb = 1;
					break;
				case 'H':
					malloc_strcpy(&dcc_source_host, get_arg(arg,
							argv[ac], &ac));
					break;
				case 'h':
					malloc_strcpy(&source_host, get_arg(arg,
							argv[ac], &ac));
					break;
				case 'i':
					if (arg[0] == 'r' && arg[1] == 'c')
						client_default_icb = 0;
					else
					if (arg[0] == 'c' && arg[1] == 'b')
						client_default_icb = 1;
					else
					{
						printf("irc: invalid -i option; use -irc or -icb");
						exit(-1);
					}
					arg += 2;
					break;
#ifdef DEBUG
				case 'D':
					setdlevel(my_atoi(get_arg(arg, argv[ac],
						&ac)));
					break;
				case 'o':
					{
						FILE	*fp;
						u_char	*file = get_arg(arg, argv[ac], &ac);

						if (!file)
						{
							printf("irc: need filename for -o\n");
							exit(-1);
						}
						fp = freopen(CP(file), "w", stderr);
						if (!fp)
						{
							printf("irc: can not open %s: %s\n", file, errno ? "" : strerror(errno));
							exit(-1);
						}
					}
					break;
#endif /* DEBUG */
				case 'l':
					malloc_strcpy(&ircrc_file, get_arg(arg, argv[ac], &ac));
					break;
				case 'I':
					malloc_strcpy(&ircquick_file,get_arg(arg, argv[ac], &ac));
					break;
				case 'a':
					add_servers = 1;
					break;
				case 's':
					using_server_process = 0;
					break;
				case 'S':
					using_server_process = 1;
					break;
				case 't':
					tflag = 1;
					break;
				case 'T':
					tflag = 0;
					break;
				case 'q':
					if (bflag)
					{
						fprintf(stderr, "Can not use -q with -b\n");
						exit(1);
					}
					qflag = 1;
					break;
				case 'r':
					{
						int oldfg = get_int_var(FOREGROUND_COLOUR_VAR);
						set_int_var(FOREGROUND_COLOUR_VAR, get_int_var(BACKGROUND_COLOUR_VAR));
						set_int_var(BACKGROUND_COLOUR_VAR, oldfg);
					}
					break;
				case '-':
					if (argv[ac])
					{
						int first = 1;

						while ((arg = argv[ac++]) != NULL)
						{
							malloc_strcat(&command_line, arg);
							if (first)
								first = 0;
							else
								malloc_strcat(&command_line, UP(" "));
						}
					}
					minus_minus = 1;
					break;
				default:
					fputs(switch_help, stderr);
					exit(1);
				}
				if (minus_minus)
					break;
			}
		}
		else
		{
			if (nickname && *nickname)
				build_server_list(arg);
			else
				malloc_strcpy(&nickname, arg);
		}
		if (minus_minus)
			break;
	}
	if ((u_char *) 0 != (ptr = my_getenv("IRCLIB")))
	{
		malloc_strcpy(&irc_lib, ptr);
		malloc_strcat(&irc_lib, UP("/"));
	}
	else
		malloc_strcpy(&irc_lib, UP(IRCLIB));

	/* -h overrides environment variable */
	if ((u_char *) 0 == source_host && (u_char *) 0 != (ptr = my_getenv("IRCHOST")))
		malloc_strcpy(&source_host, ptr);

	if ((u_char *) 0 == ircrc_file && (u_char *) 0 != (ptr = my_getenv("IRCRC")))
		malloc_strcpy(&ircrc_file, ptr);

	if ((u_char *) 0 == ircquick_file && (u_char *) 0 != (ptr = my_getenv("IRCQUICK")))
		malloc_strcpy(&ircquick_file, ptr);

	if ((nickname == 0 || *nickname == '\0') && (u_char *) 0 != (ptr = my_getenv("IRCNICK")))
		malloc_strcpy(&nickname, ptr);

	if ((u_char *) 0 != (ptr = my_getenv("IRCUMODE")))
		malloc_strcpy(&send_umode, ptr);

	if ((u_char *) 0 != (ptr = my_getenv("IRCNAME")))
		my_strmcpy(realname, ptr, REALNAME_LEN);

	if ((u_char *) 0 != (ptr = my_getenv("IRCPATH")))
		malloc_strcpy(&irc_path, ptr);
	else
	{
#ifdef IRCPATH
		malloc_strcpy(&irc_path, UP(IRCPATH));
#else
#ifdef __MSDOS__
		malloc_strcpy(&irc_path, ".:~/irc:");
#else
		malloc_strcpy(&irc_path, ".:~/.irc:");
#endif /* __MSDOS__ */
		malloc_strcat(&irc_path, irc_lib);
		malloc_strcat(&irc_path, "script");
#endif /* IRCPATH */
	}

	set_string_var(LOAD_PATH_VAR, irc_path);
	new_free(&irc_path);
	if ((u_char *) 0 != (ptr = my_getenv("IRCSERVER")))
		build_server_list(ptr);
	if (0 == number_of_servers || add_servers)
	{
#ifdef SERVERS_FILE
		if (read_server_file() || (number_of_servers == 0))
#endif /* SERVERS_FILE */
		{
			u_char *s = (u_char *) 0;

#ifdef _Windows
			GetProfileString("IRC", "Server",
						"Choose.File->Setup.From.Menu",
						buffer, sizeof(buffer));
			malloc_strcpy(&s, buffer);
#else
			malloc_strcpy(&s, UP(DEFAULT_SERVER));
#endif /* _Windows */
			build_server_list(s);
			new_free(&s);
		}
	}
#ifdef _Windows
	if (nickname == 0 || !*nickname)
	{
		GetProfileString("IRC", "Nick", "ircuser", buffer, sizeof buffer);
		malloc_strcpy(&nickname, buffer);
	}
	GetProfileString("IRC", "UserName", "ircuser", username, NAME_LEN + 1);
	GetProfileString("IRC", "RealName", "ircuser", realname, REALNAME_LEN + 1);
	GetProfileString("IRC", "StartDir", get_path(4), buffer, sizeof buffer);
	malloc_strcpy(&my_path, buffer);
#else /* _Windows */
	if ((struct passwd *) 0 != (entry = getpwuid(getuid())))
	{
		if ((*realname == '\0') && entry->pw_gecos && *(entry->pw_gecos))
		{
#ifdef GECOS_DELIMITER
			if ((ptr = my_index(entry->pw_gecos, GECOS_DELIMITER)) 
					!= NULL)
				*ptr = '\0';
#endif /* GECOS_DELIMITER */
			if ((ptr = my_index(entry->pw_gecos, '&')) == NULL)
				my_strmcpy(realname, entry->pw_gecos, REALNAME_LEN);
			else {
				size_t len = ptr - (u_char *) entry->pw_gecos;

				if (len < REALNAME_LEN && *(entry->pw_name)) {
					u_char *q = realname + len;

					my_strmcpy(realname, entry->pw_gecos, len);
					my_strmcat(realname, entry->pw_name, REALNAME_LEN);
					my_strmcat(realname, ptr + 1, REALNAME_LEN);
					if (islower(*q) && (q == realname || isspace(*(q - 1))))
						*q = toupper(*q);
				} else
					my_strmcpy(realname, entry->pw_gecos, REALNAME_LEN);
			}
		}
		if (entry->pw_name && *(entry->pw_name))
			my_strmcpy(username, entry->pw_name, NAME_LEN);

		if (entry->pw_dir && *(entry->pw_dir))
			malloc_strcpy(&my_path, UP(entry->pw_dir));
	}
#endif /* _Windows */
	if ((u_char *) 0 != (ptr = my_getenv("HOME")))
		malloc_strcpy(&my_path, ptr);
	else if (*my_path == '\0')
		malloc_strcpy(&my_path, UP("/"));

	if ('\0' == *realname)
		my_strmcpy(realname, "*Unknown*", REALNAME_LEN);

	if ('\0' == *username)
	{
		if ((ptr = my_getenv("USER")) != NULL)
			my_strmcpy(username, ptr, NAME_LEN);
		else
			my_strmcpy(username, "Unknown", NAME_LEN);
	}
	if ((u_char *) 0 != (ptr = my_getenv("IRCUSER")))
		my_strmcpy(username, ptr, REALNAME_LEN);

	if (nickname == 0 || *nickname == '\0')
		malloc_strcpy(&nickname, username);
	if ((u_char *) 0 == ircrc_file)
	{
		ircrc_file = (u_char *) new_malloc(my_strlen(my_path) +
			my_strlen(IRCRC_NAME) + 1);
		my_strcpy(ircrc_file, my_path);
		my_strcat(ircrc_file, IRCRC_NAME);
	}
	if ((u_char *) 0 == ircquick_file)
	{
		ircquick_file = (u_char *) new_malloc(my_strlen(my_path) +
			my_strlen(IRCQUICK_NAME) + 1);
		my_strcpy(ircquick_file, my_path);
		my_strcat(ircquick_file, IRCQUICK_NAME);
	}
	return (channel);
}

/*
 * TimerTimeout:  Called from irc_io to help create the timeout
 * part of the call to select.
 */
static void
TimerTimeout(tv)
	struct timeval *tv;
{
	struct timeval current;

	tv->tv_usec =0;
	tv->tv_sec  =0;
	
	if (!PendingTimers)
	{
		tv->tv_sec = 70; /* Just larger than the maximum of 60 */
		return;
	}
	gettimeofday(&current, NULL);
	
	if (PendingTimers->time < current.tv_sec ||
	    (PendingTimers->time == current.tv_sec &&
	    PendingTimers->microseconds < current.tv_usec))
	{
		/* No time to lose, the event is now or was */
		return;
	}
	
	tv->tv_sec = PendingTimers->time - current.tv_sec;
	if (PendingTimers->microseconds >= current.tv_usec)
		tv->tv_usec = PendingTimers->microseconds - current.tv_usec;
	else
	{
		tv->tv_usec = current.tv_usec - PendingTimers->microseconds;
		tv->tv_sec -= 1;
	}
}

/*
 * generally processes one screen's input.  this used to be part of irc_io()
 * but has been split out because that function was too large and too nested.
 * it returns 0 if IO processing should continue and 1 if it should stop.
 */
static	int
irc_do_a_screen(screen, rd, one_key) 
	Screen *screen;
	fd_set *rd;
	int one_key;
{
	if (!screen->alive)
		return 0;
	set_current_screen(screen);
	if (!is_main_screen(screen) &&
	    FD_ISSET(screen->wservin, rd))
		screen_wserv_message(screen);
	if (FD_ISSET(screen->fdin, rd))
	{
		/* buffer much bigger than IRCD_BUFFER_SIZE */
		u_char	lbuf[BIG_BUFFER_SIZE + 1];

		/*
		 * This section of code handles all in put from the terminal(s)
		 * connected to ircII.  Perhaps the idle time *shouldn't* be
		 * reset unless its not a screen-fd that was closed..
		 */
		idle_time = time(0);
		if (dumb)
		{
			int     old_timeout;

			old_timeout = dgets_timeout(1);
			if (dgets(lbuf, INPUT_BUFFER_SIZE,
					screen->fdin, (u_char *) 0))
			{
				(void) dgets_timeout(old_timeout);
				if (one_key)
				{
					irc_io_loop = 0;
					return 1;
				}
				*(lbuf + my_strlen(lbuf) - 1) = '\0';
				if (get_int_var(INPUT_ALIASES_VAR))	
					parse_line(NULL, lbuf,
					    empty_string, 1, 0, 0);
				else
					parse_line(NULL, lbuf,
					    NULL, 1, 0, 0);
			}
			else
			{
				say("IRCII exiting on EOF from stdin");
				irc_exit();
			}
		}
		else
		{
			int server;
			u_char	loc_buffer[BIG_BUFFER_SIZE + 1];
			int	n, i;

			server = from_server;
			from_server = get_window_server(0);
			last_input_screen = screen;
			if (one_key)
			{
				if (read(screen->fdin, lbuf, 1))
				{
					irc_io_loop = 0;
					return 1;
				}
			/*
			 * Following Fizzy's remark below, if we 
			 * don't use window create, we can't kill
			 * then, can we?  --FlashMan, October 1994
			 */
#ifdef WINDOW_CREATE
				else
				{
#ifndef _Windows
					if (!is_main_screen(screen))
						kill_screen(screen);
					else
#endif /* _Windows */
						irc_exit();
				}
#endif /* WINDOW_CREATE */
			}
			else if ((n = read(screen->fdin, loc_buffer,
					BIG_BUFFER_SIZE)) != 0)
				for (i = 0; i < n; i++)
					edit_char((u_int)loc_buffer[i]);
				/*
				 * if the current screen isn't the main  screen,
				 * then the socket to the current screen must have
				 * closed, so we call kill_screen() to handle 
				 * this - phone, jan 1993.
				 * but not when we arent running windows - Fizzy, may 1993
				 * if it is the main screen we got an EOF on, we exit..
				 * closed tty -> chew cpu -> bad .. -phone, july 1993.
				 */
#ifdef WINDOW_CREATE
			else
			{
				if (!is_main_screen(screen))
					kill_screen(screen);
				else
					irc_exit();
			}
#endif /* WINDOW_CREATE */
			cntl_c_hit = 0;
			from_server = server;
		}
	}
	return 0;
}

/*
 * irc_io: the main irc input/output loop.   Handles all io from keyboard,
 * server, exec'd processes, etc.  If a prompt is specified, it is displayed
 * in the input line and cannot be backspaced over, etc. The func is a
 * function which will take the place of the SEND_LINE function (which is
 * what happens when you hit return at the end of a line). This function must
 * decide if it's ok to exit based on anything you really want.  It can then
 * set the global irc_io_loop to false to cause irc_io to exit. 
 */
int
irc_io(prompt, func, my_use_input, loop)
	u_char	*prompt;
	void	(*func)(u_int, u_char *);
	int	my_use_input;
	int	loop;
{
	static	int	level = 0;
	fd_set	rd,
		wd;
	struct	timeval cursor_timeout,
		clock_timeout,
		right_away,
		timer,
		*timeptr;
	int	hold_over;
	int	old_loop;
	u_char	*last_input = NULL;
	u_char	*last_prompt = NULL;
	void	(*last_func)(u_int, u_char *);
	int	one_key = 0;
	Screen	*screen,
		*old_current_screen;
	int	i;

	last_func = get_send_line();
	if (my_use_input == -1)
		one_key = 1, prompt = NULL;
	/* time before cursor jumps from display area to input line */
	cursor_timeout.tv_usec = 0L;
	cursor_timeout.tv_sec = 1L;

	/* time delay for updating of internal clock */
	clock_timeout.tv_usec = 0L;
	clock_timeout.tv_sec = 30L;

	right_away.tv_usec = 0L;
	right_away.tv_sec = 0L;

	old_loop = irc_io_loop;
	irc_io_loop = loop;

			/*
			 * irc_io has been recursive to date.
			 * with multiple xterms and screen
			 * windows, this has to change
			 */
	if (level++ > 5)
	{
		level--;
		irc_io_loop = old_loop;
		return (1);
	}
	if (!dumb)
	{
		if (my_use_input)
		{
			malloc_strcpy(&last_input, get_input());
			set_input(empty_string);
			last_func = get_send_line();
			change_send_line(func);
		}
		if (prompt)
		{
			malloc_strcpy(&last_prompt, get_input_prompt());
			set_input_prompt(prompt);
		}
	}
	/*
	 * Here we work out if this has been called recursively or
	 * not..  and if not so.. -phone
	 */

#if defined(DEBUG) || defined(DO_USER2)
	if (level != 1)
	{
#ifdef DEBUG
		yell("--- Recursive call to irc_io() - careful");
#endif /* DEBUG */
	}
	else
	{
#ifdef DO_USER2
		if (setjmp(outta_here))
			yell("*** Got SIGUSR2, Aborting");
#endif /* DO_USER2 */
	}
#endif /* DEBUG || DO_USER2 */

	timeptr = &clock_timeout;
	do
	{
		break_io_processing = 0;
		ctcp_was_crypted = 0;
		FD_ZERO(&rd);
		FD_ZERO(&wd);
		set_process_bits(&rd);
		set_server_bits(&rd, &wd);
		if (my_use_input)
			for (screen = screen_list; screen; screen = screen->next)
				if (screen->alive)
				{
					FD_SET(screen->fdin, &rd);
					if (!is_main_screen(screen))
						FD_SET(screen->wservin, &rd);
				}
		set_dcc_bits(&rd, &wd);
		if (term_reset_flag)
		{
			refresh_screen(0, (u_char *) 0);
			term_reset_flag = 0;
		}
		TimerTimeout(&timer);
		if (timer.tv_sec <= timeptr->tv_sec)
			timeptr = &timer;
		if ((hold_over = unhold_windows()) != 0)
			timeptr = &right_away;
		Debug((7, "irc_io: selecting with %ld:%ld timeout", timeptr->tv_sec,
			timeptr->tv_usec));
		switch (new_select(&rd, &wd, timeptr))
		{
		case 0:
		case -1:
	/*
	 * yay for the QNX socket manager... drift here, drift there, oops,
	 * i fell down a hole..
	 */
#ifdef __QNX__
			if (errno == EBADF || errno == ESRCH)
				irc_io_loop = 0;
#endif /* __QNX__ */
			if (cntl_c_hit)
			{
				if (one_key)
				{
					irc_io_loop = 0;
					break;
				}
				edit_char((u_int)'\003');
				cntl_c_hit = 0;
			}
			if (do_status_alarmed)
			{
				real_status_alarmed();
				do_status_alarmed = 0;
			}
			if (do_refresh_screen)
			{
				refresh_screen(0, (u_char *) 0);
				do_refresh_screen = 0;
			}
			if (do_sig_user1)
			{
				real_sig_user1();
				do_sig_user1 = 0;
			}
			for (i = 0; i <= cur_signal; i++)
				if (occurred_signals[i] != 0)
					do_hook(OS_SIGNAL_LIST, "%s",
					    signals[occurred_signals[i]]);
			cur_signal = -1;
			if (!hold_over)
				cursor_to_input();
			break;
		default:
			if (term_reset_flag)
			{
				refresh_screen(0, (u_char *) 0);
				term_reset_flag = 0;
			}
			old_current_screen = current_screen;
			set_current_screen(last_input_screen);
			if (!break_io_processing)
				dcc_check(&rd, &wd);
			if (!break_io_processing)
				do_server(&rd, &wd);
			set_current_screen(old_current_screen);
			for (screen = screen_list; screen &&
					!break_io_processing; screen = screen->next)
				if (irc_do_a_screen(screen, &rd, one_key))
					break;
			set_current_screen(old_current_screen);
			if (!break_io_processing)
				do_processes(&rd);
			break;
		}
		execute_timer();
		check_process_limits();
		while (check_wait_status(-1) >= 0)
			;
		if ((primary_server == -1) && !never_connected)
			do_hook(DISCONNECT_LIST, "%s", nickname);
		timeptr = &clock_timeout;

		old_current_screen = current_screen;
		for (screen = screen_list; screen; screen = screen->next)
		{
			set_current_screen(screen);
			if (screen->alive && is_cursor_in_display())
				timeptr = &cursor_timeout;
		}
		set_current_screen(old_current_screen);

		if (update_clock(0, 0, UPDATE_TIME))
		{
			Debug((8, "update_clock(0,0,0) returned true; updating clock"));
			if (get_int_var(CLOCK_VAR) || check_mail_status())
			{
				status_update(1);
				cursor_to_input();
			}
			if (primary_server != -1)
				do_notify();
		}
	}
	while (irc_io_loop);
	level--;
	irc_io_loop = old_loop;
	if (! dumb)
	{
		if (my_use_input)
		{
			set_input(last_input);
			new_free(&last_input);
			change_send_line(last_func);
		}
		if (prompt)
		{
			if (level == 0)
			    set_input_prompt(get_string_var(INPUT_PROMPT_VAR));
			else
			    set_input_prompt(last_prompt);
			new_free(&last_prompt);
		}
	}
	update_input(UPDATE_ALL);
	return (0);
}

int
/*ARGSUSED*/
main(int, char *[], char *[]);

int
main(argc, argv, envp)
	int	argc;
	char	*argv[];
	char	*envp[];
{
	u_char	*channel;

	srandom(time(NULL) ^ getpid());	/* something */

	start_time = time((time_t *)0);
#ifdef	SOCKS
	SOCKSinit(argv[0]);
#endif /* SOCKS */
	channel = parse_args((u_char **) argv, argc);
	if ((use_input == 0) && !no_fork)
	{
		if (fork())
			_exit(0);
	}
	if (gethostname(CP(hostname), NAME_LEN))
	{
		fprintf(stderr, "irc: couldn't figure out the name of your machine!\n");
		exit(1);
	}
	term_set_fp(stdout);
	if (dumb)
		new_window();
	else
	{
		init_screen();

#if !defined(MUNIX) && !defined(_RT)
		(void) MY_SIGNAL(SIGCONT, (sigfunc *) term_cont, 0);
#elif defined(SIGCONT)
		(void) MY_SIGNAL(SIGCONT, (sigfunc *) sig_on, 0 );
#endif /* !defined(MUNIX) && !defined(_RT) */

#if !defined(_RT) && defined(SIGWINCH)
		(void) MY_SIGNAL(SIGWINCH, (sigfunc *) sig_refresh_screen, 0);
#elif defined(SIGWINCH)
		(void) MY_SIGNAL(SIGWINCH, (sigfunc *) sig_on, 0 );
#endif /* _RT */

#ifndef ALLOC_DEBUG
# ifdef CORECATCH
		(void) MY_SIGNAL(SIGSEGV, (sigfunc *) coredump, 0);
#  ifdef SIGBUS
		(void) MY_SIGNAL(SIGBUS, (sigfunc *) coredump, 0);
#  endif /* SIGBUS */
# else
		(void) MY_SIGNAL(SIGSEGV, (sigfunc *) SIG_DFL, 0);
		/* Linux doesn't have SIGBUS */
#  ifdef SIGBUS
		(void) MY_SIGNAL(SIGBUS, (sigfunc *) SIG_DFL, 0);
#  endif /* SIGBUS */
# endif /* CORECATCH */
#endif /* ALLOC_DEBUG */

#ifdef MUNIX
		(void) MY_SIGNAL(SIGQUIT, (sigfunc *) cntl_y, 0);
#endif /* MINUX */

		(void) MY_SIGNAL(SIGHUP, (sigfunc *) irc_exit, 0);
		(void) MY_SIGNAL(SIGTERM, (sigfunc *) irc_exit, 0);
		(void) MY_SIGNAL(SIGPIPE, (sigfunc *) sig_on, 0);
		(void) MY_SIGNAL(SIGINT, (sigfunc *) cntl_c, 0);
#ifdef SIGSTOP
		(void) MY_SIGNAL(SIGSTOP, (sigfunc *) sig_on, 0);
#endif /* SIGSTOP */

		(void) MY_SIGNAL(SIGUSR1, (sigfunc *) sig_user1, 0);
#ifdef DO_USER2
		(void) MY_SIGNAL(SIGUSR2, (sigfunc *) sig_user2, 0);
#else
		(void) MY_SIGNAL(SIGUSR2, (sigfunc *) sig_on, 0);
#endif /* DO_USER2 */

#ifdef SIGPWR
		(void) MY_SIGNAL(SIGPWR, (sigfunc *) sig_on, 0);
#endif
#ifdef SIGCHLD
		(void) MY_SIGNAL(SIGCHLD, (sigfunc *) sig_on, 0);
#endif
#ifdef SIGURG
		(void) MY_SIGNAL(SIGURG, (sigfunc *) sig_on, 0);
#endif
#ifdef SIGTRAP
		(void) MY_SIGNAL(SIGTRAP, (sigfunc *) sig_on, 0);
#endif
#ifdef SIGTTIN
		(void) MY_SIGNAL(SIGTTIN, (sigfunc *) sig_on, 0);
#endif
#ifdef SIGTTOU
		(void) MY_SIGNAL(SIGTTOU, (sigfunc *) sig_on, 0);
#endif
#ifdef SIGXCPU
		(void) MY_SIGNAL(SIGXCPU, (sigfunc *) sig_on, 0);
#endif
#ifdef SIGXFSZ
		(void) MY_SIGNAL(SIGXFSZ, (sigfunc *) sig_on, 0);
#endif
#ifdef SIGPOLL
		(void) MY_SIGNAL(SIGPOLL, (sigfunc *) sig_on, 0);
#endif
#ifdef SIGINFO
		(void) MY_SIGNAL(SIGINFO, (sigfunc *) sig_on, 0);
#endif
#ifdef SIGWAITING
		(void) MY_SIGNAL(SIGWAITING, (sigfunc *) sig_on, 0);
#endif
#ifdef SIGLWP
		(void) MY_SIGNAL(SIGLWP, (sigfunc *) sig_on, 0);
#endif
#ifdef SIGSYS
		(void) MY_SIGNAL(SIGSYS, (sigfunc *) sig_on, 0);
#endif
#ifdef SIGIO
		(void) MY_SIGNAL(SIGIO, (sigfunc *) sig_on, 0);
#endif
		/* More signals could probably be added, perhaps some
		   should be removed */
	}

	init_variables();

	if (!dumb)
	{
		build_status((u_char *) 0);
		update_input(UPDATE_ALL);
	}

#ifdef MOTD_FILE
	{
		struct	stat	motd_stat,
				my_stat;
		u_char	*motd = NULL;
		int	des;

		malloc_strcpy(&motd, irc_lib);
		malloc_strcat(&motd, UP(MOTD_FILE));
		if (stat(CP(motd), &motd_stat) == 0)
		{
			u_char	*s = (u_char *) 0;

			malloc_strcpy(&s, my_path);
#ifdef __MSDOS__
			malloc_strcat(&s, UP("/ircmotd.red"));
#else
			malloc_strcat(&s, UP("/.ircmotd"));
#endif /* __MSDOS__ */
			if (stat(CP(s), &my_stat))
			{
				my_stat.st_atime = 0L;
				my_stat.st_mtime = 0L;
			}
			unlink(CP(s));
			if ((des = open(CP(s), O_CREAT, S_IREAD | S_IWRITE))
					!= -1)
				new_close(des);
			new_free(&s);
			if (motd_stat.st_mtime > my_stat.st_mtime)
			{
				put_file(motd);
		/* Thanks to Mark Dame <mdame@uceng.ec.edu> for this one */

#if defined(PAUSE_AFTER_MOTD) && PAUSE_AFTER_MOTD
		input_pause(UP("********  Press any key to continue  ********"));
#endif /* PAUSE_AFTER_MOTD */
				clear_window_by_refnum(0);
			}
		}
		new_free(&motd);
	}
#endif /* MOTD_FILE */

	if (bflag)
	{
		loading_global = 1;
		load(empty_string, UP("global"), empty_string);
		loading_global = 0;
		load_ircrc();
	}

	get_connected(0);
	if (channel)
	{
		if (get_server_version(primary_server) == ServerICB)
			set_server_icbgroup(primary_server, channel);
		else
		{
			u_char    *ptr;

			ptr = my_strsep(&channel, UP(","));
			if (is_channel(ptr))
				add_channel(ptr, 0, 0, CHAN_LIMBO, (ChannelList *) 0);
			while ((ptr = my_strsep(&channel, UP(","))) != NULL)
				if (is_channel(ptr))
					add_channel(ptr, 0, 0, CHAN_LIMBO, (ChannelList *) 0);
			new_free(&channel);
		}
	}
	idle_time = time(0);
	set_input(empty_string);
#ifndef _Windows
	irc_io(get_string_var(INPUT_PROMPT_VAR), NULL, use_input, irc_io_loop);
	irc_exit();
#endif /* _Windows */
	return 0;
}

/* 
 * set_irchost: This sets the source host for subsequent connections.
 */
void
set_irchost(irchost)
	u_char *irchost;
{
	if (!irchost || !*irchost)
		new_free(&source_host);
	else
		malloc_strcpy(&source_host, irchost);
}

/* 
 * set_dcchost: This sets the source host for subsequent connections.
 */
void
set_dcchost(dcchost)
	u_char *dcchost;
{
	if (!dcchost || !*dcchost)
		new_free(&dcc_source_host);
	else
		malloc_strcpy(&dcc_source_host, dcchost);
}


syntax highlighted by Code2HTML, v. 0.9.1