/* X-Chat
 * Copyright (C) 1998 Peter Zelezny.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "xchat.h"
#include "fe.h"
#include "util.h"
#include <netdb.h>
#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>
#include <time.h>
#include "cfgfiles.h"
#include "ignore.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/time.h>
#include "plugin.h"

#ifdef USE_OPENSSL
#include <openssl/ssl.h>		  /* SSL_() */
#include "ssl.h"
#endif

GSList *popup_list = 0;
GSList *button_list = 0;
GSList *command_list = 0;
GSList *ctcp_list = 0;
GSList *replace_list = 0;
GSList *sess_list = 0;
GSList *serv_list = 0;
GSList *dcc_list = 0;
GSList *url_list = 0;
GSList *away_list = 0;
GSList *ignore_list = 0;
GSList *usermenu_list = 0;
GSList *urlhandler_list = 0;

int notify_tag = -1;
int xchat_is_quitting = 0;
int auto_connect = 1;

extern GSList *ctcp_list;
extern GSList *popup_list;
extern GSList *button_list;
extern GSList *command_list;
extern GSList *replace_list;

#ifdef USE_PERL
extern struct session *perl_sess;
#endif
struct session *current_tab;
struct session *menu_sess = 0;
struct xchatprefs prefs;

void xchat_cleanup (void);
struct session *new_session (struct server *serv, char *from);
void auto_reconnect (struct server *serv, int send_quit, int err);

/* trans */
extern void serv2user (unsigned char *);
extern void user2serv (unsigned char *);
extern int load_trans_table (char *full_path);
/* ~trans */

/* inbound.c */

extern void process_line (struct server *serv, char *buf);

/* plugin.c */

extern void module_setup ();
extern void signal_setup ();

/* server.c */

extern int close_socket (int sok);
extern void connecting_fin (struct session *);
extern void connect_server (struct session *sess, char *server, int port,
									 int quiet);
extern void disconnect_server (struct session *sess, int sendquit, int err);

/* userlist.c */

extern struct User *find_name (struct session *sess, char *name);

/* notify.c */

extern void notify_load (void);
extern void notify_save (void);
extern int notify_checklist (void);
extern void notify_cleanup (void);

#ifdef USE_PERL
/* perl.c */

void perl_init (struct session *, int);
void perl_end (void);
int perl_inbound (struct session *sess, struct server *serv, char *buf);
#endif

/* python.c */

extern void pys_init ();
extern void pys_kill ();

/* editlist.c */

extern void list_loadconf (char *, GSList **, char *);

/* text.c */

extern unsigned char *strip_color (unsigned char *text);
extern void end_logging (int fd);
extern void load_text_events ();
extern void pevent_dialog_save (char *fn);

#ifdef USE_OPENSSL
/* server.c */

extern void ssl_cb_info (SSL * s, int where, int ret);
#endif


void auto_reconnect (struct server *serv, int send_quit, int err);

/* anything above SEND_MAX bytes in 1 second is
   queued and sent QUEUE_TIMEOUT milliseconds later */

#define SEND_MAX 256
#define QUEUE_TIMEOUT 2500

/* this whole throttling system is lame, can anyone suggest
   a better system? */

static int
tcp_send_queue (struct server *serv)
{
	char *buf;
	int len;
	GSList *list = serv->outbound_queue;
	time_t now = time (0);

	while (list)
	{
		buf = (char *) list->data;
		len = strlen (buf);

		if (serv->last_send != now)
		{
			serv->last_send = now;
			serv->bytes_sent = 0;
		} else
		{
			serv->bytes_sent += len;
		}

		if (serv->bytes_sent > SEND_MAX)
			return 1;				  /* don't remove the timeout handler */

		fe_add_rawlog (serv, buf, TRUE);
		if (!EMIT_SIGNAL
			 (XP_IF_SEND, (void *) serv->sok, buf, (void *) len, NULL, NULL, 0))
		{
#ifdef USE_OPENSSL
			if (!serv->ssl)
#endif
				send (serv->sok, buf, len, 0);
#ifdef USE_OPENSSL
			else
				_SSL_send (serv->ssl, buf, len);
#endif
		}

		serv->outbound_queue = g_slist_remove (serv->outbound_queue, buf);
		free (buf);

		list = serv->outbound_queue;
	}
	return 0;						  /* remove the timeout handler */
}

int
tcp_send_len (struct server *serv, char *buf, int len)
{
#ifdef USE_TRANS
	unsigned char *tbuf;
#define TRANS_STAT_BUF_LEN 1024
	static unsigned char sbuf[TRANS_STAT_BUF_LEN];
	int ret;
#endif
	time_t now = time (0);

	if (serv->last_send != now)
	{
		serv->last_send = now;
		serv->bytes_sent = 0;
	} else
	{
		serv->bytes_sent += len;
	}

	if (prefs.throttle && (serv->bytes_sent > SEND_MAX || serv->outbound_queue))
	{
		buf = strdup (buf);
		if (!serv->outbound_queue)
			fe_timeout_add (QUEUE_TIMEOUT, tcp_send_queue, serv);
		serv->outbound_queue = g_slist_append (serv->outbound_queue, buf);
		return 1;
	}
#ifdef USE_TRANS
	if (prefs.use_trans)
	{
		if (len >= TRANS_STAT_BUF_LEN)
			tbuf = malloc (len + 1);
		else
			tbuf = sbuf;
		if (!tbuf)
		{
			return -1;
		}

		strcpy (tbuf, buf);
		user2serv (tbuf);
		fe_add_rawlog (serv, buf, TRUE);
		if (!EMIT_SIGNAL
			 (XP_IF_SEND, (void *) serv->sok, buf, (void *) len, NULL, NULL, 0))
		{
#ifdef USE_OPENSSL
			if (!serv->ssl)
#endif
				ret = send (serv->sok, tbuf, len, 0);
#ifdef USE_OPENSSL
			else
				ret = _SSL_send (serv->ssl, tbuf, len);
#endif
		} else
		{
			ret = 1;
		}
		if (tbuf != sbuf)
			free (tbuf);
		return ret;
	}
#endif
	fe_add_rawlog (serv, buf, TRUE);

	if (!EMIT_SIGNAL
		 (XP_IF_SEND, (void *) serv->sok, buf, (void *) len, NULL, NULL, 0))
	{
#ifdef USE_OPENSSL
		if (!serv->ssl)
#endif
			return send (serv->sok, buf, len, 0);
#ifdef USE_OPENSSL
		else
			return _SSL_send (serv->ssl, buf, len);
#endif
	}
	return 1;
}

int
tcp_send (struct server *serv, char *buf)
{
	return tcp_send_len (serv, buf, strlen (buf));
}

int
is_server (server * serv)
{
	GSList *list = serv_list;
	while (list)
	{
		if (list->data == serv)
			return 1;
		list = list->next;
	}
	return 0;
}

int
is_session (session * sess)
{
	GSList *list = sess_list;
	while (list)
	{
		if (list->data == sess)
			return 1;
		list = list->next;
	}
	return 0;
}

struct session *
find_dialog (struct server *serv, char *nick)
{
	GSList *list = sess_list;
	struct session *sess;

	while (list)
	{
		sess = (struct session *) list->data;
		if (sess->server == serv && sess->is_dialog)
		{
			if (!strcasecmp (nick, sess->channel))
				return (sess);
		}
		list = list->next;
	}
	return 0;
}

struct session *
find_session_from_channel (char *chan, struct server *serv)
{
	struct session *sess;
	GSList *list = sess_list;
	while (list)
	{
		sess = (struct session *) list->data;
		if (!sess->is_shell && !strcasecmp (chan, sess->channel))
		{
			if (!serv)
				return sess;
			if (serv == sess->server)
				return sess;
		}
		list = list->next;
	}
	return 0;
}

struct session *
find_session_from_nick (char *nick, struct server *serv)
{
	struct session *sess;
	GSList *list = sess_list;

	sess = find_session_from_channel (nick, serv);
	if (sess)
		return sess;

	if (serv->front_session)
	{
		if (find_name (serv->front_session, nick))
			return serv->front_session;
	}

	while (list)
	{
		sess = (struct session *) list->data;
		if (sess->server == serv)
		{
			if (find_name (sess, nick))
				return sess;
		}
		list = list->next;
	}
	return 0;
}

static int
timeout_auto_reconnect (struct server *serv)
{
	if (is_server (serv))  /* make sure it hasnt been closed during the delay */
	{
		if (!serv->connected && !serv->connecting && serv->front_session)
		{
			connect_server (serv->front_session, serv->hostname, serv->port,
								 FALSE);
		}
		serv->recondelay_tag = -1;
	}
	return 0;	/* returning 0 should remove the timeout handler */
}

void
auto_reconnect (struct server *serv, int send_quit, int err)
{
	struct session *s;
	GSList *list;

	if (serv->front_session == NULL)
		return;

	list = sess_list;
	while (list)					  /* make sure auto rejoin can work */
	{
		s = (struct session *) list->data;
		if (is_channel (s->channel))
		{
			strcpy (s->waitchannel, s->channel);
			strcpy (s->willjoinchannel, s->channel);
		}
		list = list->next;
	}
	disconnect_server (serv->front_session, send_quit, err);

	if (prefs.recon_delay)
		serv->recondelay_tag =
		fe_timeout_add (prefs.recon_delay * 1000, timeout_auto_reconnect, serv);
	else
		connect_server (serv->front_session, serv->hostname, serv->port, FALSE);
}

void
read_data (struct server *serv, int sok)
{
	int err, i, len;
	char lbuf[2050];
	char *temp;

	while (1)
	{
		if (!EMIT_SIGNAL (XP_IF_RECV, &len, (void *) sok, &lbuf,
								(void *) ((sizeof (lbuf)) - 2), NULL, 0))
		{
#ifdef USE_OPENSSL
			if (!serv->ssl)
#endif
				len = recv (sok, lbuf, sizeof (lbuf) - 2, 0);
#ifdef USE_OPENSSL
			else
				len = _SSL_recv (serv->ssl, lbuf, sizeof (lbuf) - 2);
#endif
		}
		if (len < 1)
		{
			if (len < 0)
			{
				if (errno == EAGAIN || errno == EWOULDBLOCK)
					return;
				err = errno;
			} else
			{
				err = 0;
			}
			if (prefs.autoreconnect)
				auto_reconnect (serv, FALSE, err);
			else
				disconnect_server (serv->front_session, FALSE, err);
			return;
		} else
		{
			i = 0;

			lbuf[len] = 0;

			while (i < len)
			{
				switch (lbuf[i])
				{
				case '\r':
					break;

				case '\n':
					serv->linebuf[serv->pos] = 0;
#ifdef USE_TRANS
					if (prefs.use_trans)
						serv2user (serv->linebuf);
#endif
					if (prefs.stripcolor)
					{
						temp = strip_color (serv->linebuf);
						process_line (serv, temp);
						free (temp);
					} else
					{
						process_line (serv, serv->linebuf);
					}
					serv->pos = 0;
					break;

				default:
					serv->linebuf[serv->pos] = lbuf[i];
					if (serv->pos > 519)
						fprintf (stderr, "*** XCHAT WARNING: Buffer overflow - shit server!\n");
					else
						serv->pos++;
				}
				i++;
			}
		}
	}
}

void
flush_server_queue (struct server *serv)
{
	GSList *list = serv->outbound_queue;
	void *data;

	while (list)
	{
		data = list->data;
		serv->outbound_queue = g_slist_remove (serv->outbound_queue, data);
		free (data);
		list = serv->outbound_queue;
	}
	serv->last_send = 0;
	serv->bytes_sent = 0;
}

struct server *
new_server (void)
{
	struct server *serv;

	serv = malloc (sizeof (struct server));
	memset (serv, 0, sizeof (struct server));

	serv->sok = -1;
	serv->iotag = -1;
	serv->recondelay_tag = -1;
#ifdef USE_OPENSSL
	serv->ssl_do_connect_tag = -1;
#endif
	strcpy (serv->nick, prefs.nick1);
	serv_list = g_slist_prepend (serv_list, serv);

	fe_new_server (serv);

	if (prefs.use_server_tab)
	{
		register unsigned int oldh = prefs.hideuserlist;
		prefs.hideuserlist = 1;
		serv->front_session = new_session (serv, 0);
		prefs.hideuserlist = oldh;
		serv->front_session->is_server = TRUE;
	}
	return serv;
}

struct session *
new_session (struct server *serv, char *from)
{
	struct session *sess;

	sess = malloc (sizeof (struct session));
	memset (sess, 0, sizeof (struct session));

	sess->server = serv;
	sess->logfd = -1;

	if (from)
	{
		sess->is_dialog = TRUE;
		strcpy (sess->channel, from);
	}

	sess_list = g_slist_prepend (sess_list, sess);

	fe_new_window (sess);

	return sess;
}

static void
kill_server_callback (server * serv)
{
	if (serv->connected)
	{
		if (serv->iotag != -1)
			fe_input_remove (serv->iotag);
		fe_timeout_add (5000, close_socket, (void *) serv->sok);
	}
	if (serv->connecting)
	{
		kill (serv->childpid, SIGKILL);
		waitpid (serv->childpid, NULL, 0 /*WNOHANG*/);
		if (serv->iotag != -1)
			fe_input_remove (serv->iotag);
#ifdef USE_OPENSSL
		if (serv->ssl_do_connect_tag != -1)
		{
			fe_timeout_remove (serv->ssl_do_connect_tag);
			serv->ssl_do_connect_tag = -1;
		}
#endif
		close (serv->childread);
		close (serv->childwrite);
		close (serv->sok);
	}
	fe_server_callback (serv);

	serv_list = g_slist_remove (serv_list, serv);

	dcc_notify_kill (serv);
	flush_server_queue (serv);

	free (serv->gui);
	free (serv);

	notify_cleanup ();
}

static void
log_notify_kill (session * sess)
{
	if (sess->logfd != -1)
		end_logging (sess->logfd);
}

static void
exec_notify_kill (session * sess)
{
	struct nbexec *re;

	if (sess->running_exec != NULL)
	{
		re = sess->running_exec;
		sess->running_exec = NULL;
		kill (re->childpid, SIGKILL);
		fe_input_remove (re->iotag);
		close (re->myfd);
		close (re->childfd);
		free (re);
	}
}

#ifdef USE_PERL
static void
perl_notify_kill (session * sess)
{
	struct session *s;
	GSList *list = sess_list;

	if (perl_sess == sess)		  /* need to find a new perl_sess, this one's closing */
	{
		while (list)
		{
			s = (struct session *) list->data;
			if (s->server == perl_sess->server && s != perl_sess)
			{
				perl_sess = s;
				break;
			}
			list = list->next;
		}
		if (perl_sess == sess)
			perl_sess = 0;
	}
}
#endif

static void
send_quit_or_part (session * killsess)
{
	int willquit = TRUE;
	char tbuf[256];
	GSList *list;
	session *sess;
	server *killserv = killsess->server;

	/* check if this is the last session using this server */
	list = sess_list;
	while (list)
	{
		sess = (session *) list->data;
		if (sess->server == killserv && sess != killsess)
		{
			willquit = FALSE;
			list = 0;
		} else
			list = list->next;
	}

	if (xchat_is_quitting)
		willquit = TRUE;

	if (killserv->connected)
	{
		if (willquit)
		{
			if (!killserv->sent_quit)
			{
				flush_server_queue (killserv);
				snprintf (tbuf, sizeof tbuf, "QUIT :%s\r\n",
							 killsess->quitreason);
				tcp_send (killserv, tbuf);
				killserv->sent_quit = TRUE;
			}
		} else
		{
			if (!killsess->is_dialog && !killsess->is_server
				 && killsess->channel[0])
			{
				snprintf (tbuf, sizeof tbuf, "PART %s\r\n", killsess->channel);
				tcp_send (killserv, tbuf);
			}
		}
	}
}

void
kill_session_callback (session * killsess)
{
	server *killserv = killsess->server;
	session *sess;
	GSList *list;

	if (!killsess->quitreason)
		killsess->quitreason = prefs.quitreason;

	if (current_tab == killsess)
		current_tab = NULL;

	if (killserv->front_session == killsess)
	{
		/* front_session is closed, find a valid replacement */
		killserv->front_session = NULL;
		list = sess_list;
		while (list)
		{
			sess = (session *) list->data;
			if (sess != killsess && sess->server == killserv)
			{
				killserv->front_session = sess;
				break;
			}
			list = list->next;
		}
	}

	sess_list = g_slist_remove (sess_list, killsess);

	fe_session_callback (killsess);

	exec_notify_kill (killsess);

#ifdef USE_PERL
	perl_notify_kill (killsess);
#endif

	log_notify_kill (killsess);

	send_quit_or_part (killsess);

	history_free (&killsess->history);
	free (killsess->gui);
	free (killsess);

	if (!sess_list)
		xchat_cleanup ();			  /* sess_list is empty, quit! */

	list = sess_list;
	while (list)
	{
		sess = (session *) list->data;
		if (sess->server == killserv)
			return;					  /* this server is still being used! */
		list = list->next;
	}

	kill_server_callback (killserv);
}

static void
free_sessions (void)
{
	struct session *sess;
	GSList *list = sess_list;

	while (list)
	{
		sess = (struct session *) list->data;
		/*send_quit_or_part (sess); */
		fe_close_window (sess);
		list = sess_list;
	}
}

struct away_msg *
find_away_message (struct server *serv, char *nick)
{
	struct away_msg *away;
	GSList *list = away_list;
	while (list)
	{
		away = (struct away_msg *) list->data;
		if (away->server == serv && !strcasecmp (nick, away->nick))
			return away;
		list = list->next;
	}
	return 0;
}

void
save_away_message (struct server *serv, char *nick, char *msg)
{
	struct away_msg *away = find_away_message (serv, nick);

	if (away)						  /* Change message for known user */
	{
		if (away->message)
			free (away->message);
		away->message = strdup (msg);
	} else
		/* Create brand new entry */
	{
		away = malloc (sizeof (struct away_msg));
		if (away)
		{
			away->server = serv;
			strcpy (away->nick, nick);
			away->message = strdup (msg);
			away_list = g_slist_prepend (away_list, away);
		}
	}
}

static int
mail_items (char *file)
{
	FILE *fp;
	int items;
	char buf[512];

	fp = fopen (file, "r");
	if (!fp)
		return 1;

	items = 0;
	while (fgets (buf, sizeof buf, fp))
	{
		if (!strncmp (buf, "From ", 5))
			items++;
	}
	fclose (fp);

	return items;
}

static void
xchat_mail_check (void)
{
	static int last_size = -1;
	int size;
	struct stat st;
	char buf[512];
	char *maildir;

	maildir = getenv ("MAIL");
	if (!maildir)
	{
		snprintf (buf, 511, "/var/spool/mail/%s", g_get_user_name ());
		maildir = buf;
	}

	if (stat (maildir, &st) < 0)
		return;

	size = st.st_size;

	if (last_size == -1)
	{
		last_size = size;
		return;
	}

	if (size > last_size)
	{
		sprintf (buf, "%d", mail_items (maildir));
		sprintf (buf + 16, "%d", size);
		if (menu_sess && !menu_sess->is_server)
			EMIT_SIGNAL (XP_TE_NEWMAIL, menu_sess, buf, buf + 16, NULL, NULL, 0);
	}

	last_size = size;
}

static void
lag_check (void)
{
	server *serv;
	GSList *list = serv_list;
	unsigned long tim;
	struct timeval timev;
	char tbuf[256];
	time_t now = time (0);

	gettimeofday (&timev, 0);
	tim = (timev.tv_sec - 50000) * 1000000 + timev.tv_usec;

	while (list)
	{
		serv = list->data;
		if (serv->connected && serv->end_of_motd)
		{
			if (prefs.pingtimeout && ((now - serv->ping_recv) > prefs.pingtimeout))
			{
				sprintf (tbuf, "%d", (int)(now - serv->ping_recv));
				EMIT_SIGNAL (XP_TE_PINGTIMEOUT, serv->front_session, tbuf, NULL, NULL, NULL, 0);
				auto_reconnect (serv, 0, -1);
			} else
			{
				sprintf (tbuf, "PING LAG%lu :%s\r\n", tim, serv->servername);
				tcp_send (serv, tbuf);
				serv->lag_sent = tim;
			}
		}
		list = list->next;
	}
}

static int
xchat_misc_checks (void)		  /* this gets called every 2 seconds */
{
	static int count = 0;

	dcc_check_timeouts ();

	count++;

	if (count == 5 && prefs.lagometer)
	{
		lag_check ();
	}

	if (count == 10)
	{
		count = 0;
		if (prefs.mail_check)
			xchat_mail_check ();
	}

	return 1;
}

#define defaultconf_ctcp \
	"NAME TIME\n"		"CMD /nctcp %s TIME %t\n\n"\
	"NAME PING\n"		"CMD /nctcp %s PING %d\n\n"

#define defaultconf_popup \
	"NAME SUB\n"					"CMD CTCP\n\n"\
	"NAME Version\n"				"CMD /ctcp %s VERSION\n\n"\
	"NAME Userinfo\n"				"CMD /ctcp %s USERINFO\n\n"\
	"NAME Clientinfo\n"			"CMD /ctcp %s CLIENTINFO\n\n"\
	"NAME Ping\n"					"CMD /ping %s\n\n"\
	"NAME Time\n"					"CMD /ctcp %s TIME\n\n"\
	"NAME Finger\n"				"CMD /ctcp %s FINGER\n\n"\
	"NAME XDCC List\n"			"CMD /ctcp %s XDCC LIST\n\n"\
	"NAME CDCC List\n"			"CMD /ctcp %s CDCC LIST\n\n"\
	"NAME ENDSUB\n"				"CMD \n\n"\
	"NAME SUB\nCMD DCC\n\n"\
	"NAME Send File\nCMD /dcc send %s\n\n"\
	"NAME Offer Chat\nCMD /dcc chat %s\n\n"\
	"NAME Abort Chat\nCMD /dcc close chat %s\n\n"\
	"NAME ENDSUB\nCMD \n\n"\
	"NAME SUB\nCMD Oper\n\n"\
	"NAME Kill\nCMD /quote KILL %s :die!\n\n"\
	"NAME ENDSUB\nCMD \n\n"\
	"NAME SUB\nCMD Mode\n\n"\
	"NAME Give Voice\nCMD /voice %s\n\n"\
	"NAME Take Voice\nCMD /devoice %s\n"\
	"NAME SEP\nCMD \n\n"\
	"NAME Give Ops\nCMD /op %s\n\n"\
	"NAME Take Ops\nCMD /deop %s\n\n"\
	"NAME ENDSUB\nCMD \n\n"\
	"NAME SUB\nCMD Ignore\n\n"\
	"NAME Ignore User\nCMD /ignore %s!*@* ALL\n\n"\
	"NAME UnIgnore User\nCMD /unignore %s!*@*\n\n"\
	"NAME ENDSUB\nCMD \n\n"\
	"NAME SUB\nCMD Kick/Ban\n\n"\
	"NAME Kick\nCMD /kick %s\n\n"\
	"NAME Ban *!*@*.host\nCMD /ban %s 0\n\n"\
	"NAME Ban *!*@domain\nCMD /ban %s 1\n\n"\
	"NAME Ban *!user@*.host\nCMD /ban %s 2\n\n"\
	"NAME Ban *!user@domain\nCMD /ban %s 3\n\n"\
	"NAME Kick&Ban\nCMD /kickban %s\n\n"\
	"NAME ENDSUB\nCMD \n\n"\
	"NAME SUB\nCMD Info\n\n"\
	"NAME Who\nCMD /quote WHO %s\n\n"\
	"NAME Whois\nCMD /quote WHOIS %s\n\n"\
	"NAME DNS Lookup\nCMD /dns %s\n\n"\
	"NAME Trace\nCMD /quote TRACE %s\n\n"\
	"NAME UserHost\nCMD /quote USERHOST %s\n\n"\
	"NAME ENDSUB\nCMD \n\n"\
	"NAME SUB\n"			"CMD External\n\n"\
	"NAME Traceroute\n"	"CMD !xterm -e /bin/sh -c \"traceroute %h ; sleep 30\"\n\n"\
	"NAME Ping\n"			"CMD !xterm -e /bin/sh -c \"ping -c 4 %h ; sleep 30\"\n\n"\
	"NAME Telnet\n"		"CMD !xterm -e telnet %h\n\n"\
	"NAME ENDSUB\n"		"CMD \n\n"\
	"NAME Open Query\n"	"CMD /query %s\n\n"

#define defaultconf_buttons   "NAME Op\nCMD /op %a\n\n"\
                              "NAME DeOp\nCMD /deop %a\n\n"\
                              "NAME Ban\nCMD /ban %s\n\n"\
                              "NAME Kick\nCMD /kick %s\n\n"\
                              "NAME Send\nCMD /dcc send %s\n\n"\
                              "NAME Dialog\nCMD /query %s\n\n"\
                              "NAME Lookup\nCMD /dns %s\n\n"\
                              "NAME Whois\nCMD /whois %s\n"

#define defaultconf_replace "NAME teh\nCMD the\n\n"\
                            "NAME r\nCMD are\n\n"\
                            "NAME u\nCMD you\n\n"

#define defaultconf_commands \
   "NAME ACTION\n"		"CMD /me &2\n\n"\
	"NAME AME\n"			"CMD /allchan /me &2\n\n"\
	"NAME AMSG\n"			"CMD /allchan &2\n\n"\
   "NAME BANLIST\n"		"CMD /quote MODE %c +b\n\n"\
   "NAME CHAT\n"			"CMD /dcc chat %2\n\n"\
   "NAME DIALOG\n"		"CMD /query %2\n\n"\
   "NAME DMSG\n"			"CMD /msg =%2 &3\n\n"\
   "NAME EXIT\n"			"CMD /quit\n\n"\
   "NAME J\n"				"CMD /join &2\n\n"\
   "NAME KILL\n"			"CMD /quote KILL %2 :&3\n\n"\
   "NAME LEAVE\n"			"CMD /part &2\n\n"\
   "NAME M\n"				"CMD /msg &2\n\n"\
   "NAME ONOTICE\n"		"CMD /notice @%c &2\n\n"\
   "NAME RAW\n"			"CMD /quote &2\n\n"\
   "NAME SERVHELP\n"		"CMD /quote HELP\n\n"\
	"NAME SPING\n"			"CMD /ping\n\n"\
   "NAME SV\n"				"CMD /echo xchat %v %m\n\n"\
   "NAME UMODE\n"			"CMD /mode %n &2\n\n"\
   "NAME UPTIME\n"		"CMD /quote STATS u\n\n"\
   "NAME VER\n"			"CMD /ctcp %2 VERSION\n\n"\
   "NAME VERSION\n"		"CMD /ctcp %2 VERSION\n\n"\
   "NAME WALLOPS\n"		"CMD /quote WALLOPS :&2\n\n"\
   "NAME WII\n"			"CMD /quote WHOIS %2 %2\n\n"

#define defaultconf_usermenu \
	"NAME SUB\n"					"CMD IRC Stuff\n\n"\
   "NAME Disconnect\n"			"CMD /discon\n\n"\
   "NAME Reconnect\n"			"CMD /reconnect\n\n"\
	"NAME Part Channel\n"		"CMD /part\n\n"\
   "NAME Cycle Channel\n"		"CMD /cycle\n\n"\
   "NAME Server Map\n"			"CMD /quote MAP\n\n"\
	"NAME Server Links\n"		"CMD /quote LINKS\n\n"\
   "NAME Ping Server\n"			"CMD /ping\n\n"\
   "NAME ENDSUB\n"				"CMD \n\n"\
	"NAME SUB\n"					"CMD Connect\n\n"\
   "NAME irc.xchat.org #Linux\n""CMD /servchan irc.xchat.org 6667 #linux\n\n"\
 	"NAME Go to EFNet\n"			"CMD /newserver irc.efnet.net\n\n"\
   "NAME ENDSUB\n"				"CMD \n\n"\
	"NAME SUB\n"					"CMD Settings\n\n"\
	"NAME TOGGLE Hide Version\n"		"CMD hide_version\n\n"\
	"NAME TOGGLE Colored Nicks\n"		"CMD colorednicks\n\n"\
	"NAME TOGGLE 1.4.x Nick Comp.\n"	"CMD old_nickcompletion\n\n"\
	"NAME TOGGLE Strip mIRC color\n"	"CMD stripcolor\n\n"\
	"NAME TOGGLE Filter Beeps\n"		"CMD filterbeep\n\n"\
	"NAME TOGGLE Raw MODE Display\n"	"CMD raw_modes\n\n"\
	"NAME TOGGLE Perl Warnings\n"		"CMD perlwarnings\n\n"\
	"NAME TOGGLE Mail Checker\n"		"CMD mail_check\n\n"\
   "NAME ENDSUB\n"				"CMD \n\n"\
	"NAME SUB\n"					"CMD External\n\n"\
   "NAME Run XMMS\n"				"CMD !xmms\n\n"\
	"NAME Run RXVT\n"				"CMD !rxvt\n\n"\
   "NAME ENDSUB\n"				"CMD \n\n"

#define defaultconf_urlhandlers \
   "NAME -> Netscape (Existing)\n"		"CMD !netscape -remote 'openURL(%s)'\n\n"\
   "NAME -> Netscape (New Window)\n"	"CMD !netscape -remote 'openURL(%s,new-window)'\n\n"\
   "NAME -> Netscape (Run New)\n"		"CMD !netscape %s\n\n"\
   "NAME -> Lynx\n"							"CMD !xterm -e lynx %s\n\n"\
   "NAME -> NcFTP\n" 						"CMD !xterm -e ncftp %s\n\n"\
   "NAME -> gFTP\n"							"CMD !gftp %s\n\n"\
   "NAME -> Mozilla\n"						"CMD !mozilla %s\n\n"\
   "NAME -> KFM\n"							"CMD !kfmclient openURL %s\n\n"\
   "NAME -> Gnome URL Handler\n"			"CMD !gnome-moz-remote %s\n\n"\
   "NAME -> Connect as IRC Server\n"	"CMD /newserver %s\n\n"

static void
xchat_init (void)
{
	struct session *sess;
	struct server *serv;
	struct sigaction act;

	act.sa_handler = SIG_IGN;
	act.sa_flags = 0;
	sigemptyset (&act.sa_mask);
	sigaction (SIGPIPE, &act, NULL);

	signal_setup ();
	load_text_events ();
	notify_load ();
	ignore_load ();
	list_loadconf ("popup.conf", &popup_list, defaultconf_popup);
	list_loadconf ("ctcpreply.conf", &ctcp_list, defaultconf_ctcp);
	list_loadconf ("buttons.conf", &button_list, defaultconf_buttons);
	list_loadconf ("commands.conf", &command_list, defaultconf_commands);
	list_loadconf ("replace.conf", &replace_list, defaultconf_replace);
	list_loadconf ("usermenu.conf", &usermenu_list, defaultconf_usermenu);
	list_loadconf ("urlhandlers.conf", &urlhandler_list,
						defaultconf_urlhandlers);

#ifdef USE_TRANS
	if (prefs.use_trans)
	{
		if (load_trans_table (prefs.trans_file) == 0)
			prefs.use_trans = 0;
	}
#endif

	serv = new_server ();
	if (prefs.use_server_tab)
		sess = serv->front_session;
	else
		sess = new_session (serv, 0);

#ifdef USE_PYTHON
	pys_init ();
#endif
#ifdef USE_PLUGIN
	module_setup ();
#endif
#ifdef USE_PERL
	perl_init (sess, TRUE);
#endif

	if (prefs.notify_timeout)
		notify_tag =
			fe_timeout_add (prefs.notify_timeout * 1000, notify_checklist, 0);

	fe_timeout_add (2000, xchat_misc_checks, 0);
}

void
xchat_cleanup (void)
{
	xchat_is_quitting = TRUE;
#ifdef USE_PERL
	perl_end ();
#endif
	fe_cleanup ();
	if (prefs.autosave)
	{
		save_config ();
		pevent_dialog_save (NULL);
	}
	notify_save ();
	ignore_save ();
	free_sessions ();
	fe_exit ();
}

int
child_handler (int pid)
{
	if (waitpid (pid, 0, WNOHANG) == pid)
		return 0;					  /* remove timeout handler */
	return 1;						  /* keep the timeout handler */
}

void
xchat_exec (char *cmd)
{
	int pid;

	pid = util_exec (cmd);
	if (pid != -1)
	/* zombie avoiding system. Don't ask! it has to be like this to work
      with zvt (which overrides the default handler) */
		fe_timeout_add (1000, child_handler, (void *)pid);
}

int
main (int argc, char *argv[])
{
#ifdef SOCKS
	SOCKSinit (argv[0]);
#endif

#ifdef USE_OPENSSL
	_SSL_context_init (ssl_cb_info);
#endif

	if (!fe_args (argc, argv))
		return 0;

	load_config ();

	fe_init ();

	xchat_init ();

	fe_main ();

#ifdef USE_OPENSSL
	_SSL_context_free ();
#endif

#ifdef USE_PYTHON
	pys_kill ();					  /* this never returns! */
#endif

	return 0;
}
