summary refs log tree commit diff stats
path: root/src/common
diff options
context:
space:
mode:
authorberkeviktor@aol.com <berkeviktor@aol.com>2011-02-24 04:14:30 +0100
committerberkeviktor@aol.com <berkeviktor@aol.com>2011-02-24 04:14:30 +0100
commit4a6ceffb98a0b785494f680d3776c4bfc4052f9e (patch)
tree850703c1c841ccd99f58d0b06084615aaebe782c /src/common
parentf16af8be941b596dedac3bf4e371ee2d21f4b598 (diff)
add xchat r1489
Diffstat (limited to 'src/common')
-rw-r--r--src/common/Makefile.am61
-rw-r--r--src/common/cfgfiles.c1105
-rw-r--r--src/common/cfgfiles.h55
-rw-r--r--src/common/chanopt.c431
-rw-r--r--src/common/chanopt.h6
-rw-r--r--src/common/ctcp.c191
-rw-r--r--src/common/ctcp.h6
-rw-r--r--src/common/dbus/Makefile.am56
-rw-r--r--src/common/dbus/README198
-rw-r--r--src/common/dbus/apps_xchat_url_handler.schemas37
-rw-r--r--src/common/dbus/dbus-client.c118
-rw-r--r--src/common/dbus/dbus-client.h27
-rw-r--r--src/common/dbus/dbus-plugin.c1087
-rw-r--r--src/common/dbus/dbus-plugin.h31
-rw-r--r--src/common/dbus/example.c201
-rw-r--r--src/common/dbus/example.py28
-rw-r--r--src/common/dbus/marshallers.list1
-rw-r--r--src/common/dbus/org.xchat.service.service.in3
-rw-r--r--src/common/dbus/remote-object.xml142
-rw-r--r--src/common/dcc.c2587
-rw-r--r--src/common/dcc.h117
-rw-r--r--src/common/fe.h162
-rw-r--r--src/common/history.c121
-rw-r--r--src/common/history.h18
-rw-r--r--src/common/identd.c89
-rw-r--r--src/common/ignore.c424
-rw-r--r--src/common/ignore.h38
-rw-r--r--src/common/inbound.c1336
-rw-r--r--src/common/inbound.h39
-rw-r--r--src/common/inet.h43
-rw-r--r--src/common/make-te.c58
-rw-r--r--src/common/modes.c836
-rw-r--r--src/common/modes.h12
-rw-r--r--src/common/msproxy.c467
-rw-r--r--src/common/msproxy.h257
-rw-r--r--src/common/network.c383
-rw-r--r--src/common/network.h34
-rw-r--r--src/common/notify.c634
-rw-r--r--src/common/notify.h44
-rw-r--r--src/common/outbound.c4403
-rw-r--r--src/common/outbound.h20
-rw-r--r--src/common/plugin-timer.c208
-rw-r--r--src/common/plugin-timer.h7
-rw-r--r--src/common/plugin.c1569
-rw-r--r--src/common/plugin.h132
-rw-r--r--src/common/proto-irc.c1260
-rw-r--r--src/common/proto-irc.h6
-rw-r--r--src/common/server.c2047
-rw-r--r--src/common/server.h26
-rw-r--r--src/common/servlist.c1311
-rw-r--r--src/common/servlist.h62
-rw-r--r--src/common/ssl.c323
-rw-r--r--src/common/ssl.h65
-rw-r--r--src/common/text.c2309
-rw-r--r--src/common/text.h42
-rw-r--r--src/common/textenums.h76
-rw-r--r--src/common/textevents.h424
-rw-r--r--src/common/textevents.in840
-rw-r--r--src/common/tree.c215
-rw-r--r--src/common/tree.h16
-rw-r--r--src/common/url.c280
-rw-r--r--src/common/url.h19
-rw-r--r--src/common/userlist.c454
-rw-r--r--src/common/userlist.h41
-rw-r--r--src/common/util.c1729
-rw-r--r--src/common/util.h54
-rw-r--r--src/common/xchat-plugin.h334
-rw-r--r--src/common/xchat.c951
-rw-r--r--src/common/xchat.h575
-rw-r--r--src/common/xchatc.h39
70 files changed, 31320 insertions, 0 deletions
diff --git a/src/common/Makefile.am b/src/common/Makefile.am
new file mode 100644
index 00000000..6a35da91
--- /dev/null
+++ b/src/common/Makefile.am
@@ -0,0 +1,61 @@
+## Process this file with automake to produce Makefile.in
+
+noinst_LIBRARIES = libxchatcommon.a
+
+INCLUDES = $(COMMON_CFLAGS)
+
+EXTRA_DIST = \
+	cfgfiles.h \
+	chanopt.h \
+	ctcp.h \
+	dcc.h \
+	fe.h \
+	history.h \
+	identd.c \
+	ignore.h \
+	inbound.h \
+	inet.h \
+	make-te.c \
+	modes.h \
+	msproxy.h \
+	network.h \
+	notify.h \
+	outbound.h \
+	plugin.h \
+	plugin-timer.h \
+	proto-irc.h \
+	server.h \
+	servlist.h \
+	ssl.h \
+	ssl.c	\
+	text.h \
+	textenums.h \
+	textevents.h \
+	textevents.in \
+	tree.h \
+	url.h \
+	userlist.h \
+	util.h \
+	xchat.h \
+	xchatc.h \
+	xchat-plugin.h
+
+if USE_OPENSSL
+ssl_c = ssl.c
+endif
+
+if USE_DBUS
+dbusdir = dbus
+libxchatcommon_a_LIBADD =				\
+	$(top_builddir)/src/common/dbus/dbus-*.$(OBJEXT)
+endif
+SUBDIRS = $(dbusdir) .
+
+libxchatcommon_a_SOURCES = cfgfiles.c chanopt.c ctcp.c dcc.c history.c ignore.c \
+	inbound.c modes.c msproxy.c network.c notify.c outbound.c \
+	plugin.c plugin-timer.c proto-irc.c server.c servlist.c $(ssl_c) \
+	text.c tree.c url.c userlist.c util.c xchat.c
+libxchatcommon_a_CFLAGS = $(LIBPROXY_CFLAGS)
+
+textevents: make-te
+	./make-te < textevents.in > textevents.h 2> textenums.h
diff --git a/src/common/cfgfiles.c b/src/common/cfgfiles.c
new file mode 100644
index 00000000..8bb2a61b
--- /dev/null
+++ b/src/common/cfgfiles.c
@@ -0,0 +1,1105 @@
+/* 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 <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "xchat.h"
+#include "cfgfiles.h"
+#include "util.h"
+#include "fe.h"
+#include "text.h"
+#include "xchatc.h"
+
+#ifdef WIN32
+#define XCHAT_DIR "X-Chat 2"
+#else
+#define XCHAT_DIR ".xchat2"
+#endif
+#define DEF_FONT "Monospace 9"
+
+void
+list_addentry (GSList ** list, char *cmd, char *name)
+{
+	struct popup *pop;
+	int cmd_len = 1, name_len;
+
+	/* remove <2.8.0 stuff */
+	if (!strcmp (cmd, "away") && !strcmp (name, "BACK"))
+		return;
+
+	if (cmd)
+		cmd_len = strlen (cmd) + 1;
+	name_len = strlen (name) + 1;
+
+	pop = malloc (sizeof (struct popup) + cmd_len + name_len);
+	pop->name = (char *) pop + sizeof (struct popup);
+	pop->cmd = pop->name + name_len;
+
+	memcpy (pop->name, name, name_len);
+	if (cmd)
+		memcpy (pop->cmd, cmd, cmd_len);
+	else
+		pop->cmd[0] = 0;
+
+	*list = g_slist_append (*list, pop);
+}
+
+/* read it in from a buffer to our linked list */
+
+static void
+list_load_from_data (GSList ** list, char *ibuf, int size)
+{
+	char cmd[384];
+	char name[128];
+	char *buf;
+	int pnt = 0;
+
+	cmd[0] = 0;
+	name[0] = 0;
+
+	while (buf_get_line (ibuf, &buf, &pnt, size))
+	{
+		if (*buf != '#')
+		{
+			if (!strncasecmp (buf, "NAME ", 5))
+			{
+				safe_strcpy (name, buf + 5, sizeof (name));
+			}
+			else if (!strncasecmp (buf, "CMD ", 4))
+			{
+				safe_strcpy (cmd, buf + 4, sizeof (cmd));
+				if (*name)
+				{
+					list_addentry (list, cmd, name);
+					cmd[0] = 0;
+					name[0] = 0;
+				}
+			}
+		}
+	}
+}
+
+void
+list_loadconf (char *file, GSList ** list, char *defaultconf)
+{
+	char filebuf[256];
+	char *ibuf;
+	int fh;
+	struct stat st;
+
+	snprintf (filebuf, sizeof (filebuf), "%s/%s", get_xdir_fs (), file);
+	fh = open (filebuf, O_RDONLY | OFLAGS);
+	if (fh == -1)
+	{
+		if (defaultconf)
+			list_load_from_data (list, defaultconf, strlen (defaultconf));
+		return;
+	}
+	if (fstat (fh, &st) != 0)
+	{
+		perror ("fstat");
+		abort ();
+	}
+
+	ibuf = malloc (st.st_size);
+	read (fh, ibuf, st.st_size);
+	close (fh);
+
+	list_load_from_data (list, ibuf, st.st_size);
+
+	free (ibuf);
+}
+
+void
+list_free (GSList ** list)
+{
+	void *data;
+	while (*list)
+	{
+		data = (void *) (*list)->data;
+		free (data);
+		*list = g_slist_remove (*list, data);
+	}
+}
+
+int
+list_delentry (GSList ** list, char *name)
+{
+	struct popup *pop;
+	GSList *alist = *list;
+
+	while (alist)
+	{
+		pop = (struct popup *) alist->data;
+		if (!strcasecmp (name, pop->name))
+		{
+			*list = g_slist_remove (*list, pop);
+			free (pop);
+			return 1;
+		}
+		alist = alist->next;
+	}
+	return 0;
+}
+
+char *
+cfg_get_str (char *cfg, char *var, char *dest, int dest_len)
+{
+	while (1)
+	{
+		if (!strncasecmp (var, cfg, strlen (var)))
+		{
+			char *value, t;
+			cfg += strlen (var);
+			while (*cfg == ' ')
+				cfg++;
+			if (*cfg == '=')
+				cfg++;
+			while (*cfg == ' ')
+				cfg++;
+			/*while (*cfg == ' ' || *cfg == '=')
+			   cfg++; */
+			value = cfg;
+			while (*cfg != 0 && *cfg != '\n')
+				cfg++;
+			t = *cfg;
+			*cfg = 0;
+			safe_strcpy (dest, value, dest_len);
+			*cfg = t;
+			return cfg;
+		}
+		while (*cfg != 0 && *cfg != '\n')
+			cfg++;
+		if (*cfg == 0)
+			return 0;
+		cfg++;
+		if (*cfg == 0)
+			return 0;
+	}
+}
+
+static int
+cfg_put_str (int fh, char *var, char *value)
+{
+	char buf[512];
+	int len;
+
+	snprintf (buf, sizeof buf, "%s = %s\n", var, value);
+	len = strlen (buf);
+	return (write (fh, buf, len) == len);
+}
+
+int
+cfg_put_color (int fh, int r, int g, int b, char *var)
+{
+	char buf[400];
+	int len;
+
+	snprintf (buf, sizeof buf, "%s = %04x %04x %04x\n", var, r, g, b);
+	len = strlen (buf);
+	return (write (fh, buf, len) == len);
+}
+
+int
+cfg_put_int (int fh, int value, char *var)
+{
+	char buf[400];
+	int len;
+
+	if (value == -1)
+		value = 1;
+
+	snprintf (buf, sizeof buf, "%s = %d\n", var, value);
+	len = strlen (buf);
+	return (write (fh, buf, len) == len);
+}
+
+int
+cfg_get_color (char *cfg, char *var, int *r, int *g, int *b)
+{
+	char str[128];
+
+	if (!cfg_get_str (cfg, var, str, sizeof (str)))
+		return 0;
+
+	sscanf (str, "%04x %04x %04x", r, g, b);
+	return 1;
+}
+
+int
+cfg_get_int_with_result (char *cfg, char *var, int *result)
+{
+	char str[128];
+
+	if (!cfg_get_str (cfg, var, str, sizeof (str)))
+	{
+		*result = 0;
+		return 0;
+	}
+
+	*result = 1;
+	return atoi (str);
+}
+
+int
+cfg_get_int (char *cfg, char *var)
+{
+	char str[128];
+
+	if (!cfg_get_str (cfg, var, str, sizeof (str)))
+		return 0;
+
+	return atoi (str);
+}
+
+char *xdir_fs = NULL;	/* file system encoding */
+char *xdir_utf = NULL;	/* utf-8 encoding */
+
+#ifdef WIN32
+
+#include <windows.h>
+
+static gboolean
+get_reg_str (const char *sub, const char *name, char *out, DWORD len)
+{
+	HKEY hKey;
+	DWORD t;
+
+	if (RegOpenKeyEx (HKEY_CURRENT_USER, sub, 0, KEY_READ, &hKey) ==
+			ERROR_SUCCESS)
+	{
+		if (RegQueryValueEx (hKey, name, NULL, &t, out, &len) != ERROR_SUCCESS ||
+			 t != REG_SZ)
+		{
+			RegCloseKey (hKey);
+			return FALSE;
+		}
+		out[len-1] = 0;
+		RegCloseKey (hKey);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+char *
+get_xdir_fs (void)
+{
+	if (!xdir_fs)
+	{
+		char out[256];
+
+		if (!get_reg_str ("Software\\Microsoft\\Windows\\CurrentVersion\\"
+				"Explorer\\Shell Folders", "AppData", out, sizeof (out)))
+			return "./config";
+		xdir_fs = g_strdup_printf ("%s\\" XCHAT_DIR, out);
+	}
+	return xdir_fs;
+}
+
+#else
+
+char *
+get_xdir_fs (void)
+{
+	if (!xdir_fs)
+		xdir_fs = g_strdup_printf ("%s/" XCHAT_DIR, g_get_home_dir ());
+
+	return xdir_fs;
+}
+
+#endif	/* !WIN32 */
+
+char *
+get_xdir_utf8 (void)
+{
+	if (!xdir_utf)	/* never free this, keep it for program life time */
+		xdir_utf = xchat_filename_to_utf8 (get_xdir_fs (), -1, 0, 0, 0);
+
+	return xdir_utf;
+}
+
+static void
+check_prefs_dir (void)
+{
+	char *dir = get_xdir_fs ();
+	if (access (dir, F_OK) != 0)
+	{
+#ifdef WIN32
+		if (mkdir (dir) != 0)
+#else
+		if (mkdir (dir, S_IRUSR | S_IWUSR | S_IXUSR) != 0)
+#endif
+			fe_message (_("Cannot create ~/.xchat2"), FE_MSG_ERROR);
+	}
+}
+
+static char *
+default_file (void)
+{
+	static char *dfile = 0;
+
+	if (!dfile)
+	{
+		dfile = malloc (strlen (get_xdir_fs ()) + 12);
+		sprintf (dfile, "%s/xchat.conf", get_xdir_fs ());
+	}
+	return dfile;
+}
+
+/* Keep these sorted!! */
+
+const struct prefs vars[] = {
+	{"auto_save", P_OFFINT (autosave), TYPE_BOOL},
+	{"auto_save_url", P_OFFINT (autosave_url), TYPE_BOOL},
+
+	{"away_auto_unmark", P_OFFINT (auto_unmark_away), TYPE_BOOL},
+	{"away_reason", P_OFFSET (awayreason), TYPE_STR},
+	{"away_show_message", P_OFFINT (show_away_message), TYPE_BOOL},
+	{"away_show_once", P_OFFINT (show_away_once), TYPE_BOOL},
+	{"away_size_max", P_OFFINT (away_size_max), TYPE_INT},
+	{"away_timeout", P_OFFINT (away_timeout), TYPE_INT},
+	{"away_track", P_OFFINT (away_track), TYPE_BOOL},
+
+	{"completion_amount", P_OFFINT (completion_amount), TYPE_INT},
+	{"completion_auto", P_OFFINT (nickcompletion), TYPE_BOOL},
+	{"completion_sort", P_OFFINT (completion_sort), TYPE_INT},
+	{"completion_suffix", P_OFFSET (nick_suffix), TYPE_STR},
+
+	{"dcc_auto_chat", P_OFFINT (autodccchat), TYPE_INT},
+	{"dcc_auto_resume", P_OFFINT (autoresume), TYPE_BOOL},
+	{"dcc_auto_send", P_OFFINT (autodccsend), TYPE_INT},
+	{"dcc_blocksize", P_OFFINT (dcc_blocksize), TYPE_INT},
+	{"dcc_completed_dir", P_OFFSET (dcc_completed_dir), TYPE_STR},
+	{"dcc_dir", P_OFFSET (dccdir), TYPE_STR},
+	{"dcc_fast_send", P_OFFINT (fastdccsend), TYPE_BOOL},
+	{"dcc_global_max_get_cps", P_OFFINT (dcc_global_max_get_cps), TYPE_INT},
+	{"dcc_global_max_send_cps", P_OFFINT (dcc_global_max_send_cps), TYPE_INT},
+	{"dcc_ip", P_OFFSET (dcc_ip_str), TYPE_STR},
+	{"dcc_ip_from_server", P_OFFINT (ip_from_server), TYPE_BOOL},
+	{"dcc_max_get_cps", P_OFFINT (dcc_max_get_cps), TYPE_INT},
+	{"dcc_max_send_cps", P_OFFINT (dcc_max_send_cps), TYPE_INT},
+	{"dcc_permissions", P_OFFINT (dccpermissions), TYPE_INT},
+	{"dcc_port_first", P_OFFINT (first_dcc_send_port), TYPE_INT},
+	{"dcc_port_last", P_OFFINT (last_dcc_send_port), TYPE_INT},
+	{"dcc_remove", P_OFFINT (dcc_remove), TYPE_BOOL},
+	{"dcc_save_nick", P_OFFINT (dccwithnick), TYPE_BOOL},
+	{"dcc_send_fillspaces", P_OFFINT (dcc_send_fillspaces), TYPE_BOOL},
+	{"dcc_stall_timeout", P_OFFINT (dccstalltimeout), TYPE_INT},
+	{"dcc_timeout", P_OFFINT (dcctimeout), TYPE_INT},
+
+	{"dnsprogram", P_OFFSET (dnsprogram), TYPE_STR},
+
+	{"flood_ctcp_num", P_OFFINT (ctcp_number_limit), TYPE_INT},
+	{"flood_ctcp_time", P_OFFINT (ctcp_time_limit), TYPE_INT},
+	{"flood_msg_num", P_OFFINT (msg_number_limit), TYPE_INT},
+	{"flood_msg_time", P_OFFINT (msg_time_limit), TYPE_INT},
+
+	{"gui_auto_open_chat", P_OFFINT (autoopendccchatwindow), TYPE_BOOL},
+	{"gui_auto_open_dialog", P_OFFINT (autodialog), TYPE_BOOL},
+	{"gui_auto_open_recv", P_OFFINT (autoopendccrecvwindow), TYPE_BOOL},
+	{"gui_auto_open_send", P_OFFINT (autoopendccsendwindow), TYPE_BOOL},
+	{"gui_dialog_height", P_OFFINT (dialog_height), TYPE_INT},
+	{"gui_dialog_left", P_OFFINT (dialog_left), TYPE_INT},
+	{"gui_dialog_top", P_OFFINT (dialog_top), TYPE_INT},
+	{"gui_dialog_width", P_OFFINT (dialog_width), TYPE_INT},
+	{"gui_hide_menu", P_OFFINT (hidemenu), TYPE_BOOL},
+	{"gui_input_spell", P_OFFINT (gui_input_spell), TYPE_BOOL},
+	{"gui_input_style", P_OFFINT (style_inputbox), TYPE_BOOL},
+	{"gui_join_dialog", P_OFFINT (gui_join_dialog), TYPE_BOOL},
+	{"gui_lagometer", P_OFFINT (lagometer), TYPE_INT},
+	{"gui_mode_buttons", P_OFFINT (chanmodebuttons), TYPE_BOOL},
+	{"gui_pane_left_size", P_OFFINT (gui_pane_left_size), TYPE_INT},
+	{"gui_pane_right_size", P_OFFINT (gui_pane_right_size), TYPE_INT},
+	{"gui_quit_dialog", P_OFFINT (gui_quit_dialog), TYPE_BOOL},
+	{"gui_slist_fav", P_OFFINT (slist_fav), TYPE_INT},
+	{"gui_slist_select", P_OFFINT (slist_select), TYPE_INT},
+	{"gui_slist_skip", P_OFFINT (slist_skip), TYPE_BOOL},
+	{"gui_throttlemeter", P_OFFINT (throttlemeter), TYPE_INT},
+	{"gui_topicbar", P_OFFINT (topicbar), TYPE_BOOL},
+	{"gui_tray", P_OFFINT (gui_tray), TYPE_BOOL},
+	{"gui_tray_flags", P_OFFINT (gui_tray_flags), TYPE_INT},
+	{"gui_tweaks", P_OFFINT (gui_tweaks), TYPE_INT},
+	{"gui_ulist_buttons", P_OFFINT (userlistbuttons), TYPE_BOOL},
+	{"gui_ulist_doubleclick", P_OFFSET (doubleclickuser), TYPE_STR},
+	{"gui_ulist_hide", P_OFFINT (hideuserlist), TYPE_BOOL},
+	{"gui_ulist_left", P_OFFINT (_gui_ulist_left), TYPE_BOOL},	/* obsolete */
+	{"gui_ulist_pos", P_OFFINT (gui_ulist_pos), TYPE_INT},
+	{"gui_ulist_resizable", P_OFFINT (paned_userlist), TYPE_BOOL},
+	{"gui_ulist_show_hosts", P_OFFINT(showhostname_in_userlist), TYPE_BOOL},
+	{"gui_ulist_sort", P_OFFINT (userlist_sort), TYPE_INT},
+	{"gui_ulist_style", P_OFFINT (style_namelistgad), TYPE_BOOL},
+	{"gui_url_mod", P_OFFINT (gui_url_mod), TYPE_INT},
+	{"gui_usermenu", P_OFFINT (gui_usermenu), TYPE_BOOL},
+	{"gui_win_height", P_OFFINT (mainwindow_height), TYPE_INT},
+	{"gui_win_left", P_OFFINT (mainwindow_left), TYPE_INT},
+	{"gui_win_save", P_OFFINT (mainwindow_save), TYPE_BOOL},
+	{"gui_win_state", P_OFFINT (gui_win_state), TYPE_INT},
+	{"gui_win_top", P_OFFINT (mainwindow_top), TYPE_INT},
+	{"gui_win_width", P_OFFINT (mainwindow_width), TYPE_INT},
+
+#ifdef WIN32
+	{"identd", P_OFFINT (identd), TYPE_BOOL},
+#endif
+	{"input_balloon_chans", P_OFFINT (input_balloon_chans), TYPE_BOOL},
+	{"input_balloon_hilight", P_OFFINT (input_balloon_hilight), TYPE_BOOL},
+	{"input_balloon_priv", P_OFFINT (input_balloon_priv), TYPE_BOOL},
+	{"input_balloon_time", P_OFFINT (input_balloon_time), TYPE_INT},
+	{"input_beep_chans", P_OFFINT (input_beep_chans), TYPE_BOOL},
+	{"input_beep_hilight", P_OFFINT (input_beep_hilight), TYPE_BOOL},
+	{"input_beep_msg", P_OFFINT (input_beep_priv), TYPE_BOOL},
+	{"input_command_char", P_OFFSET (cmdchar), TYPE_STR},
+	{"input_filter_beep", P_OFFINT (filterbeep), TYPE_BOOL},
+	{"input_flash_chans", P_OFFINT (input_flash_chans), TYPE_BOOL},
+	{"input_flash_hilight", P_OFFINT (input_flash_hilight), TYPE_BOOL},
+	{"input_flash_priv", P_OFFINT (input_flash_priv), TYPE_BOOL},
+	{"input_perc_ascii", P_OFFINT (perc_ascii), TYPE_BOOL},
+	{"input_perc_color", P_OFFINT (perc_color), TYPE_BOOL},
+	{"input_tray_chans", P_OFFINT (input_tray_chans), TYPE_BOOL},
+	{"input_tray_hilight", P_OFFINT (input_tray_hilight), TYPE_BOOL},
+	{"input_tray_priv", P_OFFINT (input_tray_priv), TYPE_BOOL},
+
+	{"irc_auto_rejoin", P_OFFINT (autorejoin), TYPE_BOOL},
+	{"irc_ban_type", P_OFFINT (bantype), TYPE_INT},
+	{"irc_conf_mode", P_OFFINT (confmode), TYPE_BOOL},
+	{"irc_extra_hilight", P_OFFSET (irc_extra_hilight), TYPE_STR},
+	{"irc_hide_version", P_OFFINT (hidever), TYPE_BOOL},
+	{"irc_id_ntext", P_OFFSET (irc_id_ntext), TYPE_STR},
+	{"irc_id_ytext", P_OFFSET (irc_id_ytext), TYPE_STR},
+	{"irc_invisible", P_OFFINT (invisible), TYPE_BOOL},
+	{"irc_join_delay", P_OFFINT (irc_join_delay), TYPE_INT},
+	{"irc_logging", P_OFFINT (logging), TYPE_BOOL},
+	{"irc_logmask", P_OFFSET (logmask), TYPE_STR},
+	{"irc_nick1", P_OFFSET (nick1), TYPE_STR},
+	{"irc_nick2", P_OFFSET (nick2), TYPE_STR},
+	{"irc_nick3", P_OFFSET (nick3), TYPE_STR},
+	{"irc_nick_hilight", P_OFFSET (irc_nick_hilight), TYPE_STR},
+	{"irc_no_hilight", P_OFFSET (irc_no_hilight), TYPE_STR},
+	{"irc_part_reason", P_OFFSET (partreason), TYPE_STR},
+	{"irc_quit_reason", P_OFFSET (quitreason), TYPE_STR},
+	{"irc_raw_modes", P_OFFINT (raw_modes), TYPE_BOOL},
+	{"irc_real_name", P_OFFSET (realname), TYPE_STR},
+	{"irc_servernotice", P_OFFINT (servernotice), TYPE_BOOL},
+	{"irc_skip_motd", P_OFFINT (skipmotd), TYPE_BOOL},
+	{"irc_user_name", P_OFFSET (username), TYPE_STR},
+	{"irc_wallops", P_OFFINT (wallops), TYPE_BOOL},
+	{"irc_who_join", P_OFFINT (userhost), TYPE_BOOL},
+	{"irc_whois_front", P_OFFINT (irc_whois_front), TYPE_BOOL},
+
+	{"net_auto_reconnect", P_OFFINT (autoreconnect), TYPE_BOOL},
+	{"net_auto_reconnectonfail", P_OFFINT (autoreconnectonfail), TYPE_BOOL},
+	{"net_bind_host", P_OFFSET (hostname), TYPE_STR},
+	{"net_ping_timeout", P_OFFINT (pingtimeout), TYPE_INT},
+	{"net_proxy_auth", P_OFFINT (proxy_auth), TYPE_BOOL},
+	{"net_proxy_host", P_OFFSET (proxy_host), TYPE_STR},
+	{"net_proxy_pass", P_OFFSET (proxy_pass), TYPE_STR},
+	{"net_proxy_port", P_OFFINT (proxy_port), TYPE_INT},
+	{"net_proxy_type", P_OFFINT (proxy_type), TYPE_INT},
+	{"net_proxy_use", P_OFFINT (proxy_use), TYPE_INT},
+	{"net_proxy_user", P_OFFSET (proxy_user), TYPE_STR},
+
+	{"net_reconnect_delay", P_OFFINT (recon_delay), TYPE_INT},
+	{"net_throttle", P_OFFINT (throttle), TYPE_BOOL},
+
+	{"notify_timeout", P_OFFINT (notify_timeout), TYPE_INT},
+	{"notify_whois_online", P_OFFINT (whois_on_notifyonline), TYPE_BOOL},
+
+	{"perl_warnings", P_OFFINT (perlwarnings), TYPE_BOOL},
+
+	{"sound_command", P_OFFSET (soundcmd), TYPE_STR},
+	{"sound_dir", P_OFFSET (sounddir), TYPE_STR},
+	{"stamp_log", P_OFFINT (timestamp_logs), TYPE_BOOL},
+	{"stamp_log_format", P_OFFSET (timestamp_log_format), TYPE_STR},
+	{"stamp_text", P_OFFINT (timestamp), TYPE_BOOL},
+	{"stamp_text_format", P_OFFSET (stamp_format), TYPE_STR},
+
+	{"tab_chans", P_OFFINT (tabchannels), TYPE_BOOL},
+	{"tab_dialogs", P_OFFINT (privmsgtab), TYPE_BOOL},
+	{"tab_layout", P_OFFINT (tab_layout), TYPE_INT},
+	{"tab_new_to_front", P_OFFINT (newtabstofront), TYPE_INT},
+	{"tab_notices", P_OFFINT (notices_tabs), TYPE_BOOL},
+	{"tab_pos", P_OFFINT (tab_pos), TYPE_INT},
+	{"tab_position", P_OFFINT (_tabs_position), TYPE_INT}, /* obsolete */
+	{"tab_server", P_OFFINT (use_server_tab), TYPE_BOOL},
+	{"tab_small", P_OFFINT (tab_small), TYPE_INT},
+	{"tab_sort", P_OFFINT (tab_sort), TYPE_BOOL},
+	{"tab_trunc", P_OFFINT (truncchans), TYPE_INT},
+	{"tab_utils", P_OFFINT (windows_as_tabs), TYPE_BOOL},
+
+	{"text_background", P_OFFSET (background), TYPE_STR},
+	{"text_color_nicks", P_OFFINT (colorednicks), TYPE_BOOL},
+	{"text_font", P_OFFSET (font_normal), TYPE_STR},
+	{"text_indent", P_OFFINT (indent_nicks), TYPE_BOOL},
+	{"text_max_indent", P_OFFINT (max_auto_indent), TYPE_INT},
+	{"text_max_lines", P_OFFINT (max_lines), TYPE_INT},
+	{"text_replay", P_OFFINT (text_replay), TYPE_BOOL},
+	{"text_show_marker", P_OFFINT (show_marker), TYPE_BOOL},
+	{"text_show_sep", P_OFFINT (show_separator), TYPE_BOOL},
+	{"text_stripcolor", P_OFFINT (stripcolor), TYPE_BOOL},
+	{"text_thin_sep", P_OFFINT (thin_separator), TYPE_BOOL},
+	{"text_tint_blue", P_OFFINT (tint_blue), TYPE_INT},
+	{"text_tint_green", P_OFFINT (tint_green), TYPE_INT},
+	{"text_tint_red", P_OFFINT (tint_red), TYPE_INT},
+	{"text_transparent", P_OFFINT (transparent), TYPE_BOOL},
+	{"text_wordwrap", P_OFFINT (wordwrap), TYPE_BOOL},
+
+	{0, 0, 0},
+};
+
+static char *
+convert_with_fallback (const char *str, const char *fallback)
+{
+	char *utf;
+
+	utf = g_locale_to_utf8 (str, -1, 0, 0, 0);
+	if (!utf)
+	{
+		/* this can happen if CHARSET envvar is set wrong */
+		/* maybe it's already utf8 (breakage!) */
+		if (!g_utf8_validate (str, -1, NULL))
+			utf = g_strdup (fallback);
+		else
+			utf = g_strdup (str);
+	}
+
+	return utf;
+}
+
+void
+load_config (void)
+{
+	struct stat st;
+	char *cfg, *sp;
+	const char *username, *realname;
+	int res, val, i, fh;
+
+	check_prefs_dir ();
+	username = g_get_user_name ();
+	if (!username)
+		username = "root";
+
+	realname = g_get_real_name ();
+	if ((realname && realname[0] == 0) || !realname)
+		realname = username;
+
+	username = convert_with_fallback (username, "username");
+	realname = convert_with_fallback (realname, "realname");
+
+	memset (&prefs, 0, sizeof (struct xchatprefs));
+
+	/* put in default values, anything left out is automatically zero */
+	prefs.local_ip = 0xffffffff;
+	prefs.irc_join_delay = 3;
+	prefs.show_marker = 1;
+	prefs.newtabstofront = 2;
+	prefs.completion_amount = 5;
+	prefs.away_timeout = 60;
+	prefs.away_size_max = 300;
+	prefs.away_track = 1;
+	prefs.timestamp_logs = 1;
+	prefs.truncchans = 20;
+	prefs.autoresume = 1;
+	prefs.show_away_once = 1;
+	prefs.indent_nicks = 1;
+	prefs.thin_separator = 1;
+	prefs._tabs_position = 2; /* 2 = left */
+	prefs.fastdccsend = 1;
+	prefs.wordwrap = 1;
+	prefs.autosave = 1;
+	prefs.autodialog = 1;
+	prefs.gui_input_spell = 1;
+	prefs.autoreconnect = 1;
+	prefs.recon_delay = 10;
+	prefs.text_replay = 1;
+	prefs.tabchannels = 1;
+	prefs.tab_layout = 2;	/* 0=Tabs 1=Reserved 2=Tree */
+	prefs.tab_sort = 1;
+	prefs.paned_userlist = 1;
+	prefs.newtabstofront = 2;
+	prefs.use_server_tab = 1;
+	prefs.privmsgtab = 1;
+	/*prefs.style_inputbox = 1;*/
+	prefs.dccpermissions = 0600;
+	prefs.max_lines = 500;
+	prefs.mainwindow_width = 640;
+	prefs.mainwindow_height = 400;
+	prefs.dialog_width = 500;
+	prefs.dialog_height = 256;
+	prefs.gui_join_dialog = 1;
+	prefs.gui_quit_dialog = 1;
+	prefs.dcctimeout = 180;
+	prefs.dccstalltimeout = 60;
+	prefs.notify_timeout = 15;
+	prefs.tint_red =
+		prefs.tint_green =
+		prefs.tint_blue = 195;
+	prefs.auto_indent = 1;
+	prefs.max_auto_indent = 256;
+	prefs.show_separator = 1;
+	prefs.dcc_blocksize = 1024;
+	prefs.throttle = 1;
+	 /*FIXME*/ prefs.msg_time_limit = 30;
+	prefs.msg_number_limit = 5;
+	prefs.ctcp_time_limit = 30;
+	prefs.ctcp_number_limit = 5;
+	prefs.topicbar = 1;
+	prefs.lagometer = 1;
+	prefs.throttlemeter = 1;
+	prefs.autoopendccrecvwindow = 1;
+	prefs.autoopendccsendwindow = 1;
+	prefs.autoopendccchatwindow = 1;
+	prefs.userhost = 1;
+	prefs.gui_url_mod = 4;	/* ctrl */
+	prefs.gui_tray = 1;
+	prefs.gui_pane_left_size = 100;
+	prefs.gui_pane_right_size = 100;
+	prefs.mainwindow_save = 1;
+	prefs.bantype = 2;
+	prefs.input_balloon_time = 20;
+	prefs.input_flash_priv = prefs.input_flash_hilight = 1;
+	prefs.input_tray_priv = prefs.input_tray_hilight = 1;
+	prefs.autodccsend = 2;	/* browse mode */
+#ifdef WIN32
+	prefs.identd = 1;
+#endif
+	strcpy (prefs.stamp_format, "[%H:%M] ");
+	strcpy (prefs.timestamp_log_format, "%b %d %H:%M:%S ");
+	strcpy (prefs.logmask, "%n-%c.log");
+	strcpy (prefs.nick_suffix, ",");
+	strcpy (prefs.cmdchar, "/");
+	strcpy (prefs.nick1, username);
+	strcpy (prefs.nick2, username);
+	strcat (prefs.nick2, "_");
+	strcpy (prefs.nick3, username);
+	strcat (prefs.nick3, "__");
+	strcpy (prefs.realname, realname);
+	strcpy (prefs.username, username);
+#ifdef WIN32
+	strcpy (prefs.sounddir, "./sounds");
+	{
+		char out[256];
+
+		if (get_reg_str ("Software\\Microsoft\\Windows\\CurrentVersion\\"
+						 "Explorer\\Shell Folders", "Personal", out, sizeof (out)))
+			snprintf (prefs.dccdir, sizeof (prefs.dccdir), "%s\\Downloads", out);
+		else
+			snprintf (prefs.dccdir, sizeof (prefs.dccdir), "%s\\Downloads", get_xdir_utf8 ());
+	}
+#else
+	snprintf (prefs.sounddir, sizeof (prefs.sounddir), "%s/sounds", get_xdir_utf8 ());
+	snprintf (prefs.dccdir, sizeof (prefs.dccdir), "%s/downloads", get_xdir_utf8 ());
+#endif
+	strcpy (prefs.doubleclickuser, "QUOTE WHOIS %s %s");
+	strcpy (prefs.awayreason, _("I'm busy"));
+	strcpy (prefs.quitreason, _("Leaving"));
+	strcpy (prefs.partreason, prefs.quitreason);
+	strcpy (prefs.font_normal, DEF_FONT);
+	strcpy (prefs.dnsprogram, "host");
+	strcpy (prefs.irc_no_hilight, "NickServ,ChanServ");
+
+	g_free ((char *)username);
+	g_free ((char *)realname);
+
+	fh = open (default_file (), OFLAGS | O_RDONLY);
+	if (fh != -1)
+	{
+		fstat (fh, &st);
+		cfg = malloc (st.st_size + 1);
+		cfg[0] = '\0';
+		i = read (fh, cfg, st.st_size);
+		if (i >= 0)
+			cfg[i] = '\0';					/* make sure cfg is NULL terminated */
+		close (fh);
+		i = 0;
+		do
+		{
+			switch (vars[i].type)
+			{
+			case TYPE_STR:
+				cfg_get_str (cfg, vars[i].name, (char *) &prefs + vars[i].offset,
+								 vars[i].len);
+				break;
+			case TYPE_BOOL:
+			case TYPE_INT:
+				val = cfg_get_int_with_result (cfg, vars[i].name, &res);
+				if (res)
+					*((int *) &prefs + vars[i].offset) = val;
+				break;
+			}
+			i++;
+		}
+		while (vars[i].name);
+
+		free (cfg);
+
+	} else
+	{
+#ifndef WIN32
+#ifndef __EMX__
+		/* OS/2 uses UID 0 all the time */
+		if (getuid () == 0)
+			fe_message (_("* Running IRC as root is stupid! You should\n"
+							"  create a User Account and use that to login.\n"), FE_MSG_WARN|FE_MSG_WAIT);
+#endif
+#endif /* !WIN32 */
+
+		mkdir_utf8 (prefs.dccdir);
+		mkdir_utf8 (prefs.dcc_completed_dir);
+	}
+	if (prefs.mainwindow_height < 138)
+		prefs.mainwindow_height = 138;
+	if (prefs.mainwindow_width < 106)
+		prefs.mainwindow_width = 106;
+
+	sp = strchr (prefs.username, ' ');
+	if (sp)
+		sp[0] = 0;	/* spaces in username would break the login */
+
+	/* try to make sense of old ulist/tree position settings */
+	if (prefs.gui_ulist_pos == 0)
+	{
+		prefs.gui_ulist_pos = 3;	/* top right */
+		if (prefs._gui_ulist_left)
+			prefs.gui_ulist_pos = 2;	/* bottom left */
+
+		switch (prefs._tabs_position)
+		{
+		case 0:
+			prefs.tab_pos = 6; /* bottom */
+			break;
+		case 1:
+			prefs.tab_pos = 5;	/* top */
+			break;
+		case 2:
+			prefs.tab_pos = 1; 	/* left */
+			break;
+		case 3:
+			prefs.tab_pos = 4; 	/* right */
+			break;
+		case 4:
+			prefs.tab_pos = 1;	/* (hidden)left */
+			break;
+		case 5:
+			if (prefs._gui_ulist_left)
+			{
+				prefs.tab_pos = 1; 	/* above ulist left */
+				prefs.gui_ulist_pos = 2;
+			}
+			else
+			{
+				prefs.tab_pos = 3; 	/* above ulist right */
+				prefs.gui_ulist_pos = 4;
+			}
+			break;
+		}
+	}
+}
+
+int
+save_config (void)
+{
+	int fh, i;
+	char *new_config, *config;
+
+	check_prefs_dir ();
+
+	config = default_file ();
+	new_config = malloc (strlen (config) + 5);
+	strcpy (new_config, config);
+	strcat (new_config, ".new");
+	
+	fh = open (new_config, OFLAGS | O_TRUNC | O_WRONLY | O_CREAT, 0600);
+	if (fh == -1)
+	{
+		free (new_config);
+		return 0;
+	}
+
+	if (!cfg_put_str (fh, "version", PACKAGE_VERSION))
+	{
+		free (new_config);
+		return 0;
+	}
+		
+	i = 0;
+	do
+	{
+		switch (vars[i].type)
+		{
+		case TYPE_STR:
+			if (!cfg_put_str (fh, vars[i].name, (char *) &prefs + vars[i].offset))
+			{
+				free (new_config);
+				return 0;
+			}
+			break;
+		case TYPE_INT:
+		case TYPE_BOOL:
+			if (!cfg_put_int (fh, *((int *) &prefs + vars[i].offset), vars[i].name))
+			{
+				free (new_config);
+				return 0;
+			}
+		}
+		i++;
+	}
+	while (vars[i].name);
+
+	if (close (fh) == -1)
+	{
+		free (new_config);
+		return 0;
+	}
+
+#ifdef WIN32
+	unlink (config);	/* win32 can't rename to an existing file */
+#endif
+	if (rename (new_config, config) == -1)
+	{
+		free (new_config);
+		return 0;
+	}
+	free (new_config);
+
+	return 1;
+}
+
+static void
+set_showval (session *sess, const struct prefs *var, char *tbuf)
+{
+	int len, dots, j;
+
+	len = strlen (var->name);
+	memcpy (tbuf, var->name, len);
+	dots = 29 - len;
+	if (dots < 0)
+		dots = 0;
+	tbuf[len++] = '\003';
+	tbuf[len++] = '2';
+	for (j=0;j<dots;j++)
+		tbuf[j + len] = '.';
+	len += j;
+	switch (var->type)
+	{
+	case TYPE_STR:
+		sprintf (tbuf + len, "\0033:\017 %s\n",
+					(char *) &prefs + var->offset);
+		break;
+	case TYPE_INT:
+		sprintf (tbuf + len, "\0033:\017 %d\n",
+					*((int *) &prefs + var->offset));
+		break;
+	case TYPE_BOOL:
+		if (*((int *) &prefs + var->offset))
+			sprintf (tbuf + len, "\0033:\017 %s\n", "ON");
+		else
+			sprintf (tbuf + len, "\0033:\017 %s\n", "OFF");
+		break;
+	}
+	PrintText (sess, tbuf);
+}
+
+static void
+set_list (session * sess, char *tbuf)
+{
+	int i;
+
+	i = 0;
+	do
+	{
+		set_showval (sess, &vars[i], tbuf);
+		i++;
+	}
+	while (vars[i].name);
+}
+
+int
+cfg_get_bool (char *var)
+{
+	int i = 0;
+
+	do
+	{
+		if (!strcasecmp (var, vars[i].name))
+		{
+			return *((int *) &prefs + vars[i].offset);
+		}
+		i++;
+	}
+	while (vars[i].name);
+
+	return -1;
+}
+
+int
+cmd_set (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int wild = FALSE;
+	int or = FALSE;
+	int off = FALSE;
+	int quiet = FALSE;
+	int erase = FALSE;
+	int i = 0, finds = 0, found;
+	int idx = 2;
+	char *var, *val;
+
+	if (strcasecmp (word[2], "-e") == 0)
+	{
+		idx++;
+		erase = TRUE;
+	}
+
+	/* turn a bit OFF */
+	if (strcasecmp (word[idx], "-off") == 0)
+	{
+		idx++;
+		off = TRUE;
+	}
+
+	/* turn a bit ON */
+	if (strcasecmp (word[idx], "-or") == 0 || strcasecmp (word[idx], "-on") == 0)
+	{
+		idx++;
+		or = TRUE;
+	}
+
+	if (strcasecmp (word[idx], "-quiet") == 0)
+	{
+		idx++;
+		quiet = TRUE;
+	}
+
+	var = word[idx];
+	val = word_eol[idx+1];
+
+	if (!*var)
+	{
+		set_list (sess, tbuf);
+		return TRUE;
+	}
+
+	if ((strchr (var, '*') || strchr (var, '?')) && !*val)
+		wild = TRUE;
+
+	if (*val == '=')
+		val++;
+
+	do
+	{
+		if (wild)
+			found = !match (var, vars[i].name);
+		else
+			found = strcasecmp (var, vars[i].name);
+
+		if (found == 0)
+		{
+			finds++;
+			switch (vars[i].type)
+			{
+			case TYPE_STR:
+				if (erase || *val)
+				{
+					strncpy ((char *) &prefs + vars[i].offset, val, vars[i].len);
+					((char *) &prefs)[vars[i].offset + vars[i].len - 1] = 0;
+					if (!quiet)
+						PrintTextf (sess, "%s set to: %s\n", var, (char *) &prefs + vars[i].offset);
+				} else
+				{
+					set_showval (sess, &vars[i], tbuf);
+				}
+				break;
+			case TYPE_INT:
+			case TYPE_BOOL:
+				if (*val)
+				{
+					if (vars[i].type == TYPE_BOOL)
+					{
+						if (atoi (val))
+							*((int *) &prefs + vars[i].offset) = 1;
+						else
+							*((int *) &prefs + vars[i].offset) = 0;
+						if (!strcasecmp (val, "YES") || !strcasecmp (val, "ON"))
+							*((int *) &prefs + vars[i].offset) = 1;
+						if (!strcasecmp (val, "NO") || !strcasecmp (val, "OFF"))
+							*((int *) &prefs + vars[i].offset) = 0;
+					} else
+					{
+						if (or)
+							*((int *) &prefs + vars[i].offset) |= atoi (val);
+						else if (off)
+							*((int *) &prefs + vars[i].offset) &= ~(atoi (val));
+						else
+							*((int *) &prefs + vars[i].offset) = atoi (val);
+					}
+					if (!quiet)
+						PrintTextf (sess, "%s set to: %d\n", var,
+										*((int *) &prefs + vars[i].offset));
+				} else
+				{
+					set_showval (sess, &vars[i], tbuf);
+				}
+				break;
+			}
+		}
+		i++;
+	}
+	while (vars[i].name);
+
+	if (!finds && !quiet)
+		PrintText (sess, "No such variable.\n");
+
+	return TRUE;
+}
+
+int
+xchat_open_file (char *file, int flags, int mode, int xof_flags)
+{
+	char buf[1024];
+
+	if (xof_flags & XOF_FULLPATH)
+	{
+		if (xof_flags & XOF_DOMODE)
+			return open (file, flags | OFLAGS, mode);
+		else
+			return open (file, flags | OFLAGS);
+	}
+
+	snprintf (buf, sizeof (buf), "%s/%s", get_xdir_fs (), file);
+	if (xof_flags & XOF_DOMODE)
+		return open (buf, flags | OFLAGS, mode);
+	else
+		return open (buf, flags | OFLAGS);
+}
+
+FILE *
+xchat_fopen_file (const char *file, const char *mode, int xof_flags)
+{
+	char buf[1024];
+
+	if (xof_flags & XOF_FULLPATH)
+		return fopen (file, mode);
+
+	snprintf (buf, sizeof (buf), "%s/%s", get_xdir_fs (), file);
+	return fopen (buf, mode);
+}
diff --git a/src/common/cfgfiles.h b/src/common/cfgfiles.h
new file mode 100644
index 00000000..984b9472
--- /dev/null
+++ b/src/common/cfgfiles.h
@@ -0,0 +1,55 @@
+/* cfgfiles.h */
+
+#ifndef XCHAT_CFGFILES_H
+#define XCHAT_CFGFILES_H
+
+#include "xchat.h"
+
+extern char *xdir_fs;
+extern char *xdir_utf;
+
+char *cfg_get_str (char *cfg, char *var, char *dest, int dest_len);
+int cfg_get_bool (char *var);
+int cfg_get_int_with_result (char *cfg, char *var, int *result);
+int cfg_get_int (char *cfg, char *var);
+int cfg_put_int (int fh, int value, char *var);
+int cfg_get_color (char *cfg, char *var, int *r, int *g, int *b);
+int cfg_put_color (int fh, int r, int g, int b, char *var);
+char *get_xdir_fs (void);
+char *get_xdir_utf8 (void);
+void load_config (void);
+int save_config (void);
+void list_free (GSList ** list);
+void list_loadconf (char *file, GSList ** list, char *defaultconf);
+int list_delentry (GSList ** list, char *name);
+void list_addentry (GSList ** list, char *cmd, char *name);
+int cmd_set (session *sess, char *tbuf, char *word[], char *word_eol[]);
+int xchat_open_file (char *file, int flags, int mode, int xof_flags);
+FILE *xchat_fopen_file (const char *file, const char *mode, int xof_flags);
+#define XOF_DOMODE 1
+#define XOF_FULLPATH 2
+
+#define STRUCT_OFFSET_STR(type,field) \
+( (unsigned int) (((char *) (&(((type *) NULL)->field)))- ((char *) NULL)) )
+
+#define STRUCT_OFFSET_INT(type,field) \
+( (unsigned int) (((int *) (&(((type *) NULL)->field)))- ((int *) NULL)) )
+
+#define P_OFFSET(field) STRUCT_OFFSET_STR(struct xchatprefs, field),sizeof(prefs.field)
+#define P_OFFSETNL(field) STRUCT_OFFSET_STR(struct xchatprefs, field)
+#define P_OFFINT(field) STRUCT_OFFSET_INT(struct xchatprefs, field),0
+#define P_OFFINTNL(field) STRUCT_OFFSET_INT(struct xchatprefs, field)
+
+struct prefs
+{
+	char *name;
+	unsigned short offset;
+	unsigned short len;
+	unsigned short type;
+};
+
+#define TYPE_STR 0
+#define TYPE_INT 1
+#define TYPE_BOOL 2
+
+#endif
diff --git a/src/common/chanopt.c b/src/common/chanopt.c
new file mode 100644
index 00000000..a4fd8faa
--- /dev/null
+++ b/src/common/chanopt.c
@@ -0,0 +1,431 @@
+/* per-channel/dialog settings :: /CHANOPT */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "xchat.h"
+
+#include "cfgfiles.h"
+#include "server.h"
+#include "text.h"
+#include "util.h"
+#include "xchatc.h"
+
+
+static GSList *chanopt_list = NULL;
+static gboolean chanopt_open = FALSE;
+static gboolean chanopt_changed = FALSE;
+
+
+typedef struct
+{
+	char *name;
+	char *alias;	/* old names from 2.8.4 */
+	int offset;
+} channel_options;
+
+#define S_F(xx) STRUCT_OFFSET_STR(struct session,xx)
+
+static const channel_options chanopt[] =
+{
+	{"alert_beep", "BEEP", S_F(alert_beep)},
+	{"alert_taskbar", NULL, S_F(alert_taskbar)},
+	{"alert_tray", "TRAY", S_F(alert_tray)},
+
+	{"text_hidejoinpart", "CONFMODE", S_F(text_hidejoinpart)},
+	{"text_logging", NULL, S_F(text_logging)},
+	{"text_scrollback", NULL, S_F(text_scrollback)},
+};
+
+#undef S_F
+
+static char *
+chanopt_value (guint8 val)
+{
+	switch (val)
+	{
+	case SET_OFF:
+		return "OFF";
+	case SET_ON:
+		return "ON";
+	default:
+		return "{unset}";
+	}
+}
+
+/* handle the /CHANOPT command */
+
+int
+chanopt_command (session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int dots, i = 0, j, p = 0;
+	guint8 val;
+	int offset = 2;
+	char *find;
+	gboolean quiet = FALSE;
+	int newval = -1;
+
+	if (!strcmp (word[2], "-quiet"))
+	{
+		quiet = TRUE;
+		offset++;
+	}
+
+	find = word[offset++];
+
+	if (word[offset][0])
+	{
+		if (!strcasecmp (word[offset], "ON"))
+			newval = 1;
+		else if (!strcasecmp (word[offset], "OFF"))
+			newval = 0;
+		else if (word[offset][0] == 'u')
+			newval = SET_DEFAULT;
+		else
+			newval = atoi (word[offset]);
+	}
+
+	if (!quiet)
+		PrintTextf (sess, "\002Network\002: %s \002Channel\002: %s\n",
+						sess->server->network ? server_get_network (sess->server, TRUE) : _("<none>"),
+						sess->channel[0] ? sess->channel : _("<none>"));
+
+	while (i < sizeof (chanopt) / sizeof (channel_options))
+	{
+		if (find[0] == 0 || match (find, chanopt[i].name) || (chanopt[i].alias && match (find, chanopt[i].alias)))
+		{
+			if (newval != -1)	/* set new value */
+			{
+				*(guint8 *)G_STRUCT_MEMBER_P(sess, chanopt[i].offset) = newval;
+			}
+
+			if (!quiet)	/* print value */
+			{
+				strcpy (tbuf, chanopt[i].name);
+				p = strlen (tbuf);
+
+				tbuf[p++] = 3;
+				tbuf[p++] = '2';
+
+				dots = 20 - strlen (chanopt[i].name);
+
+				for (j = 0; j < dots; j++)
+					tbuf[p++] = '.';
+				tbuf[p++] = 0;
+
+				val = G_STRUCT_MEMBER (guint8, sess, chanopt[i].offset);
+				PrintTextf (sess, "%s\0033:\017 %s", tbuf, chanopt_value (val));
+			}
+		}
+		i++;
+	}
+
+	return TRUE;
+}
+
+/* is a per-channel setting set? Or is it UNSET and
+ * the global version is set? */
+
+gboolean
+chanopt_is_set (unsigned int global, guint8 per_chan_setting)
+{
+	if (per_chan_setting == SET_DEFAULT)
+		return global;
+
+	return per_chan_setting;
+}
+
+/* additive version */
+
+gboolean
+chanopt_is_set_a (unsigned int global, guint8 per_chan_setting)
+{
+	if (per_chan_setting == SET_DEFAULT)
+		return global;
+
+	return per_chan_setting || global;
+}
+
+/* === below is LOADING/SAVING stuff only === */
+
+typedef struct
+{
+	/* Per-Channel Alerts */
+	/* use a byte, because we need a pointer to each element */
+	guint8 alert_beep;
+	guint8 alert_taskbar;
+	guint8 alert_tray;
+
+	/* Per-Channel Settings */
+	guint8 text_hidejoinpart;
+	guint8 text_logging;
+	guint8 text_scrollback;
+
+	char *network;
+	char *channel;
+
+} chanopt_in_memory;
+
+
+static chanopt_in_memory *
+chanopt_find (char *network, char *channel, gboolean add_new)
+{
+	GSList *list;
+	chanopt_in_memory *co;
+	int i;
+
+	for (list = chanopt_list; list; list = list->next)
+	{
+		co = list->data;
+		if (!strcasecmp (co->channel, channel) &&
+			 !strcasecmp (co->network, network))
+			return co;
+	}
+
+	if (!add_new)
+		return NULL;
+
+	/* allocate a new one */
+	co = g_malloc0 (sizeof (chanopt_in_memory));
+	co->channel = g_strdup (channel);
+	co->network = g_strdup (network);
+
+	/* set all values to SET_DEFAULT */
+	i = 0;
+	while (i < sizeof (chanopt) / sizeof (channel_options))
+	{
+		*(guint8 *)G_STRUCT_MEMBER_P(co, chanopt[i].offset) = SET_DEFAULT;
+		i++;
+	}
+
+	chanopt_list = g_slist_prepend (chanopt_list, co);
+	chanopt_changed = TRUE;
+
+	return co;
+}
+
+static void
+chanopt_add_opt (chanopt_in_memory *co, char *var, int new_value)
+{
+	int i;
+
+	i = 0;
+	while (i < sizeof (chanopt) / sizeof (channel_options))
+	{
+		if (!strcmp (var, chanopt[i].name))
+		{
+			*(guint8 *)G_STRUCT_MEMBER_P(co, chanopt[i].offset) = new_value;
+
+		}
+		i++;
+	}
+}
+
+/* load chanopt.conf from disk into our chanopt_list GSList */
+
+static void
+chanopt_load_all (void)
+{
+	int fh;
+	char buf[256];
+	char *eq;
+	char *network = NULL;
+	chanopt_in_memory *current = NULL;
+
+	/* 1. load the old file into our GSList */
+	fh = xchat_open_file ("chanopt.conf", O_RDONLY, 0, 0);
+	if (fh != -1)
+	{
+		while (waitline (fh, buf, sizeof buf, FALSE) != -1)
+		{
+			eq = strchr (buf, '=');
+			if (!eq)
+				continue;
+			eq[0] = 0;
+
+			if (eq != buf && eq[-1] == ' ')
+				eq[-1] = 0;
+
+			if (!strcmp (buf, "network"))
+			{
+				g_free (network);
+				network = g_strdup (eq + 2);
+			}
+			else if (!strcmp (buf, "channel"))
+			{
+				current = chanopt_find (network, eq + 2, TRUE);
+				chanopt_changed = FALSE;
+			}
+			else
+			{
+				if (current)
+					chanopt_add_opt (current, buf, atoi (eq + 2));
+			}
+
+		}
+		close (fh);
+		g_free (network);
+	}
+}
+
+void
+chanopt_load (session *sess)
+{
+	int i;
+	guint8 val;
+	chanopt_in_memory *co;
+	char *network;
+
+	if (sess->channel[0] == 0)
+		return;
+
+	network = server_get_network (sess->server, FALSE);
+	if (!network)
+		return;
+
+	if (!chanopt_open)
+	{
+		chanopt_open = TRUE;
+		chanopt_load_all ();
+	}
+
+	co = chanopt_find (network, sess->channel, FALSE);
+	if (!co)
+		return;
+
+	/* fill in all the sess->xxxxx fields */
+	i = 0;
+	while (i < sizeof (chanopt) / sizeof (channel_options))
+	{
+		val = G_STRUCT_MEMBER(guint8, co, chanopt[i].offset);
+		*(guint8 *)G_STRUCT_MEMBER_P(sess, chanopt[i].offset) = val;
+		i++;
+	}
+}
+
+void
+chanopt_save (session *sess)
+{
+	int i;
+	guint8 vals;
+	guint8 valm;
+	chanopt_in_memory *co;
+	char *network;
+
+	if (sess->channel[0] == 0)
+		return;
+
+	network = server_get_network (sess->server, FALSE);
+	if (!network)
+		return;
+
+	/* 2. reconcile sess with what we loaded from disk */
+
+	co = chanopt_find (network, sess->channel, TRUE);
+
+	i = 0;
+	while (i < sizeof (chanopt) / sizeof (channel_options))
+	{
+		vals = G_STRUCT_MEMBER(guint8, sess, chanopt[i].offset);
+		valm = G_STRUCT_MEMBER(guint8, co, chanopt[i].offset);
+
+		if (vals != valm)
+		{
+			*(guint8 *)G_STRUCT_MEMBER_P(co, chanopt[i].offset) = vals;
+			chanopt_changed = TRUE;
+		}
+
+		i++;
+	}
+}
+
+static void
+chanopt_save_one_channel (chanopt_in_memory *co, int fh)
+{
+	int i;
+	char buf[256];
+	guint8 val;
+
+	snprintf (buf, sizeof (buf), "%s = %s\n", "network", co->network);
+	write (fh, buf, strlen (buf));
+
+	snprintf (buf, sizeof (buf), "%s = %s\n", "channel", co->channel);
+	write (fh, buf, strlen (buf));
+
+	i = 0;
+	while (i < sizeof (chanopt) / sizeof (channel_options))
+	{
+		val = G_STRUCT_MEMBER (guint8, co, chanopt[i].offset);
+		if (val != SET_DEFAULT)
+		{
+			snprintf (buf, sizeof (buf), "%s = %d\n", chanopt[i].name, val);
+			write (fh, buf, strlen (buf));
+		}
+		i++;
+	}
+}
+
+void
+chanopt_save_all (void)
+{
+	int i;
+	int num_saved;
+	int fh;
+	GSList *list;
+	chanopt_in_memory *co;
+	guint8 val;
+
+	if (!chanopt_list || !chanopt_changed)
+	{
+		return;
+	}
+
+	fh = xchat_open_file ("chanopt.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE);
+	if (fh == -1)
+	{
+		return;
+	}
+
+	for (num_saved = 0, list = chanopt_list; list; list = list->next)
+	{
+		co = list->data;
+
+		i = 0;
+		while (i < sizeof (chanopt) / sizeof (channel_options))
+		{
+			val = G_STRUCT_MEMBER (guint8, co, chanopt[i].offset);
+			/* not using global/default setting, must save */
+			if (val != SET_DEFAULT)
+			{
+				if (num_saved != 0)
+					write (fh, "\n", 1);
+
+				chanopt_save_one_channel (co, fh);
+				num_saved++;
+				goto cont;
+			}
+			i++;
+		}
+
+cont:
+		g_free (co->network);
+		g_free (co->channel);
+		g_free (co);
+	}
+
+	close (fh);
+
+	/* we're quiting, no need to free */
+
+	/*g_slist_free (chanopt_list);
+	chanopt_list = NULL;
+
+	chanopt_open = FALSE;
+	chanopt_changed = FALSE;*/
+}
diff --git a/src/common/chanopt.h b/src/common/chanopt.h
new file mode 100644
index 00000000..90ca86c3
--- /dev/null
+++ b/src/common/chanopt.h
@@ -0,0 +1,6 @@
+int chanopt_command (session *sess, char *tbuf, char *word[], char *word_eol[]);
+gboolean chanopt_is_set (unsigned int global, guint8 per_chan_setting);
+gboolean chanopt_is_set_a (unsigned int global, guint8 per_chan_setting);
+void chanopt_save_all (void);
+void chanopt_save (session *sess);
+void chanopt_load (session *sess);
diff --git a/src/common/ctcp.c b/src/common/ctcp.c
new file mode 100644
index 00000000..574cda79
--- /dev/null
+++ b/src/common/ctcp.c
@@ -0,0 +1,191 @@
+/* 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 <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "xchat.h"
+#include "cfgfiles.h"
+#include "util.h"
+#include "modes.h"
+#include "outbound.h"
+#include "ignore.h"
+#include "inbound.h"
+#include "dcc.h"
+#include "text.h"
+#include "ctcp.h"
+#include "server.h"
+#include "xchatc.h"
+
+
+static void
+ctcp_reply (session *sess, char *nick, char *word[], char *word_eol[],
+				char *conf)
+{
+	char tbuf[4096];	/* can receive 2048 from IRC, so this is enough */
+
+	conf = strdup (conf);
+	/* process %C %B etc */
+	check_special_chars (conf, TRUE);
+	auto_insert (tbuf, sizeof (tbuf), conf, word, word_eol, "", "", word_eol[5],
+					 server_get_network (sess->server, TRUE), "", "", nick);
+	free (conf);
+	handle_command (sess, tbuf, FALSE);
+}
+
+static int
+ctcp_check (session *sess, char *nick, char *word[], char *word_eol[],
+				char *ctcp)
+{
+	int ret = 0;
+	char *po;
+	struct popup *pop;
+	GSList *list = ctcp_list;
+
+	po = strchr (ctcp, '\001');
+	if (po)
+		*po = 0;
+
+	po = strchr (word_eol[5], '\001');
+	if (po)
+		*po = 0;
+
+	while (list)
+	{
+		pop = (struct popup *) list->data;
+		if (!strcasecmp (ctcp, pop->name))
+		{
+			ctcp_reply (sess, nick, word, word_eol, pop->cmd);
+			ret = 1;
+		}
+		list = list->next;
+	}
+	return ret;
+}
+
+void
+ctcp_handle (session *sess, char *to, char *nick, char *ip,
+				 char *msg, char *word[], char *word_eol[], int id)
+{
+	char *po;
+	session *chansess;
+	server *serv = sess->server;
+	char outbuf[1024];
+	int ctcp_offset = 2;
+
+	if (serv->have_idmsg && (word[4][1] == '+' || word[4][1] == '-') )
+			ctcp_offset = 3;
+
+	/* consider DCC to be different from other CTCPs */
+	if (!strncasecmp (msg, "DCC", 3))
+	{
+		/* but still let CTCP replies override it */
+		if (!ctcp_check (sess, nick, word, word_eol, word[4] + ctcp_offset))
+		{
+			if (!ignore_check (word[1], IG_DCC))
+				handle_dcc (sess, nick, word, word_eol);
+		}
+		return;
+	}
+
+	/* consider ACTION to be different from other CTCPs. Check
+      ignore as if it was a PRIV/CHAN. */
+	if (!strncasecmp (msg, "ACTION ", 7))
+	{
+		if (is_channel (serv, to))
+		{
+			/* treat a channel action as a CHAN */
+			if (ignore_check (word[1], IG_CHAN))
+				return;
+		} else
+		{
+			/* treat a private action as a PRIV */
+			if (ignore_check (word[1], IG_PRIV))
+				return;
+		}
+
+		/* but still let CTCP replies override it */
+		if (ctcp_check (sess, nick, word, word_eol, word[4] + ctcp_offset))
+			goto generic;
+
+		inbound_action (sess, to, nick, ip, msg + 7, FALSE, id);
+		return;
+	}
+
+	if (ignore_check (word[1], IG_CTCP))
+		return;
+
+	if (!strcasecmp (msg, "VERSION") && !prefs.hidever)
+	{
+		snprintf (outbuf, sizeof (outbuf), "VERSION xchat "PACKAGE_VERSION" %s",
+					 get_cpu_str ());
+		serv->p_nctcp (serv, nick, outbuf);
+	}
+
+	if (!ctcp_check (sess, nick, word, word_eol, word[4] + ctcp_offset))
+	{
+		if (!strncasecmp (msg, "SOUND", 5))
+		{
+			po = strchr (word[5], '\001');
+			if (po)
+				po[0] = 0;
+
+			if (is_channel (sess->server, to))
+			{
+				chansess = find_channel (sess->server, to);
+				if (!chansess)
+					chansess = sess;
+
+				EMIT_SIGNAL (XP_TE_CTCPSNDC, chansess, word[5],
+								 nick, to, NULL, 0);
+			} else
+			{
+				EMIT_SIGNAL (XP_TE_CTCPSND, sess->server->front_session, word[5],
+								 nick, NULL, NULL, 0);
+			}
+
+			/* don't let IRCers specify path */
+#ifdef WIN32
+			if (strchr (word[5], '/') == NULL && strchr (word[5], '\\') == NULL)
+#else
+			if (strchr (word[5], '/') == NULL)
+#endif
+				sound_play (word[5], TRUE);
+			return;
+		}
+	}
+
+generic:
+	po = strchr (msg, '\001');
+	if (po)
+		po[0] = 0;
+
+	if (!is_channel (sess->server, to))
+	{
+		EMIT_SIGNAL (XP_TE_CTCPGEN, sess->server->front_session, msg, nick,
+						 NULL, NULL, 0);
+	} else
+	{
+		chansess = find_channel (sess->server, to);
+		if (!chansess)
+			chansess = sess;
+		EMIT_SIGNAL (XP_TE_CTCPGENC, chansess, msg, nick, to, NULL, 0);
+	}
+}
diff --git a/src/common/ctcp.h b/src/common/ctcp.h
new file mode 100644
index 00000000..9acd80e3
--- /dev/null
+++ b/src/common/ctcp.h
@@ -0,0 +1,6 @@
+#ifndef XCHAT_CTCP_H
+#define XCHAT_CTCP_H
+
+void ctcp_handle (session *sess, char *to, char *nick, char *ip, char *msg, char *word[], char *word_eol[], int id);
+
+#endif
diff --git a/src/common/dbus/Makefile.am b/src/common/dbus/Makefile.am
new file mode 100644
index 00000000..05ee9de6
--- /dev/null
+++ b/src/common/dbus/Makefile.am
@@ -0,0 +1,56 @@
+noinst_LIBRARIES = libxchatdbus.a
+libxchatdbus_a_SOURCES =			\
+	dbus-plugin.c				\
+	dbus-plugin.h				\
+	dbus-client.c				\
+	dbus-client.h
+
+EXTRA_DIST =				\
+	remote-object.xml		\
+	apps_xchat_url_handler.schemas	\
+	marshallers.list		\
+	example.py			\
+	org.xchat.service.service.in \
+	README
+
+BUILT_SOURCES =				\
+	marshallers.h			\
+	remote-object-glue.h
+
+CLEANFILES = $(BUILT_SOURCES)
+
+INCLUDES = $(COMMON_CFLAGS) $(DBUS_CFLAGS)
+
+noinst_PROGRAMS = example
+example_SOURCES = example.c 
+example_LDADD = $(DBUS_LIBS) $(GLIB_LIBS)
+
+remote-object-glue.h: remote-object.xml
+	$(LIBTOOL) --mode=execute $(DBUS_BINDING_TOOL) --prefix=remote_object --mode=glib-server --output=$@ $<
+
+marshallers.h: marshallers.list
+	$(LIBTOOL) --mode=execute $(GLIB_GENMARSHAL)  --header --body $< > $@
+
+# Dbus service file
+servicedir = $(DBUS_SERVICES_DIR)
+service_in_files = org.xchat.service.service.in
+service_DATA = $(service_in_files:.service.in=.service)
+
+# Rule to make the service file with bindir expanded
+$(service_DATA): $(service_in_files) Makefile
+	@sed -e "s|\@bindir\@|$(bindir)|" $< > $@
+
+if DO_GCONF
+GCONF_SCHEMA_CONFIG_SOURCE = `$(GCONFTOOL) --get-default-source`
+GCONF_SCHEMA_FILE_DIR = $(sysconfdir)/gconf/schemas
+schemadir = $(GCONF_SCHEMA_FILE_DIR)
+schema_DATA = apps_xchat_url_handler.schemas
+install-data-local:
+	if test -z "$(DESTDIR)" ; then \
+		for p in $(schema_DATA) ; do \
+			GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) $(GCONFTOOL) --makefile-install-rule $(srcdir)/$$p; \
+		done \
+	fi
+else
+install-data-local:
+endif
diff --git a/src/common/dbus/README b/src/common/dbus/README
new file mode 100644
index 00000000..d61cf4e0
--- /dev/null
+++ b/src/common/dbus/README
@@ -0,0 +1,198 @@
+For more help you can see the xchat plugin interface documentation.
+http://www.xchat.org/docs/plugin20.html
+WARNING: The dbus interface may change in the future.
+
+You can use the "/org/xchat/Remote" object with interface "org.xchat.plugin",
+but his context can be changed by other clients at any moment and
+you may receive signal asked by other clients. So for more complex usage it's
+better to get your own remote object. Using "Connect" method on interface
+"org.xchat.connection"
+
+Available methods on org.xchat.connection interface:
+
+"Connect"
+  - Parameters:
+    - gchar*: filename
+    - gchar*: name
+    - gchar*: description
+    - gchar*: version
+  - Returns:
+    - gchar*: Your own object's path.
+
+"Disconnect"
+  No parameter, no return value. It frees your remote object.
+
+Available methods on org.xchat.plugin interface:
+
+"Command"
+  - Parameters:
+    - gchar*: the command name without the "/". (e.g. "nick pseudo")
+
+"Print"
+  - Parameters:
+    - gchar*: text to print on the xchat window.
+
+"FindContext"
+  - Parameters:
+    - gchar*: the server name. Can be NULL.
+    - gchar*: the channel name. Can be NULL.
+  - Returns:
+    - guint: context ID
+
+"GetContext"
+  - Returns:
+    - guint: current context's ID
+
+"SetContext"
+  - Parameters:
+    - guint: context ID to switch, returned by "FindContext" or "GetContext"
+  - Returns:
+    - gboolean: 1 for success, 0 for failure.
+
+"GetInfo"
+  - Parameters:
+    - gchar*: ID of the information you want.
+  - Returns:
+    - gchar*: information you requested.
+
+"GetPrefs"
+  - Parameters:
+    - gchar*: Setting name required.
+  - Returns:
+    - int: 0-Failed 1-Returned a string 2-Returned an Integer
+           3-Returned a Boolean.
+    - gchar*: the information requested if it's a string.
+    - int: the information requested if it's a integer or boolean.
+
+"HookCommand"
+  - Parameters:
+    - gchar*: Name of the command (without the forward slash).
+    - int: Priority of this command.
+    - gchar*: String of text to display when the user executes /help
+              for this command. May be NULL if you're lazy.
+    - int: Value to returns when the command is catched. See XCHAT_EAT_*.
+  - Returns:
+    - guint: The ID of the hook.
+
+"HookServer"
+  - Parameters:
+    - gchar*: Name of the server event.
+    - int: Priority of this command.
+    - int: Value to returns when the command is catched. See XCHAT_EAT_*.
+  - Returns:
+    - guint: The ID of the hook.
+
+"HookPrint"
+  - Parameters:
+    - gchar*: Name of the print event.
+    - int: Priority of this command.
+    - int: Value to returns when the command is catched. See XCHAT_EAT_*.
+  - Returns:
+    - guint: The ID of the hook.
+
+"Unhook"
+  - Parameters:
+    - guint: ID of the hook to unhook.
+      (the return value of "HookCommand", "HookServer" or "HookPrint")
+
+"ListGet"
+  - Parameters:
+    - gchar*: The list name.
+  - Returns:
+    - guint: List ID.
+
+"ListNext"
+  - Parameters:
+    - guint: List ID returned by "ListGet".
+  - Returns:
+    - gboolean: says if there is no more item in the list.
+
+"ListStr"
+  - Parameters:
+    - guint: List ID returned by "ListGet".
+    - gchar*: Name of the information needed.
+  - Returns:
+    - gchar*: The information requested.
+Warning: "context" attribut of "channels" list should be get with "ListInt"
+
+"ListInt"
+  - Parameters:
+    - guint: List ID returned by "ListGet".
+    - gchar*: Name of the information needed.
+  - Returns:
+    - guint: The information requested.
+
+"ListTime"
+  - Parameters:
+    - guint: List ID returned by "ListGet".
+    - gchar*: Name of the information needed.
+  - Returns:
+    - guint64: The information requested.
+
+"ListFields"
+  - Parameters:
+    - gchar*: The list name.
+  - Returns:
+    - gchar**: information names in this list.
+
+"ListFree"
+  - Parameters:
+    - guint: List ID returned by "ListGet".
+
+"EmitPrint"
+  - Parameters:
+    - gchar*: Text event to print.
+    - gchar**: NULL terminated array of string.
+  - Returns:
+    - gboolean: 1-Success 0-Failure.
+
+"Nickcmp"
+  - Parameters:
+    - gchar*: String to compare.
+    - gchar*: String to compare.
+  - Returns:
+    - int: An integer less than, equal to, or greater than zero if s1 is found,
+           respectively, to be less than, to match, or be greater than s2. 
+
+"Strip"
+  - Parameters:
+    - gchar*: String to strip.
+    - int: Length of the string (or -1 for NULL terminated).
+    - int: Bit-field of flags: 0-Strip mIRC colors, 1-Strip text attributes. 
+  - Returns:
+    - gchar*: striped string.
+
+"SendModes"
+  - Parameters:
+    - gchar**: NULL terminated array of targets (strings). The names of people
+               whom the action will be performed on.
+    - int: Maximum modes to send per line.
+    - gchar: Mode sign, '-' or '+'.
+    - gchar: Mode char, e.g. 'o' for Ops.
+
+
+Available signals:
+
+"ServerSignal"
+  - Parameters:
+    - gchar**: word returned by xchat.
+    - gchar**: word_eol returned bu xchat.
+    - guint: the ID of the hook. (the return value of "HookServer").
+    - guint: the ID of the context where the event come from.
+
+"CommandSignal"
+  - Parameters:
+    - gchar**: word returned by xchat.
+    - gchar**: word_eol returned bu xchat.
+    - guint: the ID of the hook. (the return value of "HookCommand").
+    - guint: the ID of the context where the event come from.
+
+"PrintSignal"
+  - Parameters:
+    - gchar**: word returned by xchat.
+    - guint: the ID of the hook. (the return value of "HookPrint").
+    - guint: the ID of the context where the event come from.
+
+"UnloadSignal"
+  emited when the user asks to unload your program.
+  Please exit(0); when received !
diff --git a/src/common/dbus/apps_xchat_url_handler.schemas b/src/common/dbus/apps_xchat_url_handler.schemas
new file mode 100644
index 00000000..10ac948d
--- /dev/null
+++ b/src/common/dbus/apps_xchat_url_handler.schemas
@@ -0,0 +1,37 @@
+<gconfschemafile>
+	<schemalist>
+		<schema>
+			<key>/schemas/desktop/gnome/url-handlers/irc/command</key>
+			<applyto>/desktop/gnome/url-handlers/irc/command</applyto>
+			<owner>xchat</owner>
+			<type>string</type>
+			<default>xchat --existing --url=%u</default>
+			<locale name="C">
+				<short>The handler for "irc://" URLs</short>
+			</locale>
+		</schema>
+
+		<schema>
+			<key>/schemas/desktop/gnome/url-handlers/irc/enabled</key>
+			<applyto>/desktop/gnome/url-handlers/irc/enabled</applyto>
+			<owner>xchat</owner>
+			<type>bool</type>
+			<default>true</default>
+			<locale name="C">
+				<short>Set it at TRUE if you want it activated</short>
+			</locale>
+		</schema>
+
+		<schema>
+			<key>/schemas/desktop/gnome/url-handlers/irc/needs_terminal</key>
+			<applyto>/desktop/gnome/url-handlers/irc/needs_terminal</applyto>
+			<owner>xchat</owner>
+			<type>bool</type>
+			<default>false</default>
+			<locale name="C">
+				<short>Run xchat in a terminal?</short>
+			</locale>
+		</schema>
+
+	</schemalist>
+</gconfschemafile>
diff --git a/src/common/dbus/dbus-client.c b/src/common/dbus/dbus-client.c
new file mode 100644
index 00000000..ac6bf6dc
--- /dev/null
+++ b/src/common/dbus/dbus-client.c
@@ -0,0 +1,118 @@
+/* dbus-client.c - XChat command-line options for D-Bus
+ * Copyright (C) 2006 Claessens Xavier
+ *
+ * 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
+ *
+ * Claessens Xavier
+ * xclaesse@gmail.com
+ */
+
+#include <dbus/dbus-glib.h>
+#include "dbus-client.h"
+#include "../xchat.h"
+#include "../xchatc.h"
+
+#define DBUS_SERVICE "org.xchat.service"
+#define DBUS_REMOTE "/org/xchat/Remote"
+#define DBUS_REMOTE_INTERFACE "org.xchat.plugin"
+
+static void
+write_error (char *message,
+	     GError **error)
+{
+	if (error == NULL || *error == NULL) {
+		return;
+	}
+	g_printerr ("%s: %s\n", message, (*error)->message);
+	g_clear_error (error);
+}
+
+void
+xchat_remote (void)
+/* TODO: dbus_g_connection_unref (connection) are commented because it makes
+ * dbus to crash. Fixed in dbus >=0.70 ?!?
+ * https://launchpad.net/distros/ubuntu/+source/dbus/+bug/54375
+ */
+{
+	DBusGConnection *connection;
+	DBusGProxy *dbus = NULL;
+	DBusGProxy *remote_object = NULL;
+	gboolean xchat_running;
+	GError *error = NULL;
+	char *command = NULL;
+
+	/* GnomeVFS >=2.15 uses D-Bus and threads, so threads should be
+	 * initialised before opening for the first time a D-Bus connection */
+	if (!g_thread_supported ()) {
+		g_thread_init (NULL);
+	}
+	dbus_g_thread_init ();
+
+	/* if there is nothing to do, return now. */
+	if (!arg_existing || !(arg_url || arg_command)) {
+		return;
+	}
+
+	arg_dont_autoconnect = TRUE;
+
+	connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
+	if (!connection) {
+		write_error (_("Couldn't connect to session bus"), &error);
+		return;
+	}
+
+	/* Checks if xchat is already running */
+	dbus = dbus_g_proxy_new_for_name (connection,
+					  DBUS_SERVICE_DBUS,
+					  DBUS_PATH_DBUS,
+					  DBUS_INTERFACE_DBUS);
+	if (!dbus_g_proxy_call (dbus, "NameHasOwner", &error,
+				G_TYPE_STRING, DBUS_SERVICE,
+				G_TYPE_INVALID,
+				G_TYPE_BOOLEAN, &xchat_running,
+				G_TYPE_INVALID)) {
+		write_error (_("Failed to complete NameHasOwner"), &error);
+		xchat_running = FALSE;
+	}
+	g_object_unref (dbus);
+
+	if (!xchat_running) {
+		//dbus_g_connection_unref (connection);
+		return;
+	}
+
+	remote_object = dbus_g_proxy_new_for_name (connection,
+						   DBUS_SERVICE,
+						   DBUS_REMOTE,
+						   DBUS_REMOTE_INTERFACE);
+
+	if (arg_url) {
+		command = g_strdup_printf ("url %s", arg_url);
+	} else if (arg_command) {
+		command = g_strdup (arg_command);
+	}
+
+	if (command) {
+		if (!dbus_g_proxy_call (remote_object, "Command",
+					&error,
+					G_TYPE_STRING, command,
+					G_TYPE_INVALID,G_TYPE_INVALID)) {
+			write_error (_("Failed to complete Command"), &error);
+		}
+		g_free (command);
+	}
+
+	exit (0);
+}
diff --git a/src/common/dbus/dbus-client.h b/src/common/dbus/dbus-client.h
new file mode 100644
index 00000000..cafc2b71
--- /dev/null
+++ b/src/common/dbus/dbus-client.h
@@ -0,0 +1,27 @@
+/* dbus-client.h - XChat command-line options for D-Bus
+ * Copyright (C) 2006 Claessens Xavier
+ *
+ * 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
+ *
+ * Claessens Xavier
+ * xclaesse@gmail.com
+ */
+
+#ifndef __DBUS_PLUGIN_H__
+#define __DBUS_PLUGIN_H__
+
+void xchat_remote (void);
+
+#endif /* __DBUS_PLUGIN_H__ */
diff --git a/src/common/dbus/dbus-plugin.c b/src/common/dbus/dbus-plugin.c
new file mode 100644
index 00000000..012812cc
--- /dev/null
+++ b/src/common/dbus/dbus-plugin.c
@@ -0,0 +1,1087 @@
+/* dbus-plugin.c - xchat plugin for remote access using D-Bus
+ * Copyright (C) 2006 Claessens Xavier
+ *
+ * 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
+ *
+ * Claessens Xavier
+ * xclaesse@gmail.com
+ */
+
+#define DBUS_API_SUBJECT_TO_CHANGE
+
+#include <config.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <glib/gi18n.h>
+#include "../xchat-plugin.h"
+
+#define PNAME _("remote access")
+#define PDESC _("plugin for remote access using DBUS")
+#define PVERSION ""
+
+#define DBUS_SERVICE "org.xchat.service"
+#define DBUS_OBJECT_PATH "/org/xchat"
+
+static xchat_plugin *ph;
+static guint last_context_id = 0;
+static GList *contexts = NULL;
+static GHashTable *clients = NULL;
+static DBusGConnection *connection;
+
+typedef struct RemoteObject RemoteObject;
+typedef struct RemoteObjectClass RemoteObjectClass;
+
+GType Remote_object_get_type (void);
+
+struct RemoteObject
+{
+	GObject parent;
+
+	guint last_hook_id;
+	guint last_list_id;
+	xchat_context *context;
+	char *dbus_path;
+	char *filename;
+	GHashTable *hooks;
+	GHashTable *lists;
+	void *handle;
+};
+
+struct RemoteObjectClass
+{
+	GObjectClass parent;
+};
+
+typedef struct 
+{
+	guint id;
+	int return_value;
+	xchat_hook *hook;
+	RemoteObject *obj;
+} HookInfo;
+
+typedef struct
+{
+	guint id;
+	xchat_context *context;
+} ContextInfo;
+
+enum
+{
+	SERVER_SIGNAL,
+	COMMAND_SIGNAL,
+	PRINT_SIGNAL,
+	UNLOAD_SIGNAL,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+#define REMOTE_TYPE_OBJECT              (remote_object_get_type ())
+#define REMOTE_OBJECT(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), REMOTE_TYPE_OBJECT, RemoteObject))
+#define REMOTE_OBJECT_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), REMOTE_TYPE_OBJECT, RemoteObjectClass))
+#define REMOTE_IS_OBJECT(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), REMOTE_TYPE_OBJECT))
+#define REMOTE_IS_OBJECT_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), REMOTE_TYPE_OBJECT))
+#define REMOTE_OBJECT_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), REMOTE_TYPE_OBJECT, RemoteObjectClass))
+#define REMOTE_OBJECT_ERROR (remote_object_error_quark ())
+#define REMOTE_TYPE_ERROR (remote_object_error_get_type ()) 
+
+G_DEFINE_TYPE (RemoteObject, remote_object, G_TYPE_OBJECT)
+
+/* Available services */
+
+static gboolean		remote_object_connect		(RemoteObject *obj,
+							 const char *filename,
+							 const char *name,
+							 const char *desc,
+							 const char *version,
+							 DBusGMethodInvocation *context);
+
+static gboolean		remote_object_disconnect	(RemoteObject *obj,
+							 DBusGMethodInvocation *context);
+
+static gboolean		remote_object_command		(RemoteObject *obj,
+							 const char *command,
+							 GError **error);
+
+static gboolean		remote_object_print		(RemoteObject *obj,
+							 const char *text,
+							 GError **error);
+
+static gboolean		remote_object_find_context	(RemoteObject *obj,
+							 const char *server,
+							 const char *channel,
+							 guint *ret_id,
+							 GError **error);
+
+static gboolean		remote_object_get_context	(RemoteObject *obj,
+							 guint *ret_id,
+							 GError **error);
+
+static gboolean		remote_object_set_context	(RemoteObject *obj,
+							 guint id,
+							 gboolean *ret,
+							 GError **error);
+
+static gboolean		remote_object_print		(RemoteObject *obj,
+							 const char *text,
+							 GError **error);
+
+static gboolean		remote_object_get_info		(RemoteObject *obj,
+							 const char *id,
+							 char **ret_info,
+							 GError **error);
+
+static gboolean		remote_object_get_prefs		(RemoteObject *obj,
+							 const char *name,
+							 int *ret_type,
+							 char **ret_str,
+							 int *ret_int,
+							 GError **error);
+
+static gboolean		remote_object_hook_command	(RemoteObject *obj,
+							 const char *name,
+							 int pri,
+							 const char *help_text,
+							 int return_value,
+							 guint *ret_id,
+							 GError **error);
+
+static gboolean		remote_object_hook_server	(RemoteObject *obj,
+							 const char *name,
+							 int pri,
+							 int return_value,
+							 guint *ret_id,
+							 GError **error);
+
+static gboolean		remote_object_hook_print	(RemoteObject *obj,
+							 const char *name,
+							 int pri,
+							 int return_value,
+							 guint *ret_id,
+							 GError **error);
+
+static gboolean		remote_object_unhook		(RemoteObject *obj,
+							 guint id,
+							 GError **error);
+
+static gboolean		remote_object_list_get		(RemoteObject *obj,
+							 const char *name,
+							 guint *ret_id,
+							 GError **error);
+
+static gboolean		remote_object_list_next		(RemoteObject *obj,
+							 guint id,
+							 gboolean *ret,
+							 GError **error);
+
+static gboolean		remote_object_list_str		(RemoteObject *obj,
+							 guint id,
+							 const char *name,
+							 char **ret_str,
+							 GError **error);
+
+static gboolean		remote_object_list_int		(RemoteObject *obj,
+							 guint id,
+							 const char *name,
+							 int *ret_int,
+							 GError **error);
+
+static gboolean		remote_object_list_time		(RemoteObject *obj,
+							 guint id,
+							 const char *name,
+							 guint64 *ret_time,
+							 GError **error);
+
+static gboolean		remote_object_list_fields	(RemoteObject *obj,
+							 const char *name,
+							 char ***ret,
+							 GError **error);
+
+static gboolean		remote_object_list_free		(RemoteObject *obj,
+							 guint id,
+							 GError **error);
+
+static gboolean		remote_object_emit_print	(RemoteObject *obj,
+							 const char *event_name,
+							 const char *args[],
+							 gboolean *ret,
+							 GError **error);
+
+static gboolean		remote_object_nickcmp		(RemoteObject *obj,
+							 const char *nick1,
+							 const char *nick2,
+							 int *ret,
+							 GError **error);
+
+static gboolean		remote_object_strip		(RemoteObject *obj,
+							 const char *str,
+							 int len,
+							 int flag,
+							 char **ret_str,
+							 GError **error);
+
+static gboolean		remote_object_send_modes	(RemoteObject *obj,
+							 const char *targets[],
+							 int modes_per_line,
+							 char sign,
+							 char mode,
+							 GError **error);
+
+#include "remote-object-glue.h"
+#include "marshallers.h"
+
+/* Useful functions */
+
+static char**		build_list			(char *word[]);
+static guint		context_list_find_id		(xchat_context *context);
+static xchat_context*	context_list_find_context	(guint id);
+
+/* Remote Object */
+
+static void
+hook_info_destroy (gpointer data)
+{
+	HookInfo *info = (HookInfo*)data;
+
+	if (info == NULL) {
+		return;
+	}
+	xchat_unhook (ph, info->hook);
+	g_free (info);
+}
+
+static void
+list_info_destroy (gpointer data)
+{
+	xchat_list_free (ph, (xchat_list*)data);
+}
+
+static void
+remote_object_finalize (GObject *obj)
+{
+	RemoteObject *self = (RemoteObject*)obj;
+
+	g_hash_table_destroy (self->lists);
+	g_hash_table_destroy (self->hooks);
+	g_free (self->dbus_path);
+	g_free (self->filename);
+	xchat_plugingui_remove (ph, self->handle);
+
+	G_OBJECT_CLASS (remote_object_parent_class)->finalize (obj);
+}
+
+static void
+remote_object_init (RemoteObject *obj)
+{
+	obj->hooks =
+		g_hash_table_new_full (g_int_hash,
+				       g_int_equal,
+				       NULL,
+				       hook_info_destroy);
+
+	obj->lists =
+		g_hash_table_new_full (g_int_hash,
+				       g_int_equal,
+				       g_free,
+				       list_info_destroy);
+	obj->dbus_path = NULL;
+	obj->filename = NULL;
+	obj->last_hook_id = 0;
+	obj->last_list_id = 0;
+	obj->context = xchat_get_context (ph);
+}
+
+static void
+remote_object_class_init (RemoteObjectClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+	gobject_class->finalize = remote_object_finalize;
+
+	signals[SERVER_SIGNAL] =
+		g_signal_new ("server_signal",
+			      G_OBJECT_CLASS_TYPE (klass),
+			      G_SIGNAL_RUN_LAST,
+			      0,
+			      NULL, NULL,
+			      g_cclosure_user_marshal_VOID__POINTER_POINTER_UINT_UINT,
+			      G_TYPE_NONE,
+			      4, G_TYPE_STRV, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_UINT);
+
+	signals[COMMAND_SIGNAL] =
+		g_signal_new ("command_signal",
+			      G_OBJECT_CLASS_TYPE (klass),
+			      G_SIGNAL_RUN_LAST,
+			      0,
+			      NULL, NULL,
+			      g_cclosure_user_marshal_VOID__POINTER_POINTER_UINT_UINT,
+			      G_TYPE_NONE,
+			      4, G_TYPE_STRV, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_UINT);
+
+	signals[PRINT_SIGNAL] =
+		g_signal_new ("print_signal",
+			      G_OBJECT_CLASS_TYPE (klass),
+			      G_SIGNAL_RUN_LAST,
+			      0,
+			      NULL, NULL,
+			      g_cclosure_user_marshal_VOID__POINTER_POINTER_UINT_UINT,
+			      G_TYPE_NONE,
+			      3, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_UINT);
+
+	signals[UNLOAD_SIGNAL] =
+		g_signal_new ("unload_signal",
+			      G_OBJECT_CLASS_TYPE (klass),
+			      G_SIGNAL_RUN_LAST,
+			      0,
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE,
+			      0);
+}
+
+/* Implementation of services */
+
+static gboolean
+remote_object_connect (RemoteObject *obj,
+		       const char *filename,
+		       const char *name,
+		       const char *desc,
+		       const char *version,
+		       DBusGMethodInvocation *context)
+{
+	static guint count = 0;
+	char *sender, *path;
+	RemoteObject *remote_object;
+	
+	sender = dbus_g_method_get_sender (context);
+	remote_object = g_hash_table_lookup (clients, sender);
+	if (remote_object != NULL) {
+		dbus_g_method_return (context, remote_object->dbus_path);
+		g_free (sender);
+		return TRUE;
+	}
+	path = g_strdup_printf (DBUS_OBJECT_PATH"/%d", count++);
+	remote_object = g_object_new (REMOTE_TYPE_OBJECT, NULL);
+	remote_object->dbus_path = path;
+	remote_object->filename = g_path_get_basename (filename);
+	remote_object->handle = xchat_plugingui_add (ph,
+						     remote_object->filename,
+						     name,
+						     desc,
+						     version, NULL);
+	dbus_g_connection_register_g_object (connection,
+					     path,
+					     G_OBJECT (remote_object));
+
+	g_hash_table_insert (clients,
+			     sender,
+			     remote_object);
+
+	dbus_g_method_return (context, path);
+	return TRUE;
+}
+
+static gboolean
+remote_object_disconnect (RemoteObject *obj,
+			  DBusGMethodInvocation *context)
+{
+	char *sender;
+	
+	sender = dbus_g_method_get_sender (context);
+	g_hash_table_remove (clients, sender);
+	g_free (sender);
+
+	dbus_g_method_return (context);
+	return TRUE;
+}
+
+static gboolean
+remote_object_command (RemoteObject *obj,
+		       const char *command,
+		       GError **error)
+{
+	if (xchat_set_context (ph, obj->context)) {
+		xchat_command (ph, command);
+	}
+	return TRUE;
+}
+
+static gboolean
+remote_object_print (RemoteObject *obj,
+		     const char *text,
+		     GError **error)
+{
+	if (xchat_set_context (ph, obj->context)) {
+		xchat_print (ph, text);
+	}
+	return TRUE;
+}
+
+static gboolean
+remote_object_find_context (RemoteObject *obj,
+			    const char *server,
+			    const char *channel,
+			    guint *ret_id,
+			    GError **error)
+{
+	xchat_context *context;
+
+	if (*server == '\0') {
+		server = NULL;
+	}
+	if (*channel == '\0') {
+		channel = NULL;
+	}
+	context = xchat_find_context (ph, server, channel);
+	*ret_id = context_list_find_id (context);
+
+	return TRUE;
+}
+
+static gboolean
+remote_object_get_context (RemoteObject *obj,
+			   guint *ret_id,
+			   GError **error)
+{
+	*ret_id = context_list_find_id (obj->context);
+	return TRUE;
+}
+
+static gboolean
+remote_object_set_context (RemoteObject *obj,
+			   guint id,
+			   gboolean *ret,
+			   GError **error)
+{
+	xchat_context *context;
+	
+	context = context_list_find_context (id);
+	if (context == NULL) {
+		*ret = FALSE;
+		return TRUE;
+	}
+	obj->context = context;
+	*ret = TRUE;
+
+	return TRUE;
+}
+
+static gboolean
+remote_object_get_info (RemoteObject *obj,
+			const char *id,
+			char **ret_info,
+			GError **error)
+{
+	/* win_ptr is a GtkWindow* casted to char* and will crash
+	 * D-Bus if we send it as a string */
+	if (!xchat_set_context (ph, obj->context) ||
+	    g_str_equal (id, "win_ptr")) {
+		*ret_info = NULL;
+		return TRUE;
+	}
+	*ret_info = g_strdup (xchat_get_info (ph, id));
+	return TRUE;
+}
+
+static gboolean
+remote_object_get_prefs (RemoteObject *obj,
+			 const char *name,
+			 int *ret_type,
+			 char **ret_str,
+			 int *ret_int,
+			 GError **error)
+{
+	const char *str;
+
+	if (!xchat_set_context (ph, obj->context)) {
+		*ret_type = 0;
+		return TRUE;
+	}
+	*ret_type = xchat_get_prefs (ph, name, &str, ret_int);
+	*ret_str = g_strdup (str);
+
+	return TRUE;
+}
+
+static int
+server_hook_cb (char *word[],
+		char *word_eol[],
+		void *userdata)
+{
+	HookInfo *info = (HookInfo*)userdata;
+	char **arg1;
+	char **arg2;
+
+	arg1 = build_list (word + 1);
+	arg2 = build_list (word_eol + 1);
+	info->obj->context = xchat_get_context (ph);
+	g_signal_emit (info->obj,
+		       signals[SERVER_SIGNAL],
+		       0,
+		       arg1, arg2, info->id,
+		       context_list_find_id (info->obj->context));
+	g_strfreev (arg1);
+	g_strfreev (arg2);
+  
+	return info->return_value;
+}
+
+static int
+command_hook_cb (char *word[],
+		 char *word_eol[],
+		 void *userdata)
+{
+	HookInfo *info = (HookInfo*)userdata;
+	char **arg1;
+	char **arg2;
+
+	arg1 = build_list (word + 1);
+	arg2 = build_list (word_eol + 1);
+	info->obj->context = xchat_get_context (ph);
+	g_signal_emit (info->obj,
+		       signals[COMMAND_SIGNAL],
+		       0,
+		       arg1, arg2, info->id,
+		       context_list_find_id (info->obj->context));
+	g_strfreev (arg1);
+	g_strfreev (arg2);
+  
+	return info->return_value;
+}
+
+static int
+print_hook_cb (char *word[],
+	       void *userdata)
+{
+	HookInfo *info = (HookInfo*)userdata;
+	char **arg1;
+
+	arg1 = build_list (word + 1);
+	info->obj->context = xchat_get_context (ph);
+	g_signal_emit (info->obj,
+		       signals[PRINT_SIGNAL],
+		       0,
+		       arg1, info->id,
+		       context_list_find_id (info->obj->context));
+	g_strfreev (arg1);
+  
+	return info->return_value;
+}
+
+static gboolean
+remote_object_hook_command (RemoteObject *obj,
+			    const char *name,
+			    int priority,
+			    const char *help_text,
+			    int return_value,
+			    guint *ret_id,
+			    GError **error)
+{
+	HookInfo *info;
+
+	info = g_new0 (HookInfo, 1);
+	info->obj = obj;
+	info->return_value = return_value;
+	info->id = ++obj->last_hook_id;
+	info->hook = xchat_hook_command (ph,
+					 name,
+					 priority,
+					 command_hook_cb,
+					 help_text,
+					 info);
+	g_hash_table_insert (obj->hooks, &info->id, info);
+	*ret_id = info->id;
+
+	return TRUE;
+}
+
+static gboolean
+remote_object_hook_server (RemoteObject *obj,
+			   const char *name,
+			   int priority,
+			   int return_value,
+			   guint *ret_id,
+			   GError **error)
+{
+	HookInfo *info;
+
+	info = g_new0 (HookInfo, 1);
+	info->obj = obj;
+	info->return_value = return_value;
+	info->id = ++obj->last_hook_id;
+	info->hook = xchat_hook_server (ph,
+					name,
+					priority,
+					server_hook_cb,
+					info);
+	g_hash_table_insert (obj->hooks, &info->id, info);
+	*ret_id = info->id;
+
+	return TRUE;
+}
+
+static gboolean
+remote_object_hook_print (RemoteObject *obj,
+			  const char *name,
+			  int priority,
+			  int return_value,
+			  guint *ret_id,
+			  GError **error)
+{
+	HookInfo *info;
+
+	info = g_new0 (HookInfo, 1);
+	info->obj = obj;
+	info->return_value = return_value;
+	info->id = ++obj->last_hook_id;
+	info->hook = xchat_hook_print (ph,
+				       name,
+				       priority,
+				       print_hook_cb,
+				       info);
+	g_hash_table_insert (obj->hooks, &info->id, info);
+	*ret_id = info->id;
+
+	return TRUE;
+}
+
+static gboolean
+remote_object_unhook (RemoteObject *obj,
+		      guint id,
+		      GError **error)
+{
+	g_hash_table_remove (obj->hooks, &id);
+	return TRUE;
+}
+
+static gboolean
+remote_object_list_get (RemoteObject *obj,
+			const char *name,
+			guint *ret_id,
+			GError **error)
+{
+	xchat_list *xlist;
+	guint *id;
+
+	if (!xchat_set_context (ph, obj->context)) {
+		*ret_id = 0;
+		return TRUE;
+	}
+	xlist = xchat_list_get (ph, name);
+	if (xlist == NULL) {
+		*ret_id = 0;
+		return TRUE;
+	}
+	id = g_new (guint, 1);
+	*id = ++obj->last_list_id;
+	*ret_id = *id;
+	g_hash_table_insert (obj->lists,
+			     id,
+			     xlist);
+
+	return TRUE;
+}
+
+static gboolean
+remote_object_list_next	(RemoteObject *obj,
+			 guint id,
+			 gboolean *ret,
+			 GError **error)
+{
+	xchat_list *xlist;
+	
+	xlist = g_hash_table_lookup (obj->lists, &id);
+	if (xlist == NULL) {
+		*ret = FALSE;
+		return TRUE;
+	}
+	*ret = xchat_list_next (ph, xlist);
+
+	return TRUE;
+}			 
+
+static gboolean
+remote_object_list_str (RemoteObject *obj,
+			guint id,
+			const char *name,
+			char **ret_str,
+			GError **error)
+{
+	xchat_list *xlist;
+	
+	xlist = g_hash_table_lookup (obj->lists, &id);
+	if (xlist == NULL && !xchat_set_context (ph, obj->context)) {
+		*ret_str = NULL;
+		return TRUE;
+	}
+	if (g_str_equal (name, "context")) {
+		*ret_str = NULL;
+		return TRUE;
+	}
+	*ret_str = g_strdup (xchat_list_str (ph, xlist, name));
+
+	return TRUE;
+}
+
+static gboolean
+remote_object_list_int (RemoteObject *obj,
+			guint id,
+			const char *name,
+			int *ret_int,
+			GError **error)
+{
+	xchat_list *xlist;
+	
+	xlist = g_hash_table_lookup (obj->lists, &id);
+	if (xlist == NULL && !xchat_set_context (ph, obj->context)) {
+		*ret_int = -1;
+		return TRUE;
+	}
+	if (g_str_equal (name, "context")) {
+		xchat_context *context;
+		context = (xchat_context*)xchat_list_str (ph, xlist, name);
+		*ret_int = context_list_find_id (context);
+	} else {
+		*ret_int = xchat_list_int (ph, xlist, name);
+	}
+
+	return TRUE;
+}
+
+static gboolean
+remote_object_list_time (RemoteObject *obj,
+			 guint id,
+			 const char *name,
+			 guint64 *ret_time,
+			 GError **error)
+{
+	xchat_list *xlist;
+	
+	xlist = g_hash_table_lookup (obj->lists, &id);
+	if (xlist == NULL) {
+		*ret_time = (guint64) -1;
+		return TRUE;
+	}
+	*ret_time = xchat_list_time (ph, xlist, name);
+	
+	return TRUE;
+}
+
+static gboolean
+remote_object_list_fields (RemoteObject *obj,
+			   const char *name,
+			   char ***ret,
+			   GError **error)
+{
+	*ret = g_strdupv ((char**)xchat_list_fields (ph, name));
+	if (*ret == NULL) {
+		*ret = g_new0 (char*, 1);
+	}
+	return TRUE;
+}
+
+static gboolean
+remote_object_list_free (RemoteObject *obj,
+			 guint id,
+			 GError **error)
+{
+	g_hash_table_remove (obj->lists, &id);
+	return TRUE;
+}
+
+static gboolean
+remote_object_emit_print (RemoteObject *obj,
+			  const char *event_name,
+			  const char *args[],
+			  gboolean *ret,
+			  GError **error)
+{
+	const char *argv[4] = {NULL, NULL, NULL, NULL};
+	int i;
+	
+	for (i = 0; i < 4 && args[i] != NULL; i++) {
+		argv[i] = args[i];
+	}
+
+	*ret = xchat_set_context (ph, obj->context);
+	if (*ret) {
+		*ret = xchat_emit_print (ph, event_name, argv[0], argv[1],
+							 argv[2], argv[3]);
+	}
+
+	return TRUE;
+}
+
+static gboolean
+remote_object_nickcmp (RemoteObject *obj,
+		       const char *nick1,
+		       const char *nick2,
+		       int *ret,
+		       GError **error)
+{
+	xchat_set_context (ph, obj->context);
+	*ret = xchat_nickcmp (ph, nick1, nick2);
+	return TRUE;
+}
+
+static gboolean
+remote_object_strip (RemoteObject *obj,
+		     const char *str,
+		     int len,
+		     int flag,
+		     char **ret_str,
+		     GError **error)
+{
+	*ret_str = xchat_strip (ph, str, len, flag);
+	return TRUE;
+}
+
+static gboolean
+remote_object_send_modes (RemoteObject *obj,
+			  const char *targets[],
+			  int modes_per_line,
+			  char sign,
+			  char mode,
+			  GError **error)
+{
+	if (xchat_set_context (ph, obj->context)) {
+		xchat_send_modes (ph, targets,
+				  g_strv_length ((char**)targets),
+				  modes_per_line,
+				  sign, mode);
+	}
+	return TRUE;
+}
+
+/* DBUS stuffs */
+
+static void
+name_owner_changed (DBusGProxy *driver_proxy,
+		    const char *name,
+		    const char *old_owner,
+		    const char *new_owner,
+		    void       *user_data)
+{
+	if (*new_owner == '\0') {
+		/* this name has vanished */
+		g_hash_table_remove (clients, name);
+	}
+}
+
+static gboolean
+init_dbus (void)
+{
+	DBusGProxy *proxy;
+	RemoteObject *remote;
+	guint request_name_result;
+	GError *error = NULL;
+
+	dbus_g_object_type_install_info (REMOTE_TYPE_OBJECT,
+					 &dbus_glib_remote_object_object_info);
+
+	connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
+	if (connection == NULL) {
+		xchat_printf (ph, _("Couldn't connect to session bus: %s\n"),
+			      error->message);
+		g_error_free (error);
+		return FALSE;
+	}
+
+	proxy = dbus_g_proxy_new_for_name (connection,
+					   DBUS_SERVICE_DBUS,
+					   DBUS_PATH_DBUS,
+					   DBUS_INTERFACE_DBUS);
+
+	if (!dbus_g_proxy_call (proxy, "RequestName", &error,
+				G_TYPE_STRING, DBUS_SERVICE,
+				G_TYPE_UINT, DBUS_NAME_FLAG_ALLOW_REPLACEMENT,
+				G_TYPE_INVALID,
+				G_TYPE_UINT, &request_name_result,
+				G_TYPE_INVALID)) {
+		xchat_printf (ph, _("Failed to acquire %s: %s\n"),
+			      DBUS_SERVICE,
+			      error->message);
+		g_error_free (error);
+
+		return FALSE;
+	}
+
+	dbus_g_proxy_add_signal (proxy, "NameOwnerChanged",
+				 G_TYPE_STRING,
+				 G_TYPE_STRING,
+				 G_TYPE_STRING,
+				 G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal (proxy, "NameOwnerChanged", 
+				     G_CALLBACK (name_owner_changed),
+				     NULL, NULL);
+
+	remote = g_object_new (REMOTE_TYPE_OBJECT, NULL);
+	dbus_g_connection_register_g_object (connection,
+					     DBUS_OBJECT_PATH"/Remote",
+					     G_OBJECT (remote));
+
+	return TRUE;
+}
+
+/* xchat_plugin stuffs */
+
+static char**
+build_list (char *word[])
+{
+	guint i;
+	guint num = 0;
+	char **result;
+
+	if (word == NULL) {
+		return NULL;
+	}
+  
+	while (word[num] && word[num][0]) {
+		num++;
+	}
+	
+	result = g_new0 (char*, num + 1);
+	for (i = 0; i < num; i++) {
+		result[i] = g_strdup (word[i]);
+	}
+
+	return result;
+}
+
+static guint
+context_list_find_id (xchat_context *context)
+{
+	GList *l = NULL;
+
+	for (l = contexts; l != NULL; l = l->next) {
+		if (((ContextInfo*)l->data)->context == context) {
+			return ((ContextInfo*)l->data)->id;
+		}
+	}
+
+	return 0;
+}
+
+static xchat_context*
+context_list_find_context (guint id)
+{
+	GList *l = NULL;
+
+	for (l = contexts; l != NULL; l = l->next) {
+		if (((ContextInfo*)l->data)->id == id) {
+			return ((ContextInfo*)l->data)->context;
+		}
+	}
+
+	return NULL;
+}
+
+static int
+open_context_cb (char *word[],
+		 void *userdata)
+{
+	ContextInfo *info;
+	
+	info = g_new0 (ContextInfo, 1);
+	info->id = ++last_context_id;
+	info->context = xchat_get_context (ph);
+	contexts = g_list_prepend (contexts, info);
+
+	return XCHAT_EAT_NONE;
+}
+
+static int
+close_context_cb (char *word[],
+		  void *userdata)
+{
+	GList *l;
+	xchat_context *context = xchat_get_context (ph);
+
+	for (l = contexts; l != NULL; l = l->next) {
+		if (((ContextInfo*)l->data)->context == context) {
+			g_free (l->data);
+			contexts = g_list_delete_link (contexts, l);
+			break;
+		}
+	}
+
+	return XCHAT_EAT_NONE;
+}
+
+static gboolean
+clients_find_filename_foreach (gpointer key,
+			       gpointer value,
+			       gpointer user_data)
+{
+	RemoteObject *obj = REMOTE_OBJECT (value);
+	return g_str_equal (obj->filename, (char*)user_data);
+}
+
+static int
+unload_plugin_cb (char *word[], char *word_eol[], void *userdata)
+{
+	RemoteObject *obj;
+	
+	obj = g_hash_table_find (clients,
+				 clients_find_filename_foreach,
+				 word[2]);
+	if (obj != NULL) {
+		g_signal_emit (obj, 
+			       signals[UNLOAD_SIGNAL],
+			       0);
+		return XCHAT_EAT_ALL;
+	}
+	
+	return XCHAT_EAT_NONE;
+}
+
+int
+dbus_plugin_init (xchat_plugin *plugin_handle,
+		  char **plugin_name,
+		  char **plugin_desc,
+		  char **plugin_version,
+		  char *arg)
+{
+	ph = plugin_handle;
+	*plugin_name = PNAME;
+	*plugin_desc = PDESC;
+	*plugin_version = PVERSION;
+
+	if (init_dbus()) {
+		/*xchat_printf (ph, _("%s loaded successfully!\n"), PNAME);*/
+
+		clients = g_hash_table_new_full (g_str_hash,
+						 g_str_equal,
+						 g_free,
+						 g_object_unref);
+
+		xchat_hook_print (ph, "Open Context",
+				  XCHAT_PRI_NORM,
+				  open_context_cb,
+				  NULL);
+
+		xchat_hook_print (ph, "Close Context",
+				  XCHAT_PRI_NORM,
+				  close_context_cb,
+				  NULL);
+
+		xchat_hook_command (ph, "unload",
+				    XCHAT_PRI_HIGHEST,
+				    unload_plugin_cb, NULL, NULL);
+	}
+
+	return TRUE; 
+}
diff --git a/src/common/dbus/dbus-plugin.h b/src/common/dbus/dbus-plugin.h
new file mode 100644
index 00000000..bda8f61c
--- /dev/null
+++ b/src/common/dbus/dbus-plugin.h
@@ -0,0 +1,31 @@
+/* dbus-plugin.c - xchat plugin for remote access using DBUS
+ * Copyright (C) 2006 Claessens Xavier
+ *
+ * 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
+ *
+ * Claessens Xavier
+ * xclaesse@gmail.com
+ */
+
+#ifndef XCHAT_DBUS_PLUGIN_H
+#define XCHAT_DBUS_PLUGIN_H
+
+int	dbus_plugin_init	(xchat_plugin *plugin_handle,
+				 char **plugin_name,
+				 char **plugin_desc,
+				 char **plugin_version,
+				 char *arg);
+
+#endif
diff --git a/src/common/dbus/example.c b/src/common/dbus/example.c
new file mode 100644
index 00000000..1d072785
--- /dev/null
+++ b/src/common/dbus/example.c
@@ -0,0 +1,201 @@
+/* example.c - program to demonstrate some D-BUS stuffs.
+ * Copyright (C) 2006 Claessens Xavier
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Claessens Xavier
+ * xclaesse@gmail.com
+ */
+
+#include <config.h>
+#include <dbus/dbus-glib.h>
+#include <stdlib.h>
+#include "marshallers.h"
+
+#define DBUS_SERVICE "org.xchat.service"
+#define DBUS_REMOTE "/org/xchat/Remote"
+#define DBUS_REMOTE_CONNECTION_INTERFACE "org.xchat.connection"
+#define DBUS_REMOTE_PLUGIN_INTERFACE "org.xchat.plugin"
+
+guint command_id;
+guint server_id;
+
+static void
+write_error (char *message,
+	     GError **error)
+{
+	if (error == NULL || *error == NULL) {
+		return;
+	}
+	g_printerr ("%s: %s\n", message, (*error)->message);
+	g_clear_error (error);
+}
+
+static void
+test_server_cb (DBusGProxy *proxy,
+		char *word[],
+		char *word_eol[],
+		guint hook_id,
+		guint context_id,
+		gpointer user_data)
+{
+	if (hook_id == server_id) {
+		g_print ("message: %s\n", word_eol[0]);
+	}
+}
+
+static void
+test_command_cb (DBusGProxy *proxy,
+		 char *word[],
+		 char *word_eol[],
+		 guint hook_id,
+		 guint context_id,
+		 gpointer user_data)
+{
+	GError *error = NULL;
+
+	if (hook_id == command_id) {
+		if (!dbus_g_proxy_call (proxy, "Unhook",
+					&error,
+					G_TYPE_UINT, hook_id,
+					G_TYPE_INVALID, G_TYPE_INVALID)) {
+			write_error ("Failed to complete unhook", &error);
+		}
+		/* Now if you write "/test blah" again in the xchat window
+		 * you'll get a "Unknown command" error message */
+		g_print ("test command received: %s\n", word_eol[1]);
+		if (!dbus_g_proxy_call (proxy, "Print",
+					&error,
+					G_TYPE_STRING, "test command succeed",
+					G_TYPE_INVALID,
+					G_TYPE_INVALID)) {
+			write_error ("Failed to complete Print", &error);
+		}
+	}
+}
+
+static void
+unload_cb (void)
+{
+	g_print ("Good bye !\n");
+	exit (EXIT_SUCCESS);
+}
+
+int
+main (int argc, char **argv)
+{
+	DBusGConnection *connection;
+	DBusGProxy *remote_object;
+	GMainLoop *mainloop;
+	gchar *path;
+	GError *error = NULL;
+
+	g_type_init ();
+
+	connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
+	if (connection == NULL) {
+		write_error ("Couldn't connect to session bus", &error);
+		return EXIT_FAILURE;
+	}
+  
+	remote_object = dbus_g_proxy_new_for_name (connection,
+						   DBUS_SERVICE,
+						   DBUS_REMOTE,
+						   DBUS_REMOTE_CONNECTION_INTERFACE);
+	if (!dbus_g_proxy_call (remote_object, "Connect",
+				&error,
+				G_TYPE_STRING, argv[0],
+				G_TYPE_STRING, "example",
+				G_TYPE_STRING, "Example of a D-Bus client",
+				G_TYPE_STRING, "1.0",
+				G_TYPE_INVALID,
+				G_TYPE_STRING, &path, G_TYPE_INVALID)) {
+		write_error ("Failed to complete Connect", &error);
+		return EXIT_FAILURE;
+	}
+	g_object_unref (remote_object);
+
+	remote_object = dbus_g_proxy_new_for_name (connection,
+						   DBUS_SERVICE,
+						   path,
+						   DBUS_REMOTE_PLUGIN_INTERFACE);
+	g_free (path);
+
+	if (!dbus_g_proxy_call (remote_object, "HookCommand",
+				&error,
+				G_TYPE_STRING, "test",
+				G_TYPE_INT, 0,
+				G_TYPE_STRING, "Simple D-BUS example",
+				G_TYPE_INT, 1, G_TYPE_INVALID,
+				G_TYPE_UINT, &command_id, G_TYPE_INVALID)) {
+		write_error ("Failed to complete HookCommand", &error);
+		return EXIT_FAILURE;
+	}
+	g_print ("Command hook id=%d\n", command_id);
+
+	if (!dbus_g_proxy_call (remote_object, "HookServer",
+				&error,
+				G_TYPE_STRING, "RAW LINE",
+				G_TYPE_INT, 0,
+				G_TYPE_INT, 0, G_TYPE_INVALID,
+				G_TYPE_UINT, &server_id, G_TYPE_INVALID)) {
+		write_error ("Failed to complete HookServer", &error);
+		return EXIT_FAILURE;
+	}
+	g_print ("Server hook id=%d\n", server_id);
+
+	dbus_g_object_register_marshaller (
+		g_cclosure_user_marshal_VOID__POINTER_POINTER_UINT_UINT,
+		G_TYPE_NONE,
+		G_TYPE_STRV, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_UINT,
+		G_TYPE_INVALID);
+
+	dbus_g_object_register_marshaller (
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE,
+		G_TYPE_INVALID);
+
+	dbus_g_proxy_add_signal (remote_object, "CommandSignal",
+				 G_TYPE_STRV,
+				 G_TYPE_STRV,
+				 G_TYPE_UINT,
+				 G_TYPE_UINT,
+				 G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal (remote_object, "CommandSignal",
+				     G_CALLBACK (test_command_cb),
+				     NULL, NULL);
+
+	dbus_g_proxy_add_signal (remote_object, "ServerSignal",
+				 G_TYPE_STRV,
+				 G_TYPE_STRV,
+				 G_TYPE_UINT,
+				 G_TYPE_UINT,
+				 G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal (remote_object, "ServerSignal",
+				     G_CALLBACK (test_server_cb),
+				     NULL, NULL);
+
+	dbus_g_proxy_add_signal (remote_object, "UnloadSignal",
+				 G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal (remote_object, "UnloadSignal",
+				     G_CALLBACK (unload_cb),
+				     NULL, NULL);
+
+	/* Now you can write on the xchat windows: "/test arg1 arg2 ..." */
+	mainloop = g_main_loop_new (NULL, FALSE);
+	g_main_loop_run (mainloop);
+
+	return EXIT_SUCCESS;
+}
diff --git a/src/common/dbus/example.py b/src/common/dbus/example.py
new file mode 100644
index 00000000..08bfdac3
--- /dev/null
+++ b/src/common/dbus/example.py
@@ -0,0 +1,28 @@
+#! /usr/bin/python
+
+import dbus
+
+bus = dbus.SessionBus()
+proxy = bus.get_object('org.xchat.service', '/org/xchat/Remote')
+remote = dbus.Interface(proxy, 'org.xchat.connection')
+path = remote.Connect ("example.py",
+		       "Python example",
+		       "Example of a D-Bus client written in python",
+		       "1.0")
+proxy = bus.get_object('org.xchat.service', path)
+xchat = dbus.Interface(proxy, 'org.xchat.plugin')
+
+channels = xchat.ListGet ("channels")
+while xchat.ListNext (channels):
+	name = xchat.ListStr (channels, "channel")
+	print "------- " + name + " -------"
+	xchat.SetContext (xchat.ListInt (channels, "context"))
+	xchat.EmitPrint ("Channel Message", ["John", "Hi there", "@"])
+	users = xchat.ListGet ("users")
+	while xchat.ListNext (users):
+		print "Nick: " + xchat.ListStr (users, "nick")
+	xchat.ListFree (users)
+xchat.ListFree (channels)
+
+print xchat.Strip ("\00312Blue\003 \002Bold!\002", -1, 1|2)
+
diff --git a/src/common/dbus/marshallers.list b/src/common/dbus/marshallers.list
new file mode 100644
index 00000000..bc3c4ad5
--- /dev/null
+++ b/src/common/dbus/marshallers.list
@@ -0,0 +1 @@
+VOID:POINTER,POINTER,UINT,UINT
diff --git a/src/common/dbus/org.xchat.service.service.in b/src/common/dbus/org.xchat.service.service.in
new file mode 100644
index 00000000..19490121
--- /dev/null
+++ b/src/common/dbus/org.xchat.service.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.xchat.service
+Exec=@bindir@/xchat
diff --git a/src/common/dbus/remote-object.xml b/src/common/dbus/remote-object.xml
new file mode 100644
index 00000000..88a8ae7c
--- /dev/null
+++ b/src/common/dbus/remote-object.xml
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<node name="/">
+
+  <interface name="org.xchat.connection">
+    <method name="Connect">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+      <arg type="s" name="filename" direction="in"/>
+      <arg type="s" name="name" direction="in"/>
+      <arg type="s" name="desc" direction="in"/>
+      <arg type="s" name="version" direction="in"/>
+      <arg type="s" name="path" direction="out"/>
+    </method>  
+    <method name="Disconnect">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+    </method>
+  </interface>
+
+  <interface name="org.xchat.plugin">
+    <method name="Command">
+      <arg type="s" name="command" direction="in"/>
+    </method>
+    <method name="Print">
+      <arg type="s" name="text" direction="in"/>
+    </method>
+    <method name="FindContext">
+      <arg type="s" name="server" direction="in"/>
+      <arg type="s" name="channel" direction="in"/>
+      <arg type="u" name="ret_id" direction="out"/>
+    </method>
+    <method name="GetContext">
+      <arg type="u" name="ret_id" direction="out"/>
+    </method>
+    <method name="SetContext">
+      <arg type="u" name="id" direction="in"/>
+      <arg type="b" name="ret" direction="out"/>
+    </method>
+    <method name="GetInfo">
+      <arg type="s" name="id" direction="in"/>
+      <arg type="s" name="ret_info" direction="out"/>
+    </method>
+    <method name="GetPrefs">
+      <arg type="s" name="name" direction="in"/>
+      <arg type="i" name="ret_type" direction="out"/>
+      <arg type="s" name="ret_str" direction="out"/>
+      <arg type="i" name="ret_int" direction="out"/>
+    </method>
+    <method name="HookCommand">
+      <arg type="s" name="name" direction="in"/>
+      <arg type="i" name="priority" direction="in"/>
+      <arg type="s" name="help_text" direction="in"/>
+      <arg type="i" name="return_value" direction="in"/>
+      <arg type="u" name="ret_id" direction="out"/>
+    </method>
+    <method name="HookServer">
+      <arg type="s" name="name" direction="in"/>
+      <arg type="i" name="priority" direction="in"/>
+      <arg type="i" name="return_value" direction="in"/>
+      <arg type="u" name="ret_id" direction="out"/>
+    </method>
+    <method name="HookPrint">
+      <arg type="s" name="name" direction="in"/>
+      <arg type="i" name="priority" direction="in"/>
+      <arg type="i" name="return_value" direction="in"/>
+      <arg type="u" name="ret_id" direction="out"/>
+    </method>
+    <method name="Unhook">
+      <arg type="u" name="id" direction="in"/>
+    </method>
+    <method name="ListGet">
+      <arg type="s" name="name" direction="in"/>
+      <arg type="u" name="ret_id" direction="out"/>
+    </method>
+    <method name="ListNext">
+      <arg type="u" name="id" direction="in"/>
+      <arg type="b" name="ret" direction="out"/>
+    </method>
+    <method name="ListStr">
+      <arg type="u" name="id" direction="in"/>
+      <arg type="s" name="name" direction="in"/>
+      <arg type="s" name="ret_str" direction="out"/>
+    </method>
+    <method name="ListInt">
+      <arg type="u" name="id" direction="in"/>
+      <arg type="s" name="name" direction="in"/>
+      <arg type="i" name="ret_int" direction="out"/>
+    </method>
+    <method name="ListTime">
+      <arg type="u" name="id" direction="in"/>
+      <arg type="s" name="name" direction="in"/>
+      <arg type="t" name="ret_time" direction="out"/>
+    </method>
+    <method name="ListFields">
+      <arg type="s" name="name" direction="in"/>
+      <arg type="as" name="ret" direction="out"/>
+    </method>
+    <method name="ListFree">
+      <arg type="u" name="id" direction="in"/>
+    </method>
+    <method name="EmitPrint">
+      <arg type="s" name="event_name" direction="in"/>
+      <arg type="as" name="args" direction="in"/>
+      <arg type="b" name="ret" direction="out"/>
+    </method>
+    <method name="Nickcmp">
+      <arg type="s" name="nick1" direction="in"/>
+      <arg type="s" name="nick2" direction="in"/>
+      <arg type="i" name="ret" direction="out"/>
+    </method>
+    <method name="Strip">
+      <arg type="s" name="str" direction="in"/>
+      <arg type="i" name="len" direction="in"/>
+      <arg type="i" name="flag" direction="in"/>
+      <arg type="s" name="ret_str" direction="out"/>
+    </method>
+    <method name="SendModes">
+      <arg type="as" name="targets" direction="in"/>
+      <arg type="i" name="modes_per_line" direction="in"/>
+      <arg type="y" name="sign" direction="in"/>
+      <arg type="y" name="mode" direction="in"/>
+    </method>
+
+    <signal name="CommandSignal">
+      <arg type="as" name="word"/>
+      <arg type="as" name="word_eol"/>
+      <arg type="u" name="hook_id"/>
+      <arg type="u" name="context_id"/>
+    </signal>
+    <signal name="ServerSignal">
+      <arg type="as" name="word"/>
+      <arg type="as" name="word_eol"/>
+      <arg type="u" name="hook_id"/>
+      <arg type="u" name="context_id"/>
+    </signal>
+    <signal name="PrintSignal">
+      <arg type="as" name="word"/>
+      <arg type="u" name="hook_id"/>
+      <arg type="u" name="context_id"/>
+    </signal>
+    <signal name="UnloadSignal"/>
+  </interface>
+</node>
diff --git a/src/common/dcc.c b/src/common/dcc.c
new file mode 100644
index 00000000..8f289342
--- /dev/null
+++ b/src/common/dcc.c
@@ -0,0 +1,2587 @@
+/* X-Chat
+ * Copyright (C) 1998-2006 Peter Zelezny.
+ * Copyright (C) 2006 Damjan Jovanovic
+ *
+ * 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
+ *
+ * Wayne Conrad, 3 Apr 1999: Color-coded DCC file transfer status windows
+ * Bernhard Valenti <bernhard.valenti@gmx.net> 2000-11-21: Fixed DCC send behind nat
+ *
+ * 2001-03-08 Added support for getting "dcc_ip" config parameter.
+ * Jim Seymour (jseymour@LinxNet.com)
+ */
+
+/* we only use 32 bits, but without this define, you get only 31! */
+#define _FILE_OFFSET_BITS 64
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#define WANTSOCKET
+#define WANTARPA
+#define WANTDNS
+#include "inet.h"
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+#include "xchat.h"
+#include "util.h"
+#include "fe.h"
+#include "outbound.h"
+#include "inbound.h"
+#include "network.h"
+#include "plugin.h"
+#include "server.h"
+#include "text.h"
+#include "url.h"
+#include "xchatc.h"
+
+#ifdef USE_DCC64
+#define BIG_STR_TO_INT(x) strtoull(x,NULL,10)
+#else
+#define BIG_STR_TO_INT(x) strtoul(x,NULL,10)
+#endif
+
+static char *dcctypes[] = { "SEND", "RECV", "CHAT", "CHAT" };
+
+struct dccstat_info dccstat[] = {
+	{N_("Waiting"), 1 /*black */ },
+	{N_("Active"), 12 /*cyan */ },
+	{N_("Failed"), 4 /*red */ },
+	{N_("Done"), 3 /*green */ },
+	{N_("Connect"), 1 /*black */ },
+	{N_("Aborted"), 4 /*red */ },
+};
+
+static int dcc_global_throttle;	/* 0x1 = sends, 0x2 = gets */
+/*static*/ int dcc_sendcpssum, dcc_getcpssum;
+
+static struct DCC *new_dcc (void);
+static void dcc_close (struct DCC *dcc, int dccstat, int destroy);
+static gboolean dcc_send_data (GIOChannel *, GIOCondition, struct DCC *);
+static gboolean dcc_read (GIOChannel *, GIOCondition, struct DCC *);
+static gboolean dcc_read_ack (GIOChannel *source, GIOCondition condition, struct DCC *dcc);
+
+static int new_id()
+{
+	static int id = 0;
+	if (id == 0)
+	{
+		/* start the first ID at a random number for pseudo security */
+		/* 1 - 255 */
+		id = RAND_INT(255) + 1;
+		/* ignore overflows, since it can go to 2 billion */
+	}
+	return id++;
+}
+
+static double
+timeval_diff (GTimeVal *greater,
+				 GTimeVal *less)
+{
+	long usecdiff;
+	double result;
+	
+	result = greater->tv_sec - less->tv_sec;
+	usecdiff = (long) greater->tv_usec - less->tv_usec;
+	result += (double) usecdiff / 1000000;
+	
+	return result;
+}
+
+static void
+dcc_unthrottle (struct DCC *dcc)
+{
+	/* don't unthrottle here, but delegate to funcs */
+	if (dcc->type == TYPE_RECV)
+		dcc_read (NULL, 0, dcc);
+	else
+		dcc_send_data (NULL, 0, dcc);
+}
+
+static void
+dcc_calc_cps (struct DCC *dcc)
+{
+	GTimeVal now;
+	int oldcps;
+	double timediff, startdiff;
+	int glob_throttle_bit, wasthrottled;
+	int *cpssum, glob_limit;
+	DCC_SIZE pos, posdiff;
+
+	g_get_current_time (&now);
+
+	/* the pos we use for sends is an average
+		between pos and ack */
+	if (dcc->type == TYPE_SEND)
+	{
+		/* carefull to avoid 32bit overflow */
+		pos = dcc->pos - ((dcc->pos - dcc->ack) / 2);
+		glob_throttle_bit = 0x1;
+		cpssum = &dcc_sendcpssum;
+		glob_limit = prefs.dcc_global_max_send_cps;
+	}
+	else
+	{
+		pos = dcc->pos;
+		glob_throttle_bit = 0x2;
+		cpssum = &dcc_getcpssum;
+		glob_limit = prefs.dcc_global_max_get_cps;
+	}
+
+	if (!dcc->firstcpstv.tv_sec && !dcc->firstcpstv.tv_usec)
+		dcc->firstcpstv = now;
+	else
+	{
+		startdiff = timeval_diff (&now, &dcc->firstcpstv);
+		if (startdiff < 1)
+			startdiff = 1;
+		else if (startdiff > CPS_AVG_WINDOW)
+			startdiff = CPS_AVG_WINDOW;
+
+		timediff = timeval_diff (&now, &dcc->lastcpstv);
+		if (timediff > startdiff)
+			timediff = startdiff = 1;
+
+		posdiff = pos - dcc->lastcpspos;
+		oldcps = dcc->cps;
+		dcc->cps = ((double) posdiff / timediff) * (timediff / startdiff) +
+			(double) dcc->cps * (1.0 - (timediff / startdiff));
+
+		*cpssum += dcc->cps - oldcps;
+	}
+
+	dcc->lastcpspos = pos;
+	dcc->lastcpstv = now;
+
+	/* now check cps against set limits... */
+	wasthrottled = dcc->throttled;
+
+	/* check global limits first */
+	dcc->throttled &= ~0x2;
+	if (glob_limit > 0 && *cpssum >= glob_limit)
+	{
+		dcc_global_throttle |= glob_throttle_bit;
+		if (dcc->maxcps >= 0)
+			dcc->throttled |= 0x2;
+	}
+	else
+		dcc_global_throttle &= ~glob_throttle_bit;
+
+	/* now check per-connection limit */
+	if (dcc->maxcps > 0 && dcc->cps > dcc->maxcps)
+		dcc->throttled |= 0x1;
+	else
+		dcc->throttled &= ~0x1;
+
+	/* take action */
+	if (wasthrottled && !dcc->throttled)
+		dcc_unthrottle (dcc);
+}
+
+static void
+dcc_remove_from_sum (struct DCC *dcc)
+{
+	if (dcc->dccstat != STAT_ACTIVE)
+		return;
+	if (dcc->type == TYPE_SEND)
+		dcc_sendcpssum -= dcc->cps;
+	else if (dcc->type == TYPE_RECV)
+		dcc_getcpssum -= dcc->cps;
+}
+
+gboolean
+is_dcc (struct DCC *dcc)
+{
+	GSList *list = dcc_list;
+	while (list)
+	{
+		if (list->data == dcc)
+			return TRUE;
+		list = list->next;
+	}
+	return FALSE;
+}
+
+/* this is called from xchat.c:xchat_misc_checks() every 1 second. */
+
+void
+dcc_check_timeouts (void)
+{
+	struct DCC *dcc;
+	time_t tim = time (0);
+	GSList *next, *list = dcc_list;
+
+	while (list)
+	{
+		dcc = (struct DCC *) list->data;
+		next = list->next;
+
+		switch (dcc->dccstat)
+		{
+		case STAT_ACTIVE:
+			dcc_calc_cps (dcc);
+			fe_dcc_update (dcc);
+
+			if (dcc->type == TYPE_SEND || dcc->type == TYPE_RECV)
+			{
+				if (prefs.dccstalltimeout > 0)
+				{
+					if (!dcc->throttled
+						&& tim - dcc->lasttime > prefs.dccstalltimeout)
+					{
+						EMIT_SIGNAL (XP_TE_DCCSTALL, dcc->serv->front_session,
+										 dcctypes[dcc->type],
+										 file_part (dcc->file), dcc->nick, NULL, 0);
+						dcc_close (dcc, STAT_ABORTED, FALSE);
+					}
+				}
+			}
+			break;
+		case STAT_QUEUED:
+			if (dcc->type == TYPE_SEND || dcc->type == TYPE_CHATSEND)
+			{
+				if (tim - dcc->offertime > prefs.dcctimeout)
+				{
+					if (prefs.dcctimeout > 0)
+					{
+						EMIT_SIGNAL (XP_TE_DCCTOUT, dcc->serv->front_session,
+										 dcctypes[dcc->type],
+										 file_part (dcc->file), dcc->nick, NULL, 0);
+						dcc_close (dcc, STAT_ABORTED, FALSE);
+					}
+				}
+			}
+			break;
+		case STAT_DONE:
+		case STAT_FAILED:
+		case STAT_ABORTED:
+			if (prefs.dcc_remove)
+				dcc_close (dcc, 0, TRUE);
+			break;
+		}
+		list = next;
+	}
+}
+
+static int
+dcc_lookup_proxy (char *host, struct sockaddr_in *addr)
+{
+	struct hostent *h;
+	static char *cache_host = NULL;
+	static guint32 cache_addr;
+
+	/* too lazy to thread this, so we cache results */
+	if (cache_host)
+	{
+		if (strcmp (host, cache_host) == 0)
+		{
+			memcpy (&addr->sin_addr, &cache_addr, 4);
+			return TRUE;
+		}
+		free (cache_host);
+		cache_host = NULL;
+	}
+
+	h = gethostbyname (host);
+	if (h != NULL && h->h_length == 4 && h->h_addr_list[0] != NULL)
+	{
+		memcpy (&addr->sin_addr, h->h_addr, 4);
+		memcpy (&cache_addr, h->h_addr, 4);
+		cache_host = strdup (host);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+#define DCC_USE_PROXY() (prefs.proxy_host[0] && prefs.proxy_type>0 && prefs.proxy_type<5 && prefs.proxy_use!=1)
+
+static int
+dcc_connect_sok (struct DCC *dcc)
+{
+	int sok;
+	struct sockaddr_in addr;
+
+	sok = socket (AF_INET, SOCK_STREAM, 0);
+	if (sok == -1)
+		return -1;
+
+	memset (&addr, 0, sizeof (addr));
+	addr.sin_family = AF_INET;
+	if (DCC_USE_PROXY ())
+	{
+		if (!dcc_lookup_proxy (prefs.proxy_host, &addr))
+		{
+			closesocket (sok);
+			return -1;
+		}
+		addr.sin_port = htons (prefs.proxy_port);
+	}
+	else
+	{
+		addr.sin_port = htons (dcc->port);
+		addr.sin_addr.s_addr = htonl (dcc->addr);
+	}
+
+	set_nonblocking (sok);
+	connect (sok, (struct sockaddr *) &addr, sizeof (addr));
+
+	return sok;
+}
+
+static void
+dcc_close (struct DCC *dcc, int dccstat, int destroy)
+{
+	if (dcc->wiotag)
+	{
+		fe_input_remove (dcc->wiotag);
+		dcc->wiotag = 0;
+	}
+
+	if (dcc->iotag)
+	{
+		fe_input_remove (dcc->iotag);
+		dcc->iotag = 0;
+	}
+
+	if (dcc->sok != -1)
+	{
+		closesocket (dcc->sok);
+		dcc->sok = -1;
+	}
+
+	dcc_remove_from_sum (dcc);
+
+	if (dcc->fp != -1)
+	{
+		close (dcc->fp);
+		dcc->fp = -1;
+
+		if(dccstat == STAT_DONE)
+		{
+			/* if we just completed a dcc recieve, move the */
+			/* completed file to the completed directory */
+			if(dcc->type == TYPE_RECV)
+			{			
+				/* mgl: change this to use destfile_fs for correctness and to */
+				/* handle the case where dccwithnick is set */
+				move_file_utf8 (prefs.dccdir, prefs.dcc_completed_dir, 
+									 file_part (dcc->destfile), prefs.dccpermissions);
+			}
+
+		}
+	}
+
+	dcc->dccstat = dccstat;
+	if (dcc->dccchat)
+	{
+		free (dcc->dccchat);
+		dcc->dccchat = NULL;
+	}
+
+	if (destroy)
+	{
+		dcc_list = g_slist_remove (dcc_list, dcc);
+		fe_dcc_remove (dcc);
+		if (dcc->proxy)
+			free (dcc->proxy);
+		if (dcc->file)
+			free (dcc->file);
+		if (dcc->destfile)
+			g_free (dcc->destfile);
+		if (dcc->destfile_fs)
+			g_free (dcc->destfile_fs);
+		free (dcc->nick);
+		free (dcc);
+		return;
+	}
+
+	fe_dcc_update (dcc);
+}
+
+void
+dcc_abort (session *sess, struct DCC *dcc)
+{
+	if (dcc)
+	{
+		switch (dcc->dccstat)
+		{
+		case STAT_QUEUED:
+		case STAT_CONNECTING:
+		case STAT_ACTIVE:
+			dcc_close (dcc, STAT_ABORTED, FALSE);
+			switch (dcc->type)
+			{
+			case TYPE_CHATSEND:
+			case TYPE_CHATRECV:
+				EMIT_SIGNAL (XP_TE_DCCCHATABORT, sess, dcc->nick, NULL, NULL,
+								 NULL, 0);
+				break;
+			case TYPE_SEND:
+				EMIT_SIGNAL (XP_TE_DCCSENDABORT, sess, dcc->nick,
+								 file_part (dcc->file), NULL, NULL, 0);
+				break;
+			case TYPE_RECV:
+				EMIT_SIGNAL (XP_TE_DCCRECVABORT, sess, dcc->nick,
+								 dcc->file, NULL, NULL, 0);
+			}
+			break;
+		default:
+			dcc_close (dcc, 0, TRUE);
+		}
+	}
+}
+
+void
+dcc_notify_kill (struct server *serv)
+{
+	struct server *replaceserv = 0;
+	struct DCC *dcc;
+	GSList *list = dcc_list;
+	if (serv_list)
+		replaceserv = (struct server *) serv_list->data;
+	while (list)
+	{
+		dcc = (struct DCC *) list->data;
+		if (dcc->serv == serv)
+			dcc->serv = replaceserv;
+		list = list->next;
+	}
+}
+
+struct DCC *
+dcc_write_chat (char *nick, char *text)
+{
+	struct DCC *dcc;
+	int len;
+
+	dcc = find_dcc (nick, "", TYPE_CHATRECV);
+	if (!dcc)
+		dcc = find_dcc (nick, "", TYPE_CHATSEND);
+	if (dcc && dcc->dccstat == STAT_ACTIVE)
+	{
+		len = strlen (text);
+		tcp_send_real (NULL, dcc->sok, dcc->serv->encoding, dcc->serv->using_irc,
+							text, len);
+		send (dcc->sok, "\n", 1, 0);
+		dcc->size += len;
+		fe_dcc_update (dcc);
+		return dcc;
+	}
+	return 0;
+}
+
+/* returns: 0 - ok
+				1 - the dcc is closed! */
+
+static int
+dcc_chat_line (struct DCC *dcc, char *line)
+{
+	session *sess;
+	char *word[PDIWORDS];
+	char *po;
+	char *utf;
+	char *conv;
+	int ret, i;
+	int len;
+	gsize utf_len;
+	char portbuf[32];
+
+	len = strlen (line);
+	if (dcc->serv->using_cp1255)
+		len++;	/* include the NUL terminator */
+
+	if (dcc->serv->using_irc) /* using "IRC" encoding (CP1252/UTF-8 hybrid) */
+		utf = NULL;
+	else if (dcc->serv->encoding == NULL)     /* system */
+		utf = g_locale_to_utf8 (line, len, NULL, &utf_len, NULL);
+	else
+		utf = g_convert (line, len, "UTF-8", dcc->serv->encoding, 0, &utf_len, 0);
+
+	if (utf)
+	{
+		line = utf;
+		len = utf_len;
+	}
+
+	if (dcc->serv->using_cp1255 && len > 0)
+		len--;
+
+	/* we really need valid UTF-8 now */
+	conv = text_validate (&line, &len);
+
+	sess = find_dialog (dcc->serv, dcc->nick);
+	if (!sess)
+		sess = dcc->serv->front_session;
+
+	sprintf (portbuf, "%d", dcc->port);
+
+	word[0] = "DCC Chat Text";
+	word[1] = net_ip (dcc->addr);
+	word[2] = portbuf;
+	word[3] = dcc->nick;
+	word[4] = line;
+	for (i = 5; i < PDIWORDS; i++)
+		word[i] = "\000";
+
+	ret = plugin_emit_print (sess, word);
+
+	/* did the plugin close it? */
+	if (!g_slist_find (dcc_list, dcc))
+	{
+		if (utf)
+			g_free (utf);
+		if (conv)
+			g_free (conv);
+		return 1;
+	}
+
+	/* did the plugin eat the event? */
+	if (ret)
+	{
+		if (utf)
+			g_free (utf);
+		if (conv)
+			g_free (conv);
+		return 0;
+	}
+
+	url_check_line (line, len);
+
+	if (line[0] == 1 && !strncasecmp (line + 1, "ACTION", 6))
+	{
+		po = strchr (line + 8, '\001');
+		if (po)
+			po[0] = 0;
+		inbound_action (sess, dcc->serv->nick, dcc->nick, "", line + 8, FALSE, FALSE);
+	} else
+	{
+		inbound_privmsg (dcc->serv, dcc->nick, "", line, FALSE);
+	}
+	if (utf)
+		g_free (utf);
+	if (conv)
+		g_free (conv);
+	return 0;
+}
+
+static gboolean
+dcc_read_chat (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	int i, len, dead;
+	char portbuf[32];
+	char lbuf[2050];
+
+	while (1)
+	{
+		if (dcc->throttled)
+		{
+			fe_input_remove (dcc->iotag);
+			dcc->iotag = 0;
+			return FALSE;
+		}
+
+		if (!dcc->iotag)
+			dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read_chat, dcc);
+
+		len = recv (dcc->sok, lbuf, sizeof (lbuf) - 2, 0);
+		if (len < 1)
+		{
+			if (len < 0)
+			{
+				if (would_block ())
+					return TRUE;
+			}
+			sprintf (portbuf, "%d", dcc->port);
+			EMIT_SIGNAL (XP_TE_DCCCHATF, dcc->serv->front_session, dcc->nick,
+							 net_ip (dcc->addr), portbuf,
+							 errorstring ((len < 0) ? sock_error () : 0), 0);
+			dcc_close (dcc, STAT_FAILED, FALSE);
+			return TRUE;
+		}
+		i = 0;
+		lbuf[len] = 0;
+		while (i < len)
+		{
+			switch (lbuf[i])
+			{
+			case '\r':
+				break;
+			case '\n':
+				dcc->dccchat->linebuf[dcc->dccchat->pos] = 0;
+				dead = dcc_chat_line (dcc, dcc->dccchat->linebuf);
+
+				if (dead || !dcc->dccchat) /* the dcc has been closed, don't use (DCC *)! */
+					return TRUE;
+
+				dcc->pos += dcc->dccchat->pos;
+				dcc->dccchat->pos = 0;
+				fe_dcc_update (dcc);
+				break;
+			default:
+				dcc->dccchat->linebuf[dcc->dccchat->pos] = lbuf[i];
+				if (dcc->dccchat->pos < (sizeof (dcc->dccchat->linebuf) - 1))
+					dcc->dccchat->pos++;
+			}
+			i++;
+		}
+	}
+}
+
+static void
+dcc_calc_average_cps (struct DCC *dcc)
+{
+	time_t sec;
+
+	sec = time (0) - dcc->starttime;
+	if (sec < 1)
+		sec = 1;
+	if (dcc->type == TYPE_SEND)
+		dcc->cps = (dcc->ack - dcc->resumable) / sec;
+	else
+		dcc->cps = (dcc->pos - dcc->resumable) / sec;
+}
+
+static void
+dcc_send_ack (struct DCC *dcc)
+{
+	/* send in 32-bit big endian */
+	guint32 pos = htonl (dcc->pos & 0xffffffff);
+	send (dcc->sok, (char *) &pos, 4, 0);
+}
+
+static gboolean
+dcc_read (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	char *old;
+	char buf[4096];
+	int n;
+	gboolean need_ack = FALSE;
+
+	if (dcc->fp == -1)
+	{
+
+		/* try to create the download dir (even if it exists, no harm) */
+		mkdir_utf8 (prefs.dccdir);
+
+		if (dcc->resumable)
+		{
+			dcc->fp = open (dcc->destfile_fs, O_WRONLY | O_APPEND | OFLAGS);
+			dcc->pos = dcc->resumable;
+			dcc->ack = dcc->resumable;
+		} else
+		{
+			if (access (dcc->destfile_fs, F_OK) == 0)
+			{
+				n = 0;
+				do
+				{
+					n++;
+					snprintf (buf, sizeof (buf), "%s.%d", dcc->destfile_fs, n);
+				}
+				while (access (buf, F_OK) == 0);
+
+				g_free (dcc->destfile_fs);
+				dcc->destfile_fs = g_strdup (buf);
+
+				old = dcc->destfile;
+				dcc->destfile = xchat_filename_to_utf8 (buf, -1, 0, 0, 0);
+
+				EMIT_SIGNAL (XP_TE_DCCRENAME, dcc->serv->front_session,
+								 old, dcc->destfile, NULL, NULL, 0);
+				g_free (old);
+			}
+			dcc->fp =
+				open (dcc->destfile_fs, OFLAGS | O_TRUNC | O_WRONLY | O_CREAT,
+						prefs.dccpermissions);
+		}
+	}
+	if (dcc->fp == -1)
+	{
+		/* the last executed function is open(), errno should be valid */
+		EMIT_SIGNAL (XP_TE_DCCFILEERR, dcc->serv->front_session, dcc->destfile,
+						 errorstring (errno), NULL, NULL, 0);
+		dcc_close (dcc, STAT_FAILED, FALSE);
+		return TRUE;
+	}
+	while (1)
+	{
+		if (dcc->throttled)
+		{
+			if (need_ack)
+				dcc_send_ack (dcc);
+
+			fe_input_remove (dcc->iotag);
+			dcc->iotag = 0;
+			return FALSE;
+		}
+
+		if (!dcc->iotag)
+			dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read, dcc);
+
+		n = recv (dcc->sok, buf, sizeof (buf), 0);
+		if (n < 1)
+		{
+			if (n < 0)
+			{
+				if (would_block ())
+				{
+					if (need_ack)
+						dcc_send_ack (dcc);
+					return TRUE;
+				}
+			}
+			EMIT_SIGNAL (XP_TE_DCCRECVERR, dcc->serv->front_session, dcc->file,
+							 dcc->destfile, dcc->nick,
+							 errorstring ((n < 0) ? sock_error () : 0), 0);
+			/* send ack here? but the socket is dead */
+			/*if (need_ack)
+				dcc_send_ack (dcc);*/
+			dcc_close (dcc, STAT_FAILED, FALSE);
+			return TRUE;
+		}
+
+		if (write (dcc->fp, buf, n) == -1) /* could be out of hdd space */
+		{
+			EMIT_SIGNAL (XP_TE_DCCRECVERR, dcc->serv->front_session, dcc->file,
+							 dcc->destfile, dcc->nick, errorstring (errno), 0);
+			if (need_ack)
+				dcc_send_ack (dcc);
+			dcc_close (dcc, STAT_FAILED, FALSE);
+			return TRUE;
+		}
+
+		dcc->lasttime = time (0);
+		dcc->pos += n;
+		need_ack = TRUE;	/* send ack when we're done recv()ing */
+
+		if (dcc->pos >= dcc->size)
+		{
+			dcc_send_ack (dcc);
+			dcc_close (dcc, STAT_DONE, FALSE);
+			dcc_calc_average_cps (dcc);	/* this must be done _after_ dcc_close, or dcc_remove_from_sum will see the wrong value in dcc->cps */
+			sprintf (buf, "%d", dcc->cps);
+			EMIT_SIGNAL (XP_TE_DCCRECVCOMP, dcc->serv->front_session,
+							 dcc->file, dcc->destfile, dcc->nick, buf, 0);
+			return TRUE;
+		}
+	}
+}
+
+static void
+dcc_open_query (server *serv, char *nick)
+{
+	if (prefs.autodialog)
+		open_query (serv, nick, FALSE);
+}
+
+static gboolean
+dcc_did_connect (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	int er;
+	struct sockaddr_in addr;
+	
+#ifdef WIN32
+	if (condition & G_IO_ERR)
+	{
+		int len;
+
+		/* find the last errno for this socket */
+		len = sizeof (er);
+		getsockopt (dcc->sok, SOL_SOCKET, SO_ERROR, (char *)&er, &len);
+		EMIT_SIGNAL (XP_TE_DCCCONFAIL, dcc->serv->front_session,
+						 dcctypes[dcc->type], dcc->nick, errorstring (er),
+						 NULL, 0);
+		dcc->dccstat = STAT_FAILED;
+		fe_dcc_update (dcc);
+		return FALSE;
+	}
+
+#else
+	memset (&addr, 0, sizeof (addr));
+	addr.sin_port = htons (dcc->port);
+	addr.sin_family = AF_INET;
+	addr.sin_addr.s_addr = htonl (dcc->addr);
+
+	/* check if it's already connected; This always fails on winXP */
+	if (connect (dcc->sok, (struct sockaddr *) &addr, sizeof (addr)) != 0)
+	{
+		er = sock_error ();
+		if (er != EISCONN)
+		{
+			EMIT_SIGNAL (XP_TE_DCCCONFAIL, dcc->serv->front_session,
+							 dcctypes[dcc->type], dcc->nick, errorstring (er),
+							 NULL, 0);
+			dcc->dccstat = STAT_FAILED;
+			fe_dcc_update (dcc);
+			return FALSE;
+		}
+	}
+#endif
+	
+	return TRUE;
+}
+
+static gboolean
+dcc_connect_finished (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	char host[128];
+
+	if (dcc->iotag)
+	{
+		fe_input_remove (dcc->iotag);
+		dcc->iotag = 0;
+	}
+
+	if (!dcc_did_connect (source, condition, dcc))
+		return TRUE;
+
+	dcc->dccstat = STAT_ACTIVE;
+	snprintf (host, sizeof host, "%s:%d", net_ip (dcc->addr), dcc->port);
+
+	switch (dcc->type)
+	{
+	case TYPE_RECV:
+		dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read, dcc);
+		EMIT_SIGNAL (XP_TE_DCCCONRECV, dcc->serv->front_session,
+						 dcc->nick, host, dcc->file, NULL, 0);
+		break;
+	case TYPE_SEND:
+		/* passive send */
+		dcc->fastsend = prefs.fastdccsend;
+		if (dcc->fastsend)
+			dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE, dcc_send_data, dcc);
+		dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read_ack, dcc);
+		dcc_send_data (NULL, 0, (gpointer)dcc);
+		EMIT_SIGNAL (XP_TE_DCCCONSEND, dcc->serv->front_session,
+						 dcc->nick, host, dcc->file, NULL, 0);
+		break;
+	case TYPE_CHATSEND:	/* pchat */
+		dcc_open_query (dcc->serv, dcc->nick);
+	case TYPE_CHATRECV:	/* normal chat */
+		dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read_chat, dcc);
+		dcc->dccchat = malloc (sizeof (struct dcc_chat));
+		dcc->dccchat->pos = 0;
+		EMIT_SIGNAL (XP_TE_DCCCONCHAT, dcc->serv->front_session,
+						 dcc->nick, host, NULL, NULL, 0);
+		break;
+	}
+	dcc->starttime = time (0);
+	dcc->lasttime = dcc->starttime;
+	fe_dcc_update (dcc);
+
+	return TRUE;
+}
+
+static gboolean
+read_proxy (struct DCC *dcc)
+{
+	struct proxy_state *proxy = dcc->proxy;
+	while (proxy->bufferused < proxy->buffersize)
+	{
+		int ret = recv (dcc->sok, &proxy->buffer[proxy->bufferused],
+						proxy->buffersize - proxy->bufferused, 0);
+		if (ret > 0)
+			proxy->bufferused += ret;
+		else
+		{
+			if (would_block ())
+				return FALSE;
+			else
+			{
+				dcc->dccstat = STAT_FAILED;
+				fe_dcc_update (dcc);
+				if (dcc->iotag)
+				{
+					fe_input_remove (dcc->iotag);
+					dcc->iotag = 0;
+				}
+				return FALSE;
+			}
+		}
+	}
+	return TRUE;
+}
+
+static gboolean
+write_proxy (struct DCC *dcc)
+{
+	struct proxy_state *proxy = dcc->proxy;
+	while (proxy->bufferused < proxy->buffersize)
+	{
+		int ret = send (dcc->sok, &proxy->buffer[proxy->bufferused],
+						proxy->buffersize - proxy->bufferused, 0);
+		if (ret >= 0)
+			proxy->bufferused += ret;
+		else
+		{
+			if (would_block ())
+				return FALSE;
+			else
+			{
+				dcc->dccstat = STAT_FAILED;
+				fe_dcc_update (dcc);
+				if (dcc->wiotag)
+				{
+					fe_input_remove (dcc->wiotag);
+					dcc->wiotag = 0;
+				}
+				return FALSE;
+			}
+		}
+	}
+	return TRUE;
+}
+
+static gboolean
+proxy_read_line (struct DCC *dcc)
+{
+	struct proxy_state *proxy = dcc->proxy;
+	while (1)
+	{
+		proxy->buffersize = proxy->bufferused + 1;
+		if (!read_proxy (dcc))
+			return FALSE;
+		if (proxy->buffer[proxy->bufferused - 1] == '\n'
+			|| proxy->bufferused == MAX_PROXY_BUFFER)
+		{
+			proxy->buffer[proxy->bufferused - 1] = 0;
+			return TRUE;
+		}
+	}
+}
+
+static gboolean
+dcc_wingate_proxy_traverse (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	struct proxy_state *proxy = dcc->proxy;
+	if (proxy->phase == 0)
+	{
+		proxy->buffersize = snprintf ((char*) proxy->buffer, MAX_PROXY_BUFFER,
+										"%s %d\r\n", net_ip(dcc->addr),
+										dcc->port);
+		proxy->bufferused = 0;
+		dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX,
+									dcc_wingate_proxy_traverse, dcc);
+		++proxy->phase;
+	}
+	if (proxy->phase == 1)
+	{
+		if (!read_proxy (dcc))
+			return TRUE;
+		fe_input_remove (dcc->wiotag);
+		dcc->wiotag = 0;
+		dcc_connect_finished (source, 0, dcc);
+	}
+	return TRUE;
+}
+
+struct sock_connect
+{
+	char version;
+	char type;
+	guint16 port;
+	guint32 address;
+	char username[10];
+};
+static gboolean
+dcc_socks_proxy_traverse (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	struct proxy_state *proxy = dcc->proxy;
+
+	if (proxy->phase == 0)
+	{
+		struct sock_connect sc;
+		sc.version = 4;
+		sc.type = 1;
+		sc.port = htons (dcc->port);
+		sc.address = htonl (dcc->addr);
+		strncpy (sc.username, prefs.username, 9);
+		memcpy (proxy->buffer, &sc, sizeof (sc));
+		proxy->buffersize = 8 + strlen (sc.username) + 1;
+		proxy->bufferused = 0;
+		dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX,
+									dcc_socks_proxy_traverse, dcc);
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 1)
+	{
+		if (!write_proxy (dcc))
+			return TRUE;
+		fe_input_remove (dcc->wiotag);
+		dcc->wiotag = 0;
+		proxy->bufferused = 0;
+		proxy->buffersize = 8;
+		dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX,
+									dcc_socks_proxy_traverse, dcc);
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 2)
+	{
+		if (!read_proxy (dcc))
+			return TRUE;
+		fe_input_remove (dcc->iotag);
+		dcc->iotag = 0;
+		if (proxy->buffer[1] == 90)
+			dcc_connect_finished (source, 0, dcc);
+		else
+		{
+			dcc->dccstat = STAT_FAILED;
+			fe_dcc_update (dcc);
+		}
+	}
+
+	return TRUE;
+}
+
+struct sock5_connect1
+{
+        char version;
+        char nmethods;
+        char method;
+};
+static gboolean
+dcc_socks5_proxy_traverse (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	struct proxy_state *proxy = dcc->proxy;
+	int auth = prefs.proxy_auth && prefs.proxy_user[0] && prefs.proxy_pass[0];
+
+	if (proxy->phase == 0)
+	{
+		struct sock5_connect1 sc1;
+
+		sc1.version = 5;
+		sc1.nmethods = 1;
+		sc1.method = 0;
+		if (auth)
+			sc1.method = 2;
+		memcpy (proxy->buffer, &sc1, 3);
+		proxy->buffersize = 3;
+		proxy->bufferused = 0;
+		dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX,
+									dcc_socks5_proxy_traverse, dcc);
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 1)
+	{
+		if (!write_proxy (dcc))
+			return TRUE;
+		fe_input_remove (dcc->wiotag);
+		dcc->wiotag = 0;
+		proxy->bufferused = 0;
+		proxy->buffersize = 2;
+		dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX,
+									dcc_socks5_proxy_traverse, dcc);
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 2)
+	{
+		if (!read_proxy (dcc))
+			return TRUE;
+		fe_input_remove (dcc->iotag);
+		dcc->iotag = 0;
+
+		/* did the server say no auth required? */
+		if (proxy->buffer[0] == 5 && proxy->buffer[1] == 0)
+			auth = 0;
+
+		/* Set up authentication I/O */
+		if (auth)
+		{
+			int len_u=0, len_p=0;
+
+			/* authentication sub-negotiation (RFC1929) */
+			if ( proxy->buffer[0] != 5 || proxy->buffer[1] != 2 )  /* UPA not supported by server */
+			{
+				PrintText (dcc->serv->front_session, "SOCKS\tServer doesn't support UPA authentication.\n");
+				dcc->dccstat = STAT_FAILED;
+				fe_dcc_update (dcc);
+				return TRUE;
+			}
+
+			memset (proxy->buffer, 0, MAX_PROXY_BUFFER);
+
+			/* form the UPA request */
+			len_u = strlen (prefs.proxy_user);
+			len_p = strlen (prefs.proxy_pass);
+			proxy->buffer[0] = 1;
+			proxy->buffer[1] = len_u;
+			memcpy (proxy->buffer + 2, prefs.proxy_user, len_u);
+			proxy->buffer[2 + len_u] = len_p;
+			memcpy (proxy->buffer + 3 + len_u, prefs.proxy_pass, len_p);
+
+			proxy->buffersize = 3 + len_u + len_p;
+			proxy->bufferused = 0;
+			dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX,
+										dcc_socks5_proxy_traverse, dcc);
+			++proxy->phase;
+		}
+		else
+		{
+			if (proxy->buffer[0] != 5 || proxy->buffer[1] != 0)
+			{
+				PrintText (dcc->serv->front_session, "SOCKS\tAuthentication required but disabled in settings.\n");
+				dcc->dccstat = STAT_FAILED;
+				fe_dcc_update (dcc);
+				return TRUE;
+			}
+			proxy->phase += 2;
+		}
+	}
+	
+	if (proxy->phase == 3)
+	{
+		if (!write_proxy (dcc))
+			return TRUE;
+		fe_input_remove (dcc->wiotag);
+		dcc->wiotag = 0;
+		proxy->buffersize = 2;
+		proxy->bufferused = 0;
+		dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX,
+									dcc_socks5_proxy_traverse, dcc);
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 4)
+	{
+		if (!read_proxy (dcc))
+			return TRUE;
+		if (dcc->iotag)
+		{
+			fe_input_remove (dcc->iotag);
+			dcc->iotag = 0;
+		}
+		if (proxy->buffer[1] != 0)
+		{
+			PrintText (dcc->serv->front_session, "SOCKS\tAuthentication failed. "
+							 "Is username and password correct?\n");
+			dcc->dccstat = STAT_FAILED;
+			fe_dcc_update (dcc);
+			return TRUE;
+		}
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 5)
+	{
+		proxy->buffer[0] = 5;	/* version (socks 5) */
+		proxy->buffer[1] = 1;	/* command (connect) */
+		proxy->buffer[2] = 0;	/* reserved */
+		proxy->buffer[3] = 1;	/* address type (IPv4) */
+		proxy->buffer[4] = (dcc->addr >> 24) & 0xFF;	/* IP address */
+		proxy->buffer[5] = (dcc->addr >> 16) & 0xFF;
+		proxy->buffer[6] = (dcc->addr >> 8) & 0xFF;
+		proxy->buffer[7] = (dcc->addr & 0xFF);
+		proxy->buffer[8] = (dcc->port >> 8) & 0xFF;		/* port */
+		proxy->buffer[9] = (dcc->port & 0xFF);
+		proxy->buffersize = 10;
+		proxy->bufferused = 0;
+		dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX,
+									dcc_socks5_proxy_traverse, dcc);
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 6)
+	{
+		if (!write_proxy (dcc))
+			return TRUE;
+		fe_input_remove (dcc->wiotag);
+		dcc->wiotag = 0;
+		proxy->buffersize = 4;
+		proxy->bufferused = 0;
+		dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX,
+									dcc_socks5_proxy_traverse, dcc);
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 7)
+	{
+		if (!read_proxy (dcc))
+			return TRUE;
+		if (proxy->buffer[0] != 5 || proxy->buffer[1] != 0)
+		{
+			fe_input_remove (dcc->iotag);
+			dcc->iotag = 0;
+			if (proxy->buffer[1] == 2)
+				PrintText (dcc->serv->front_session, "SOCKS\tProxy refused to connect to host (not allowed).\n");
+			else
+				PrintTextf (dcc->serv->front_session, "SOCKS\tProxy failed to connect to host (error %d).\n", proxy->buffer[1]);
+			dcc->dccstat = STAT_FAILED;
+			fe_dcc_update (dcc);
+			return TRUE;
+		}
+		switch (proxy->buffer[3])
+		{
+			case 1: proxy->buffersize += 6; break;
+			case 3: proxy->buffersize += 1; break;
+			case 4: proxy->buffersize += 18; break;
+		};
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 8)
+	{
+		if (!read_proxy (dcc))
+			return TRUE;
+		/* handle domain name case */
+		if (proxy->buffer[3] == 3)
+		{
+			proxy->buffersize = 5 + proxy->buffer[4] + 2;
+		}
+		/* everything done? */
+		if (proxy->bufferused == proxy->buffersize)
+		{
+			fe_input_remove (dcc->iotag);
+			dcc->iotag = 0;
+			dcc_connect_finished (source, 0, dcc);
+		}
+	}
+	return TRUE;
+}
+
+static gboolean
+dcc_http_proxy_traverse (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	struct proxy_state *proxy = dcc->proxy;
+
+	if (proxy->phase == 0)
+	{
+		char buf[256];
+		char auth_data[128];
+		char auth_data2[68];
+		int n, n2;
+
+		n = snprintf (buf, sizeof (buf), "CONNECT %s:%d HTTP/1.0\r\n",
+                                          net_ip(dcc->addr), dcc->port);
+		if (prefs.proxy_auth)
+		{
+			n2 = snprintf (auth_data2, sizeof (auth_data2), "%s:%s",
+							prefs.proxy_user, prefs.proxy_pass);
+			base64_encode (auth_data, auth_data2, n2);
+			n += snprintf (buf+n, sizeof (buf)-n, "Proxy-Authorization: Basic %s\r\n", auth_data);
+		}
+		n += snprintf (buf+n, sizeof (buf)-n, "\r\n");
+		proxy->buffersize = n;
+		proxy->bufferused = 0;
+		memcpy (proxy->buffer, buf, proxy->buffersize);
+		dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX,
+									dcc_http_proxy_traverse, dcc);
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 1)
+	{
+		if (!write_proxy (dcc))
+			return TRUE;
+		fe_input_remove (dcc->wiotag);
+		dcc->wiotag = 0;
+		proxy->bufferused = 0;
+		dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX,
+										dcc_http_proxy_traverse, dcc);
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 2)
+	{
+		if (!proxy_read_line (dcc))
+			return TRUE;
+		/* "HTTP/1.0 200 OK" */
+		if (proxy->bufferused < 12 ||
+			 memcmp (proxy->buffer, "HTTP/", 5) || memcmp (proxy->buffer + 9, "200", 3))
+		{
+			fe_input_remove (dcc->iotag);
+			dcc->iotag = 0;
+			PrintText (dcc->serv->front_session, proxy->buffer);
+			dcc->dccstat = STAT_FAILED;
+			fe_dcc_update (dcc);
+			return TRUE;
+		}
+		proxy->bufferused = 0;
+		++proxy->phase;
+	}
+
+	if (proxy->phase == 3)
+	{
+		while (1)
+		{
+			/* read until blank line */
+			if (proxy_read_line (dcc))
+			{
+				if (proxy->bufferused < 1 ||
+					(proxy->bufferused == 2 && proxy->buffer[0] == '\r'))
+				{
+					break;
+				}
+				if (proxy->bufferused > 1)
+					PrintText (dcc->serv->front_session, proxy->buffer);
+				proxy->bufferused = 0;
+			}
+			else
+				return TRUE;
+		}
+		fe_input_remove (dcc->iotag);
+		dcc->iotag = 0;
+		dcc_connect_finished (source, 0, dcc);
+	}
+
+	return TRUE;
+}
+
+static gboolean
+dcc_proxy_connect (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	fe_input_remove (dcc->iotag);
+	dcc->iotag = 0;
+
+	if (!dcc_did_connect (source, condition, dcc))
+		return TRUE;
+
+	dcc->proxy = malloc (sizeof (struct proxy_state));
+	if (!dcc->proxy)
+	{
+		dcc->dccstat = STAT_FAILED;
+		fe_dcc_update (dcc);
+		return TRUE;
+	}
+	memset (dcc->proxy, 0, sizeof (struct proxy_state));
+
+	switch (prefs.proxy_type)
+	{
+		case 1: return dcc_wingate_proxy_traverse (source, condition, dcc);
+		case 2: return dcc_socks_proxy_traverse (source, condition, dcc);
+		case 3: return dcc_socks5_proxy_traverse (source, condition, dcc);
+		case 4: return dcc_http_proxy_traverse (source, condition, dcc);
+	}
+	return TRUE;
+}
+
+static int dcc_listen_init (struct DCC *, struct session *);
+
+static void
+dcc_connect (struct DCC *dcc)
+{
+	int ret;
+	char tbuf[400];
+
+	if (dcc->dccstat == STAT_CONNECTING)
+		return;
+	dcc->dccstat = STAT_CONNECTING;
+
+	if (dcc->pasvid && dcc->port == 0)
+	{
+		/* accepted a passive dcc send */
+		ret = dcc_listen_init (dcc, dcc->serv->front_session);
+		if (!ret)
+		{
+			dcc_close (dcc, STAT_FAILED, FALSE);
+			return;
+		}
+		/* possible problems with filenames containing spaces? */
+		if (dcc->type == TYPE_RECV)
+			snprintf (tbuf, sizeof (tbuf), strchr (dcc->file, ' ') ?
+					"DCC SEND \"%s\" %u %d %"DCC_SFMT" %d" :
+					"DCC SEND %s %u %d %"DCC_SFMT" %d", dcc->file,
+					dcc->addr, dcc->port, dcc->size, dcc->pasvid);
+		else
+			snprintf (tbuf, sizeof (tbuf), "DCC CHAT chat %u %d %d",
+				dcc->addr, dcc->port, dcc->pasvid);
+		dcc->serv->p_ctcp (dcc->serv, dcc->nick, tbuf);
+	}
+	else
+	{
+		dcc->sok = dcc_connect_sok (dcc);
+		if (dcc->sok == -1)
+		{
+			dcc->dccstat = STAT_FAILED;
+			fe_dcc_update (dcc);
+			return;
+		}
+		if (DCC_USE_PROXY ())
+			dcc->iotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, dcc_proxy_connect, dcc);
+		else
+			dcc->iotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, dcc_connect_finished, dcc);
+	}
+	
+	fe_dcc_update (dcc);
+}
+
+static gboolean
+dcc_send_data (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	char *buf;
+	int len, sent, sok = dcc->sok;
+
+	if (prefs.dcc_blocksize < 1) /* this is too little! */
+		prefs.dcc_blocksize = 1024;
+
+	if (prefs.dcc_blocksize > 102400)	/* this is too much! */
+		prefs.dcc_blocksize = 102400;
+
+	if (dcc->throttled)
+	{
+		fe_input_remove (dcc->wiotag);
+		dcc->wiotag = 0;
+		return FALSE;
+	}
+
+	if (!dcc->fastsend)
+	{
+		if (dcc->ack < dcc->pos)
+			return TRUE;
+	}
+	else if (!dcc->wiotag)
+		dcc->wiotag = fe_input_add (sok, FIA_WRITE, dcc_send_data, dcc);
+
+	buf = malloc (prefs.dcc_blocksize);
+	if (!buf)
+		return TRUE;
+
+	lseek (dcc->fp, dcc->pos, SEEK_SET);
+	len = read (dcc->fp, buf, prefs.dcc_blocksize);
+	if (len < 1)
+		goto abortit;
+	sent = send (sok, buf, len, 0);
+
+	if (sent < 0 && !(would_block ()))
+	{
+abortit:
+		free (buf);
+		EMIT_SIGNAL (XP_TE_DCCSENDFAIL, dcc->serv->front_session,
+						 file_part (dcc->file), dcc->nick,
+						 errorstring (sock_error ()), NULL, 0);
+		dcc_close (dcc, STAT_FAILED, FALSE);
+		return TRUE;
+	}
+	if (sent > 0)
+	{
+		dcc->pos += sent;
+		dcc->lasttime = time (0);
+	}
+
+	/* have we sent it all yet? */
+	if (dcc->pos >= dcc->size)
+	{
+		/* it's all sent now, so remove the WRITE/SEND handler */
+		if (dcc->wiotag)
+		{
+			fe_input_remove (dcc->wiotag);
+			dcc->wiotag = 0;
+		}
+	}
+
+	free (buf);
+
+	return TRUE;
+}
+
+static gboolean
+dcc_handle_new_ack (struct DCC *dcc)
+{
+	guint32 ack;
+	char buf[16];
+	gboolean done = FALSE;
+
+	memcpy (&ack, dcc->ack_buf, 4);
+	dcc->ack = ntohl (ack);
+
+	/* this could mess up when xfering >32bit files */
+	if (dcc->size <= 0xffffffff)
+	{
+		/* fix for BitchX */
+		if (dcc->ack < dcc->resumable)
+			dcc->ackoffset = TRUE;
+		if (dcc->ackoffset)
+			dcc->ack += dcc->resumable;
+	}
+
+	/* DCC complete check */
+	if (dcc->pos >= dcc->size && dcc->ack >= (dcc->size & 0xffffffff))
+	{
+		dcc->ack = dcc->size;	/* force 100% ack for >4 GB */
+		dcc_close (dcc, STAT_DONE, FALSE);
+		dcc_calc_average_cps (dcc);	/* this must be done _after_ dcc_close, or dcc_remove_from_sum will see the wrong value in dcc->cps */
+		sprintf (buf, "%d", dcc->cps);
+		EMIT_SIGNAL (XP_TE_DCCSENDCOMP, dcc->serv->front_session,
+						 file_part (dcc->file), dcc->nick, buf, NULL, 0);
+		done = TRUE;
+	}
+	else if ((!dcc->fastsend) && (dcc->ack >= (dcc->pos & 0xffffffff)))
+	{
+		dcc_send_data (NULL, 0, (gpointer)dcc);
+	}
+
+#ifdef USE_DCC64
+	/* take the top 32 of "bytes send" and bottom 32 of "ack" */
+	dcc->ack = (dcc->pos & G_GINT64_CONSTANT (0xffffffff00000000)) |
+					(dcc->ack & 0xffffffff);
+	/* dcc->ack is only used for CPS and PERCENTAGE calcs from now on... */
+#endif
+
+	return done;
+}
+
+static gboolean
+dcc_read_ack (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	int len;
+
+	while (1)
+	{
+		/* try to fill up 4 bytes */
+		len = recv (dcc->sok, dcc->ack_buf, 4 - dcc->ack_pos, 0);
+		if (len < 1)
+		{
+			if (len < 0)
+			{
+				if (would_block ())	/* ok - keep waiting */
+					return TRUE;
+			}
+			EMIT_SIGNAL (XP_TE_DCCSENDFAIL, dcc->serv->front_session,
+							 file_part (dcc->file), dcc->nick,
+							 errorstring ((len < 0) ? sock_error () : 0), NULL, 0);
+			dcc_close (dcc, STAT_FAILED, FALSE);
+			return TRUE;
+		}
+
+		dcc->ack_pos += len;
+		if (dcc->ack_pos >= 4)
+		{
+			dcc->ack_pos = 0;
+			if (dcc_handle_new_ack (dcc))
+				return TRUE;
+		}
+		/* loop again until would_block() returns true */
+	}
+}
+
+static gboolean
+dcc_accept (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
+{
+	char host[128];
+	struct sockaddr_in CAddr;
+	int sok;
+	socklen_t len;
+
+	len = sizeof (CAddr);
+	sok = accept (dcc->sok, (struct sockaddr *) &CAddr, &len);
+	fe_input_remove (dcc->iotag);
+	dcc->iotag = 0;
+	closesocket (dcc->sok);
+	if (sok < 0)
+	{
+		dcc->sok = -1;
+		dcc_close (dcc, STAT_FAILED, FALSE);
+		return TRUE;
+	}
+	set_nonblocking (sok);
+	dcc->sok = sok;
+	dcc->addr = ntohl (CAddr.sin_addr.s_addr);
+
+	if (dcc->pasvid)
+		return dcc_connect_finished (NULL, 0, dcc);
+
+	dcc->dccstat = STAT_ACTIVE;
+	dcc->lasttime = dcc->starttime = time (0);
+	dcc->fastsend = prefs.fastdccsend;
+
+	snprintf (host, sizeof (host), "%s:%d", net_ip (dcc->addr), dcc->port);
+
+	switch (dcc->type)
+	{
+	case TYPE_SEND:
+		if (dcc->fastsend)
+			dcc->wiotag = fe_input_add (sok, FIA_WRITE, dcc_send_data, dcc);
+		dcc->iotag = fe_input_add (sok, FIA_READ|FIA_EX, dcc_read_ack, dcc);
+		dcc_send_data (NULL, 0, (gpointer)dcc);
+		EMIT_SIGNAL (XP_TE_DCCCONSEND, dcc->serv->front_session,
+						 dcc->nick, host, dcc->file, NULL, 0);
+		break;
+
+	case TYPE_CHATSEND:
+		dcc_open_query (dcc->serv, dcc->nick);
+		dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read_chat, dcc);
+		dcc->dccchat = malloc (sizeof (struct dcc_chat));
+		dcc->dccchat->pos = 0;
+		EMIT_SIGNAL (XP_TE_DCCCONCHAT, dcc->serv->front_session,
+						 dcc->nick, host, NULL, NULL, 0);
+		break;
+	}
+
+	fe_dcc_update (dcc);
+
+	return TRUE;
+}
+
+guint32
+dcc_get_my_address (void)	/* the address we'll tell the other person */
+{
+	struct hostent *dns_query;
+	guint32 addr = 0;
+
+	if (prefs.ip_from_server && prefs.dcc_ip)
+		addr = prefs.dcc_ip;
+	else if (prefs.dcc_ip_str[0])
+	{
+	   dns_query = gethostbyname ((const char *) prefs.dcc_ip_str);
+
+	   if (dns_query != NULL &&
+	       dns_query->h_length == 4 &&
+	       dns_query->h_addr_list[0] != NULL)
+		{
+			/*we're offered at least one IPv4 address: we take the first*/
+			addr = *((guint32*) dns_query->h_addr_list[0]);
+		}
+	}
+
+	return addr;
+}
+
+static int
+dcc_listen_init (struct DCC *dcc, session *sess)
+{
+	guint32 my_addr;
+	struct sockaddr_in SAddr;
+	int i, bindretval = -1;
+	socklen_t len;
+
+	dcc->sok = socket (AF_INET, SOCK_STREAM, 0);
+	if (dcc->sok == -1)
+		return FALSE;
+
+	memset (&SAddr, 0, sizeof (struct sockaddr_in));
+
+	len = sizeof (SAddr);
+	getsockname (dcc->serv->sok, (struct sockaddr *) &SAddr, &len);
+
+	SAddr.sin_family = AF_INET;
+
+	/*if local_ip is specified use that*/
+	if (prefs.local_ip != 0xffffffff)
+	{
+		my_addr = prefs.local_ip;
+		SAddr.sin_addr.s_addr = prefs.local_ip;
+	}
+	/*otherwise use the default*/
+	else
+		my_addr = SAddr.sin_addr.s_addr;
+
+	/*if we have a valid portrange try to use that*/
+	if (prefs.first_dcc_send_port > 0)
+	{
+		SAddr.sin_port = 0;
+		i = 0;
+		while ((prefs.last_dcc_send_port > ntohs(SAddr.sin_port)) &&
+				(bindretval == -1))
+		{
+			SAddr.sin_port = htons (prefs.first_dcc_send_port + i);
+			i++;
+			/*printf("Trying to bind against port: %d\n",ntohs(SAddr.sin_port));*/
+			bindretval = bind (dcc->sok, (struct sockaddr *) &SAddr, sizeof (SAddr));
+		}
+
+		/* with a small port range, reUseAddr is needed */
+		len = 1;
+		setsockopt (dcc->sok, SOL_SOCKET, SO_REUSEADDR, (char *) &len, sizeof (len));
+
+	} else
+	{
+		/* try random port */
+		SAddr.sin_port = 0;
+		bindretval = bind (dcc->sok, (struct sockaddr *) &SAddr, sizeof (SAddr));
+	}
+
+	if (bindretval == -1)
+	{
+		/* failed to bind */
+		PrintText (sess, "Failed to bind to any address or port.\n");
+		return FALSE;
+	}
+
+	len = sizeof (SAddr);
+	getsockname (dcc->sok, (struct sockaddr *) &SAddr, &len);
+
+	dcc->port = ntohs (SAddr.sin_port);
+
+	/*if we have a dcc_ip, we use that, so the remote client can connect*/
+	/*else we try to take an address from dcc_ip_str*/
+	/*if something goes wrong we tell the client to connect to our LAN ip*/
+	dcc->addr = dcc_get_my_address ();
+
+	/*if nothing else worked we use the address we bound to*/
+	if (dcc->addr == 0)
+	   dcc->addr = my_addr;
+
+	dcc->addr = ntohl (dcc->addr);
+
+	set_nonblocking (dcc->sok);
+	listen (dcc->sok, 1);
+	set_blocking (dcc->sok);
+
+	dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_accept, dcc);
+
+	return TRUE;
+}
+
+static struct session *dccsess;
+static char *dccto;				  /* lame!! */
+static int dccmaxcps;
+static int recursive = FALSE;
+
+static void
+dcc_send_wild (char *file)
+{
+	dcc_send (dccsess, dccto, file, dccmaxcps, 0);
+}
+
+void
+dcc_send (struct session *sess, char *to, char *file, int maxcps, int passive)
+{
+	char outbuf[512];
+	struct stat st;
+	struct DCC *dcc;
+	char *file_fs;
+
+	/* this is utf8 */
+	file = expand_homedir (file);
+
+	if (!recursive && (strchr (file, '*') || strchr (file, '?')))
+	{
+		char path[256];
+		char wild[256];
+		char *path_fs;	/* local filesystem encoding */
+
+		safe_strcpy (wild, file_part (file), sizeof (wild));
+		path_part (file, path, sizeof (path));
+		if (path[0] != '/' || path[1] != '\0')
+			path[strlen (path) - 1] = 0;	/* remove trailing slash */
+
+		dccsess = sess;
+		dccto = to;
+		dccmaxcps = maxcps;
+
+		free (file);
+
+		/* for_files() will use opendir, so we need local FS encoding */
+		path_fs = xchat_filename_from_utf8 (path, -1, 0, 0, 0);
+		if (path_fs)
+		{
+			recursive = TRUE;
+			for_files (path_fs, wild, dcc_send_wild);
+			recursive = FALSE;
+			g_free (path_fs);
+		}
+
+		return;
+	}
+
+	dcc = new_dcc ();
+	if (!dcc)
+		return;
+	dcc->file = file;
+	dcc->maxcps = maxcps;
+
+	/* get the local filesystem encoding */
+	file_fs = xchat_filename_from_utf8 (file, -1, 0, 0, 0);
+
+	if (stat (file_fs, &st) != -1)
+	{
+
+#ifndef USE_DCC64
+		if (sizeof (st.st_size) > 4 && st.st_size > 4294967295U)
+		{
+			PrintText (sess, "Cannot send files larger than 4 GB.\n");
+			goto xit;
+		}
+#endif
+
+		if (!(*file_part (file_fs)) || S_ISDIR (st.st_mode) || st.st_size < 1)
+		{
+			PrintText (sess, "Cannot send directories or empty files.\n");
+			goto xit;
+		}
+
+		dcc->starttime = dcc->offertime = time (0);
+		dcc->serv = sess->server;
+		dcc->dccstat = STAT_QUEUED;
+		dcc->size = st.st_size;
+		dcc->type = TYPE_SEND;
+		dcc->fp = open (file_fs, OFLAGS | O_RDONLY);
+		if (dcc->fp != -1)
+		{
+			g_free (file_fs);
+			if (passive || dcc_listen_init (dcc, sess))
+			{
+				char havespaces = 0;
+				file = dcc->file;
+				while (*file)
+				{
+					if (*file == ' ')
+					{
+						if (prefs.dcc_send_fillspaces)
+				    		*file = '_';
+					  	else
+					   	havespaces = 1;
+					}
+					file++;
+				}
+				dcc->nick = strdup (to);
+				if (prefs.autoopendccsendwindow)
+				{
+					if (fe_dcc_open_send_win (TRUE))	/* already open? add */
+						fe_dcc_add (dcc);
+				} else
+					fe_dcc_add (dcc);
+
+				if (passive)
+				{
+					dcc->pasvid = new_id();
+					snprintf (outbuf, sizeof (outbuf), (havespaces) ?
+							"DCC SEND \"%s\" 199 0 %"DCC_SFMT" %d" :
+							"DCC SEND %s 199 0 %"DCC_SFMT" %d",
+							file_part (dcc->file),
+							dcc->size, dcc->pasvid);
+				} else
+				{
+					snprintf (outbuf, sizeof (outbuf), (havespaces) ?
+							"DCC SEND \"%s\" %u %d %"DCC_SFMT :
+							"DCC SEND %s %u %d %"DCC_SFMT,
+							file_part (dcc->file), dcc->addr,
+							dcc->port, dcc->size);
+				}
+				sess->server->p_ctcp (sess->server, to, outbuf);
+
+				EMIT_SIGNAL (XP_TE_DCCOFFER, sess, file_part (dcc->file),
+								 to, dcc->file, NULL, 0);
+			} else
+			{
+				dcc_close (dcc, 0, TRUE);
+			}
+			return;
+		}
+	}
+	PrintTextf (sess, _("Cannot access %s\n"), dcc->file);
+	PrintTextf (sess, "%s %d: %s\n", _("Error"), errno, errorstring (errno));
+xit:
+	g_free (file_fs);
+	dcc_close (dcc, 0, TRUE);
+}
+
+static struct DCC *
+find_dcc_from_id (int id, int type)
+{
+	struct DCC *dcc;
+	GSList *list = dcc_list;
+	while (list)
+	{
+		dcc = (struct DCC *) list->data;
+		if (dcc->pasvid == id &&
+			 dcc->dccstat == STAT_QUEUED && dcc->type == type)
+		return dcc;
+		list = list->next;
+	}
+	return 0;
+}
+
+static struct DCC *
+find_dcc_from_port (int port, int type)
+{
+	struct DCC *dcc;
+	GSList *list = dcc_list;
+	while (list)
+	{
+		dcc = (struct DCC *) list->data;
+		if (dcc->port == port &&
+			 dcc->dccstat == STAT_QUEUED && dcc->type == type)
+			return dcc;
+		list = list->next;
+	}
+	return 0;
+}
+
+struct DCC *
+find_dcc (char *nick, char *file, int type)
+{
+	GSList *list = dcc_list;
+	struct DCC *dcc;
+	while (list)
+	{
+		dcc = (struct DCC *) list->data;
+		if (nick == NULL || !rfc_casecmp (nick, dcc->nick))
+		{
+			if (type == -1 || dcc->type == type)
+			{
+				if (!file[0])
+					return dcc;
+				if (!strcasecmp (file, file_part (dcc->file)))
+					return dcc;
+				if (!strcasecmp (file, dcc->file))
+					return dcc;
+			}
+		}
+		list = list->next;
+	}
+	return 0;
+}
+
+/* called when we receive a NICK change from server */
+
+void
+dcc_change_nick (struct server *serv, char *oldnick, char *newnick)
+{
+	struct DCC *dcc;
+	GSList *list = dcc_list;
+
+	while (list)
+	{
+		dcc = (struct DCC *) list->data;
+		if (dcc->serv == serv)
+		{
+			if (!serv->p_cmp (dcc->nick, oldnick))
+			{
+				if (dcc->nick)
+					free (dcc->nick);
+				dcc->nick = strdup (newnick);
+			}
+		}
+		list = list->next;
+	}
+}
+
+/* is the destination file the same? new_dcc is not opened yet */
+
+static int
+is_same_file (struct DCC *dcc, struct DCC *new_dcc)
+{
+	struct stat st_a, st_b;
+
+	/* if it's the same filename, must be same */
+	if (strcmp (dcc->destfile, new_dcc->destfile) == 0)
+		return TRUE;
+
+	/* now handle case-insensitive Filesystems: HFS+, FAT */
+#ifdef WIN32
+#warning no win32 implementation - behaviour may be unreliable
+#else
+	/* this fstat() shouldn't really fail */
+	if ((dcc->fp == -1 ? stat (dcc->destfile_fs, &st_a) : fstat (dcc->fp, &st_a)) == -1)
+		return FALSE;
+	if (stat (new_dcc->destfile_fs, &st_b) == -1)
+		return FALSE;
+
+	/* same inode, same device, same file! */
+	if (st_a.st_ino == st_b.st_ino &&
+		 st_a.st_dev == st_b.st_dev)
+		return TRUE;
+#endif
+
+	return FALSE;
+}
+
+static int
+is_resumable (struct DCC *dcc)
+{
+	dcc->resumable = 0;
+
+	/* Check the file size */
+	if (access (dcc->destfile_fs, W_OK) == 0)
+	{
+		struct stat st;
+
+		if (stat (dcc->destfile_fs, &st) != -1)
+		{
+			if (st.st_size < dcc->size)
+			{
+				dcc->resumable = st.st_size;
+				dcc->pos = st.st_size;
+			}
+			else
+				dcc->resume_error = 2;
+		} else
+		{
+			dcc->resume_errno = errno;
+			dcc->resume_error = 1;
+		}
+	} else
+	{
+		dcc->resume_errno = errno;
+		dcc->resume_error = 1;
+	}
+
+	/* Now verify that this DCC is not already in progress from someone else */
+
+	if (dcc->resumable)
+	{
+		GSList *list = dcc_list;
+		struct DCC *d;
+		while (list)
+		{
+			d = list->data;
+			if (d->type == TYPE_RECV && d->dccstat != STAT_ABORTED &&
+				 d->dccstat != STAT_DONE && d->dccstat != STAT_FAILED)
+			{
+				if (d != dcc && is_same_file (d, dcc))
+				{
+					dcc->resume_error = 3;	/* dccgui.c uses it */
+					dcc->resumable = 0;
+					dcc->pos = 0;
+					break;
+				}
+			}
+			list = list->next;
+		}
+	}
+
+	return dcc->resumable;
+}
+
+void
+dcc_get (struct DCC *dcc)
+{
+	switch (dcc->dccstat)
+	{
+	case STAT_QUEUED:
+		if (dcc->type != TYPE_CHATSEND)
+		{
+			if (dcc->type == TYPE_RECV && prefs.autoresume && dcc->resumable)
+			{
+				dcc_resume (dcc);
+			}
+			else
+			{
+				dcc->resumable = 0;
+				dcc->pos = 0;
+				dcc_connect (dcc);
+			}
+		}
+		break;
+	case STAT_DONE:
+	case STAT_FAILED:
+	case STAT_ABORTED:
+		dcc_close (dcc, 0, TRUE);
+		break;
+	}
+}
+
+/* for "Save As..." dialog */
+
+void
+dcc_get_with_destfile (struct DCC *dcc, char *file)
+{
+	g_free (dcc->destfile);
+	dcc->destfile = g_strdup (file);	/* utf-8 */
+
+	/* get the local filesystem encoding */
+	g_free (dcc->destfile_fs);
+	dcc->destfile_fs = xchat_filename_from_utf8 (dcc->destfile, -1, 0, 0, 0);
+
+	/* since destfile changed, must check resumability again */
+	is_resumable (dcc);
+
+	dcc_get (dcc);
+}
+
+void
+dcc_get_nick (struct session *sess, char *nick)
+{
+	struct DCC *dcc;
+	GSList *list = dcc_list;
+	while (list)
+	{
+		dcc = (struct DCC *) list->data;
+		if (!sess->server->p_cmp (nick, dcc->nick))
+		{
+			if (dcc->dccstat == STAT_QUEUED && dcc->type == TYPE_RECV)
+			{
+				dcc->resumable = 0;
+				dcc->pos = 0;
+				dcc->ack = 0;
+				dcc_connect (dcc);
+				return;
+			}
+		}
+		list = list->next;
+	}
+	if (sess)
+		EMIT_SIGNAL (XP_TE_DCCIVAL, sess, NULL, NULL, NULL, NULL, 0);
+}
+
+static struct DCC *
+new_dcc (void)
+{
+	struct DCC *dcc = malloc (sizeof (struct DCC));
+	if (!dcc)
+		return 0;
+	memset (dcc, 0, sizeof (struct DCC));
+	dcc->sok = -1;
+	dcc->fp = -1;
+	dcc_list = g_slist_prepend (dcc_list, dcc);
+	return (dcc);
+}
+
+void
+dcc_chat (struct session *sess, char *nick, int passive)
+{
+	char outbuf[512];
+	struct DCC *dcc;
+
+	dcc = find_dcc (nick, "", TYPE_CHATSEND);
+	if (dcc)
+	{
+		switch (dcc->dccstat)
+		{
+		case STAT_ACTIVE:
+		case STAT_QUEUED:
+		case STAT_CONNECTING:
+			EMIT_SIGNAL (XP_TE_DCCCHATREOFFER, sess, nick, NULL, NULL, NULL, 0);
+			return;
+		case STAT_ABORTED:
+		case STAT_FAILED:
+			dcc_close (dcc, 0, TRUE);
+		}
+	}
+	dcc = find_dcc (nick, "", TYPE_CHATRECV);
+	if (dcc)
+	{
+		switch (dcc->dccstat)
+		{
+		case STAT_QUEUED:
+			dcc_connect (dcc);
+			break;
+		case STAT_FAILED:
+		case STAT_ABORTED:
+			dcc_close (dcc, 0, TRUE);
+		}
+		return;
+	}
+	/* offer DCC CHAT */
+
+	dcc = new_dcc ();
+	if (!dcc)
+		return;
+	dcc->starttime = dcc->offertime = time (0);
+	dcc->serv = sess->server;
+	dcc->dccstat = STAT_QUEUED;
+	dcc->type = TYPE_CHATSEND;
+	dcc->nick = strdup (nick);
+	if (passive || dcc_listen_init (dcc, sess))
+	{
+		if (prefs.autoopendccchatwindow)
+		{
+			if (fe_dcc_open_chat_win (TRUE))	/* already open? add only */
+				fe_dcc_add (dcc);
+		} else
+			fe_dcc_add (dcc);
+
+		if (passive)
+		{
+			dcc->pasvid = new_id ();
+			snprintf (outbuf, sizeof (outbuf), "DCC CHAT chat 199 %d %d",
+						 dcc->port, dcc->pasvid);
+		} else
+		{
+			snprintf (outbuf, sizeof (outbuf), "DCC CHAT chat %u %d",
+						 dcc->addr, dcc->port);
+		}
+		dcc->serv->p_ctcp (dcc->serv, nick, outbuf);
+		EMIT_SIGNAL (XP_TE_DCCCHATOFFERING, sess, nick, NULL, NULL, NULL, 0);
+	} else
+	{
+		dcc_close (dcc, 0, TRUE);
+	}
+}
+
+static void
+dcc_malformed (struct session *sess, char *nick, char *data)
+{
+	EMIT_SIGNAL (XP_TE_MALFORMED, sess, nick, data, NULL, NULL, 0);
+}
+
+int
+dcc_resume (struct DCC *dcc)
+{
+	char tbuf[500];
+
+	if (dcc->dccstat == STAT_QUEUED && dcc->resumable)
+	{
+		dcc->resume_sent = 1;
+		/* filename contains spaces? Quote them! */
+		snprintf (tbuf, sizeof (tbuf) - 10, strchr (dcc->file, ' ') ?
+					  "DCC RESUME \"%s\" %d %"DCC_SFMT :
+					  "DCC RESUME %s %d %"DCC_SFMT,
+					  dcc->file, dcc->port, dcc->resumable);
+
+		if (dcc->pasvid)
+ 			sprintf (tbuf + strlen (tbuf), " %d", dcc->pasvid);
+
+		dcc->serv->p_ctcp (dcc->serv, dcc->nick, tbuf);
+		return 1;
+	}
+
+	return 0;
+}
+
+static void
+dcc_confirm_send (void *ud)
+{
+	struct DCC *dcc = (struct DCC *) ud;
+	dcc_get (dcc);
+}
+
+static void
+dcc_deny_send (void *ud)
+{
+	struct DCC *dcc = (struct DCC *) ud;
+	dcc_abort (dcc->serv->front_session, dcc);
+}
+
+static void
+dcc_confirm_chat (void *ud)
+{
+	struct DCC *dcc = (struct DCC *) ud;
+	dcc_connect (dcc);
+}
+
+static void
+dcc_deny_chat (void *ud)
+{
+	struct DCC *dcc = (struct DCC *) ud;
+	dcc_abort (dcc->serv->front_session, dcc);
+}
+
+static struct DCC *
+dcc_add_chat (session *sess, char *nick, int port, guint32 addr, int pasvid)
+{
+	struct DCC *dcc;
+
+	dcc = new_dcc ();
+	if (dcc)
+	{
+		dcc->serv = sess->server;
+		dcc->type = TYPE_CHATRECV;
+		dcc->dccstat = STAT_QUEUED;
+		dcc->addr = addr;
+		dcc->port = port;
+		dcc->pasvid = pasvid;
+		dcc->nick = strdup (nick);
+		dcc->starttime = time (0);
+
+		EMIT_SIGNAL (XP_TE_DCCCHATOFFER, sess->server->front_session, nick,
+						 NULL, NULL, NULL, 0);
+
+		if (prefs.autoopendccchatwindow)
+		{
+			if (fe_dcc_open_chat_win (TRUE))	/* already open? add only */
+				fe_dcc_add (dcc);
+		} else
+			fe_dcc_add (dcc);
+
+		if (prefs.autodccchat == 1)
+			dcc_connect (dcc);
+		else if (prefs.autodccchat == 2)
+		{
+			char buff[128];
+			snprintf (buff, sizeof (buff), "%s is offering DCC Chat. Do you want to accept?", nick);
+			fe_confirm (buff, dcc_confirm_chat, dcc_deny_chat, dcc);
+		}
+	}
+
+	return dcc;
+}
+
+static struct DCC *
+dcc_add_file (session *sess, char *file, DCC_SIZE size, int port, char *nick, guint32 addr, int pasvid)
+{
+	struct DCC *dcc;
+	char tbuf[512];
+
+	dcc = new_dcc ();
+	if (dcc)
+	{
+		dcc->file = strdup (file);
+
+		dcc->destfile = g_malloc (strlen (prefs.dccdir) + strlen (nick) +
+										  strlen (file) + 4);
+
+		strcpy (dcc->destfile, prefs.dccdir);
+		if (prefs.dccdir[strlen (prefs.dccdir) - 1] != '/')
+			strcat (dcc->destfile, "/");
+		if (prefs.dccwithnick)
+		{
+#ifdef WIN32
+			char *t = strlen (dcc->destfile) + dcc->destfile;
+			strcpy (t, nick);
+			while (*t)
+			{
+				if (*t == '\\' || *t == '|')
+					*t = '_';
+				t++;
+			}
+#else
+			strcat (dcc->destfile, nick);
+#endif
+			strcat (dcc->destfile, ".");
+		}
+		strcat (dcc->destfile, file);
+
+		/* get the local filesystem encoding */
+		dcc->destfile_fs = xchat_filename_from_utf8 (dcc->destfile, -1, 0, 0, 0);
+
+		dcc->resumable = 0;
+		dcc->pos = 0;
+		dcc->serv = sess->server;
+		dcc->type = TYPE_RECV;
+		dcc->dccstat = STAT_QUEUED;
+		dcc->addr = addr;
+		dcc->port = port;
+		dcc->pasvid = pasvid;
+		dcc->size = size;
+		dcc->nick = strdup (nick);
+		dcc->maxcps = prefs.dcc_max_get_cps;
+
+		is_resumable (dcc);
+
+		/* autodccsend is really autodccrecv.. right? */
+
+		if (prefs.autodccsend == 1)
+		{
+			dcc_get (dcc);
+		}
+		else if (prefs.autodccsend == 2)
+		{
+			snprintf (tbuf, sizeof (tbuf), _("%s is offering \"%s\". Do you want to accept?"), nick, file);
+			fe_confirm (tbuf, dcc_confirm_send, dcc_deny_send, dcc);
+		}
+		if (prefs.autoopendccrecvwindow)
+		{
+			if (fe_dcc_open_recv_win (TRUE))	/* was already open? just add*/
+				fe_dcc_add (dcc);
+		} else
+			fe_dcc_add (dcc);
+	}
+	sprintf (tbuf, "%"DCC_SFMT, size);
+	snprintf (tbuf + 24, 300, "%s:%d", net_ip (addr), port);
+	EMIT_SIGNAL (XP_TE_DCCSENDOFFER, sess->server->front_session, nick,
+					 file, tbuf, tbuf + 24, 0);
+
+	return dcc;
+}
+
+void
+handle_dcc (struct session *sess, char *nick, char *word[],
+				char *word_eol[])
+{
+	char tbuf[512];
+	struct DCC *dcc;
+	char *type = word[5];
+	int port, pasvid = 0;
+	guint32 addr;
+	DCC_SIZE size;
+	int psend = 0;
+
+	if (!strcasecmp (type, "CHAT"))
+	{
+		port = atoi (word[8]);
+		addr = strtoul (word[7], NULL, 10);
+
+		if (port == 0)
+			pasvid = atoi (word[9]);
+		else if (word[9][0] != 0)
+		{
+			pasvid = atoi (word[9]);
+			psend = 1;
+		}
+
+		if (!addr /*|| (port < 1024 && port != 0)*/
+			|| port > 0xffff || (port == 0 && pasvid == 0))
+		{
+			dcc_malformed (sess, nick, word_eol[4] + 2);
+			return;
+		}
+
+		if (psend)
+		{
+			dcc = find_dcc_from_id (pasvid, TYPE_CHATSEND);
+			if (dcc)
+			{
+				dcc->addr = addr;
+				dcc->port = port;
+				dcc_connect (dcc);
+			} else
+			{
+				dcc_malformed (sess, nick, word_eol[4] + 2);
+			}
+			return;
+		}
+
+		dcc = find_dcc (nick, "", TYPE_CHATSEND);
+		if (dcc)
+			dcc_close (dcc, 0, TRUE);
+
+		dcc = find_dcc (nick, "", TYPE_CHATRECV);
+		if (dcc)
+			dcc_close (dcc, 0, TRUE);
+
+		dcc_add_chat (sess, nick, port, addr, pasvid);
+		return;
+	}
+
+	if (!strcasecmp (type, "Resume"))
+	{
+		port = atoi (word[7]);
+
+		if (port == 0)
+		{ /* PASSIVE */
+			pasvid = atoi(word[9]);
+			dcc = find_dcc_from_id(pasvid, TYPE_SEND);
+		} else
+		{
+			dcc = find_dcc_from_port (port, TYPE_SEND);
+		}
+		if (!dcc)
+			dcc = find_dcc (nick, word[6], TYPE_SEND);
+		if (dcc)
+		{
+			size = BIG_STR_TO_INT (word[8]);
+			dcc->resumable = size;
+			if (dcc->resumable < dcc->size)
+			{
+				dcc->pos = dcc->resumable;
+				dcc->ack = dcc->resumable;
+				lseek (dcc->fp, dcc->pos, SEEK_SET);
+
+				/* Checking if dcc is passive and if filename contains spaces */
+				if (dcc->pasvid)
+					snprintf (tbuf, sizeof (tbuf), strchr (file_part (dcc->file), ' ') ?
+							"DCC ACCEPT \"%s\" %d %"DCC_SFMT" %d" :
+							"DCC ACCEPT %s %d %"DCC_SFMT" %d",
+							file_part (dcc->file), port, dcc->resumable, dcc->pasvid);
+				else
+					snprintf (tbuf, sizeof (tbuf), strchr (file_part (dcc->file), ' ') ?
+							"DCC ACCEPT \"%s\" %d %"DCC_SFMT :
+							"DCC ACCEPT %s %d %"DCC_SFMT,
+							file_part (dcc->file), port, dcc->resumable);
+
+				dcc->serv->p_ctcp (dcc->serv, dcc->nick, tbuf);
+			}
+			sprintf (tbuf, "%"DCC_SFMT, dcc->pos);
+			EMIT_SIGNAL (XP_TE_DCCRESUMEREQUEST, sess, nick,
+							 file_part (dcc->file), tbuf, NULL, 0);
+		}
+		return;
+	}
+	if (!strcasecmp (type, "Accept"))
+	{
+		port = atoi (word[7]);
+		dcc = find_dcc_from_port (port, TYPE_RECV);
+		if (dcc && dcc->dccstat == STAT_QUEUED)
+		{
+			dcc_connect (dcc);
+		}
+		return;
+	}
+	if (!strcasecmp (type, "SEND"))
+	{
+		char *file = file_part (word[6]);
+
+		port = atoi (word[8]);
+		addr = strtoul (word[7], NULL, 10);
+		size = BIG_STR_TO_INT (word[9]);
+
+		if (port == 0) /* Passive dcc requested */
+			pasvid = atoi (word[10]);
+		else if (word[10][0] != 0)
+		{
+			/* Requesting passive dcc.
+			 * Destination user of an active dcc is giving his
+			 * TRUE address/port/pasvid data.
+			 * This information will be used later to
+			 * establish the connection to the user.
+			 * We can recognize this type of dcc using word[10]
+			 * because this field is always null (no pasvid)
+			 * in normal dcc sends.
+			 */
+			pasvid = atoi (word[10]);
+			psend = 1;
+		}
+
+
+		if (!addr || !size /*|| (port < 1024 && port != 0)*/
+			|| port > 0xffff || (port == 0 && pasvid == 0))
+		{
+			dcc_malformed (sess, nick, word_eol[4] + 2);
+			return;
+		}
+
+		if (psend)
+		{
+			/* Third Step of Passive send.
+			 * Connecting to the destination and finally
+			 * sending file.
+			 */
+			dcc = find_dcc_from_id (pasvid, TYPE_SEND);
+			if (dcc)
+			{
+				dcc->addr = addr;
+				dcc->port = port;
+				dcc_connect (dcc);
+			} else
+			{
+				dcc_malformed (sess, nick, word_eol[4] + 2);
+			}
+			return;
+		}
+
+		dcc_add_file (sess, file, size, port, nick, addr, pasvid);
+
+	} else
+	{
+		EMIT_SIGNAL (XP_TE_DCCGENERICOFFER, sess->server->front_session,
+						 word_eol[4] + 2, nick, NULL, NULL, 0);
+	}
+}
+
+void
+dcc_show_list (struct session *sess)
+{
+	int i = 0;
+	struct DCC *dcc;
+	GSList *list = dcc_list;
+
+	EMIT_SIGNAL (XP_TE_DCCHEAD, sess, NULL, NULL, NULL, NULL, 0);
+	while (list)
+	{
+		dcc = (struct DCC *) list->data;
+		i++;
+		PrintTextf (sess, " %s  %-10.10s %-7.7s %-7"DCC_SFMT" %-7"DCC_SFMT" %s\n",
+					 dcctypes[dcc->type], dcc->nick,
+					 _(dccstat[dcc->dccstat].name), dcc->size, dcc->pos,
+					 file_part (dcc->file));
+		list = list->next;
+	}
+	if (!i)
+		PrintText (sess, _("No active DCCs\n"));
+}
diff --git a/src/common/dcc.h b/src/common/dcc.h
new file mode 100644
index 00000000..da4ce979
--- /dev/null
+++ b/src/common/dcc.h
@@ -0,0 +1,117 @@
+/* dcc.h */
+
+#include <time.h>						/* for time_t */
+
+#ifndef XCHAT_DCC_H
+#define XCHAT_DCC_H
+
+#define STAT_QUEUED 0
+#define STAT_ACTIVE 1
+#define STAT_FAILED 2
+#define STAT_DONE 3
+#define STAT_CONNECTING 4
+#define STAT_ABORTED 5
+
+#define TYPE_SEND 0
+#define TYPE_RECV 1
+#define TYPE_CHATRECV 2
+#define TYPE_CHATSEND 3
+
+#define CPS_AVG_WINDOW 10
+
+/* can we do 64-bit dcc? */
+#if defined(G_GINT64_FORMAT) && defined(HAVE_STRTOULL)
+#define USE_DCC64
+/* we really get only 63 bits, since st_size is signed */
+#define DCC_SIZE gint64
+#define DCC_SFMT G_GINT64_FORMAT
+#else
+#define DCC_SIZE unsigned int
+#define DCC_SFMT "u"
+#endif
+
+struct DCC
+{
+	struct server *serv;
+	struct dcc_chat *dccchat;
+	struct proxy_state *proxy;
+	guint32 addr;					/* the 32bit IP number, host byte order */
+	int fp;							/* file pointer */
+	int sok;
+	int iotag;						/* reading io tag */
+	int wiotag;						/* writing/sending io tag */
+	int port;
+	int pasvid;						/* mIRC's passive DCC id */
+	int cps;
+	int resume_error;
+	int resume_errno;
+
+	GTimeVal lastcpstv, firstcpstv;
+	DCC_SIZE lastcpspos;
+	int maxcps;
+
+	unsigned char ack_buf[4];	/* buffer for reading 4-byte ack */
+	int ack_pos;
+
+	DCC_SIZE size;
+	DCC_SIZE resumable;
+	DCC_SIZE ack;
+	DCC_SIZE pos;
+	time_t starttime;
+	time_t offertime;
+	time_t lasttime;
+	char *file;					/* utf8 */
+	char *destfile;			/* utf8 */
+	char *destfile_fs;		/* local filesystem encoding */
+	char *nick;
+	unsigned char type;		  /* 0 = SEND  1 = RECV  2 = CHAT */
+	unsigned char dccstat;	  /* 0 = QUEUED  1 = ACTIVE  2 = FAILED  3 = DONE */
+	unsigned int resume_sent:1;	/* resume request sent */
+	unsigned int fastsend:1;
+	unsigned int ackoffset:1;	/* is reciever sending acks as an offset from */
+										/* the resume point? */
+	unsigned int throttled:2;	/* 0x1 = per send/get throttle
+											0x2 = global throttle */
+};
+
+#define MAX_PROXY_BUFFER 1024
+struct proxy_state
+{
+	int phase;
+	unsigned char buffer[MAX_PROXY_BUFFER];
+	int buffersize;
+	int bufferused;
+};
+
+struct dcc_chat
+{
+	char linebuf[2048];
+	int pos;
+};
+
+struct dccstat_info
+{
+	char *name;						  /* Display name */
+	int color;						  /* Display color (index into colors[] ) */
+};
+
+extern struct dccstat_info dccstat[];
+
+gboolean is_dcc (struct DCC *dcc);
+void dcc_abort (session *sess, struct DCC *dcc);
+void dcc_get (struct DCC *dcc);
+int dcc_resume (struct DCC *dcc);
+void dcc_check_timeouts (void);
+void dcc_change_nick (server *serv, char *oldnick, char *newnick);
+void dcc_notify_kill (struct server *serv);
+struct DCC *dcc_write_chat (char *nick, char *text);
+void dcc_send (struct session *sess, char *to, char *file, int maxcps, int passive);
+struct DCC *find_dcc (char *nick, char *file, int type);
+void dcc_get_nick (struct session *sess, char *nick);
+void dcc_chat (session *sess, char *nick, int passive);
+void handle_dcc (session *sess, char *nick, char *word[], char *word_eol[]);
+void dcc_show_list (session *sess);
+guint32 dcc_get_my_address (void);
+void dcc_get_with_destfile (struct DCC *dcc, char *utf8file);
+
+#endif
diff --git a/src/common/fe.h b/src/common/fe.h
new file mode 100644
index 00000000..16526581
--- /dev/null
+++ b/src/common/fe.h
@@ -0,0 +1,162 @@
+#include "userlist.h"
+#include "dcc.h"
+
+#ifndef XCHAT_FE_H
+#define XCHAT_FE_H
+
+/* for storage of /menu entries */
+typedef struct
+{
+	gint32 pos;	/* position */
+	gint16 modifier;	/* keybinding */
+	gint16 root_offset;	/* bytes to offset ->path */
+
+	char is_main;	/* is part of the Main menu? (not a popup) */
+	char state;	/* state of toggle items */
+	char markup;	/* use pango markup? */
+	char enable;	/* enabled? sensitivity */
+
+	int key;
+	char *path;
+	char *label;
+	char *cmd;
+	char *ucmd;	/* unselect command (toggles) */
+	char *group;	/* for radio items or NULL */
+	char *icon;	/* filename */
+} menu_entry;
+
+int fe_args (int argc, char *argv[]);
+void fe_init (void);
+void fe_main (void);
+void fe_cleanup (void);
+void fe_exit (void);
+int fe_timeout_add (int interval, void *callback, void *userdata);
+void fe_timeout_remove (int tag);
+void fe_new_window (struct session *sess, int focus);
+void fe_new_server (struct server *serv);
+void fe_add_rawlog (struct server *serv, char *text, int len, int outbound);
+#define FE_MSG_WAIT 1
+#define FE_MSG_INFO 2
+#define FE_MSG_WARN 4
+#define FE_MSG_ERROR 8
+#define FE_MSG_MARKUP 16
+void fe_message (char *msg, int flags);
+#define FIA_READ 1
+#define FIA_WRITE 2
+#define FIA_EX 4
+#define FIA_FD 8
+int fe_input_add (int sok, int flags, void *func, void *data);
+void fe_input_remove (int tag);
+void fe_idle_add (void *func, void *data);
+void fe_set_topic (struct session *sess, char *topic, char *stripped_topic);
+void fe_set_hilight (struct session *sess);
+void fe_set_tab_color (struct session *sess, int col);
+void fe_flash_window (struct session *sess);
+void fe_update_mode_buttons (struct session *sess, char mode, char sign);
+void fe_update_channel_key (struct session *sess);
+void fe_update_channel_limit (struct session *sess);
+int fe_is_chanwindow (struct server *serv);
+void fe_add_chan_list (struct server *serv, char *chan, char *users,
+							  char *topic);
+void fe_chan_list_end (struct server *serv);
+int fe_is_banwindow (struct session *sess);
+void fe_add_ban_list (struct session *sess, char *mask, char *who, char *when, int is_exemption);
+void fe_ban_list_end (struct session *sess, int is_exemption);
+void fe_notify_update (char *name);
+void fe_notify_ask (char *name, char *networks);
+void fe_text_clear (struct session *sess, int lines);
+void fe_close_window (struct session *sess);
+void fe_progressbar_start (struct session *sess);
+void fe_progressbar_end (struct server *serv);
+void fe_print_text (struct session *sess, char *text, time_t stamp);
+void fe_userlist_insert (struct session *sess, struct User *newuser, int row, int sel);
+int fe_userlist_remove (struct session *sess, struct User *user);
+void fe_userlist_rehash (struct session *sess, struct User *user);
+void fe_userlist_update (struct session *sess, struct User *user);
+void fe_userlist_move (struct session *sess, struct User *user, int new_row);
+void fe_userlist_numbers (struct session *sess);
+void fe_userlist_clear (struct session *sess);
+void fe_userlist_set_selected (struct session *sess);
+void fe_uselect (session *sess, char *word[], int do_clear, int scroll_to);
+void fe_dcc_add (struct DCC *dcc);
+void fe_dcc_update (struct DCC *dcc);
+void fe_dcc_remove (struct DCC *dcc);
+int fe_dcc_open_recv_win (int passive);
+int fe_dcc_open_send_win (int passive);
+int fe_dcc_open_chat_win (int passive);
+void fe_clear_channel (struct session *sess);
+void fe_session_callback (struct session *sess);
+void fe_server_callback (struct server *serv);
+void fe_url_add (const char *text);
+void fe_pluginlist_update (void);
+void fe_buttons_update (struct session *sess);
+void fe_dlgbuttons_update (struct session *sess);
+void fe_dcc_send_filereq (struct session *sess, char *nick, int maxcps, int passive);
+void fe_set_channel (struct session *sess);
+void fe_set_title (struct session *sess);
+void fe_set_nonchannel (struct session *sess, int state);
+void fe_set_nick (struct server *serv, char *newnick);
+void fe_ignore_update (int level);
+void fe_beep (void);
+void fe_lastlog (session *sess, session *lastlog_sess, char *sstr, gboolean regexp);
+void fe_set_lag (server *serv, int lag);
+void fe_set_throttle (server *serv);
+void fe_set_away (server *serv);
+void fe_serverlist_open (session *sess);
+void fe_get_str (char *prompt, char *def, void *callback, void *ud);
+void fe_get_int (char *prompt, int def, void *callback, void *ud);
+#define FRF_WRITE 1	/* save file */
+#define FRF_MULTIPLE 2	/* multi-select */
+#define FRF_ADDFOLDER 4	/* add ~/.xchat2 to favourites */
+#define FRF_CHOOSEFOLDER 8	/* choosing a folder only */
+#define FRF_FILTERISINITIAL 16	/* unused */
+#define FRF_NOASKOVERWRITE 32	/* don't ask to overwrite existing files */
+void fe_get_file (const char *title, char *initial,
+				 void (*callback) (void *userdata, char *file), void *userdata,
+				 int flags);
+typedef enum {
+	FE_GUI_HIDE,
+	FE_GUI_SHOW,
+	FE_GUI_FOCUS,
+	FE_GUI_FLASH,
+	FE_GUI_COLOR,
+	FE_GUI_ICONIFY,
+	FE_GUI_MENU,
+	FE_GUI_ATTACH,
+	FE_GUI_APPLY,
+} fe_gui_action;
+void fe_ctrl_gui (session *sess, fe_gui_action action, int arg);
+int fe_gui_info (session *sess, int info_type);
+void *fe_gui_info_ptr (session *sess, int info_type);
+void fe_confirm (const char *message, void (*yesproc)(void *), void (*noproc)(void *), void *ud);
+char *fe_get_inputbox_contents (struct session *sess);
+int fe_get_inputbox_cursor (struct session *sess);
+void fe_set_inputbox_contents (struct session *sess, char *text);
+void fe_set_inputbox_cursor (struct session *sess, int delta, int pos);
+void fe_open_url (const char *url);
+void fe_menu_del (menu_entry *);
+char *fe_menu_add (menu_entry *);
+void fe_menu_update (menu_entry *);
+#define FE_SE_CONNECT 0
+#define FE_SE_LOGGEDIN 1
+#define FE_SE_DISCONNECT 2
+#define FE_SE_RECONDELAY 3
+#define FE_SE_CONNECTING 4
+void fe_server_event (server *serv, int type, int arg);
+/* pass NULL filename2 for default xchat icon */
+void fe_tray_set_flash (const char *filename1, const char *filename2, int timeout);
+/* pass NULL filename for default xchat icon */
+void fe_tray_set_file (const char *filename);
+typedef enum
+{
+	FE_ICON_NORMAL = 0,
+	FE_ICON_MESSAGE = 2,
+	FE_ICON_HIGHLIGHT = 5,
+	FE_ICON_PRIVMSG = 8,
+	FE_ICON_FILEOFFER = 11
+} feicon;
+void fe_tray_set_icon (feicon icon);
+void fe_tray_set_tooltip (const char *text);
+void fe_tray_set_balloon (const char *title, const char *text);
+
+#endif
diff --git a/src/common/history.c b/src/common/history.c
new file mode 100644
index 00000000..1cd6b508
--- /dev/null
+++ b/src/common/history.c
@@ -0,0 +1,121 @@
+/* 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 <string.h>
+#include <stdlib.h>
+#include "history.h"
+
+void
+history_add (struct history *his, char *text)
+{
+	if (his->lines[his->realpos])
+		free (his->lines[his->realpos]);
+	his->lines[his->realpos] = strdup (text);
+	his->realpos++;
+	if (his->realpos == HISTORY_SIZE)
+		his->realpos = 0;
+	his->pos = his->realpos;
+}
+
+void
+history_free (struct history *his)
+{
+	int i;
+	for (i = 0; i < HISTORY_SIZE; i++)
+	{
+		if (his->lines[i])
+		{
+			free (his->lines[i]);
+			his->lines[i] = 0;
+		}
+	}
+}
+
+char *
+history_down (struct history *his)
+{
+	int next;
+
+	if (his->pos == his->realpos)	/* allow down only after up */
+		return 0;
+	if (his->realpos == 0)
+	{
+		if (his->pos == HISTORY_SIZE - 1)
+		{
+			his->pos = 0;
+			return "";
+		}
+	} else
+	{
+		if (his->pos == his->realpos - 1)
+		{
+			his->pos++;
+			return "";
+		}
+	}
+
+	next = 0;
+	if (his->pos < HISTORY_SIZE - 1)
+		next = his->pos + 1;
+
+	if (his->lines[next])
+	{
+		his->pos = next;
+		return his->lines[his->pos];
+	}
+
+	return 0;
+}
+
+char *
+history_up (struct history *his, char *current_text)
+{
+	int next;
+
+	if (his->realpos == HISTORY_SIZE - 1)
+	{
+		if (his->pos == 0)
+			return 0;
+	} else
+	{
+		if (his->pos == his->realpos + 1)
+			return 0;
+	}
+
+	next = HISTORY_SIZE - 1;
+	if (his->pos != 0)
+		next = his->pos - 1;
+
+	if (his->lines[next])
+	{
+		if
+		(
+			current_text[0] && strcmp(current_text, his->lines[next]) &&
+			(!his->lines[his->pos] || strcmp(current_text, his->lines[his->pos])) &&
+			(!his->lines[his->realpos] || strcmp(current_text, his->lines[his->pos]))
+		)
+		{
+			history_add (his, current_text);
+		}
+		
+		his->pos = next;
+		return his->lines[his->pos];
+	}
+
+	return 0;
+}
diff --git a/src/common/history.h b/src/common/history.h
new file mode 100644
index 00000000..5267f1fc
--- /dev/null
+++ b/src/common/history.h
@@ -0,0 +1,18 @@
+#ifndef XCHAT_HISTORY_H
+#define XCHAT_HISTORY_H
+
+#define HISTORY_SIZE 100
+
+struct history
+{
+	char *lines[HISTORY_SIZE];
+	int pos;
+	int realpos;
+};
+
+void history_add (struct history *his, char *text);
+void history_free (struct history *his);
+char *history_up (struct history *his, char *current_text);
+char *history_down (struct history *his);
+
+#endif
diff --git a/src/common/identd.c b/src/common/identd.c
new file mode 100644
index 00000000..919282ea
--- /dev/null
+++ b/src/common/identd.c
@@ -0,0 +1,89 @@
+/* simple identd server for xchat under win32 */
+
+
+static int identd_is_running = FALSE;
+
+
+static int
+identd (char *username)
+{
+	int sok, read_sok, len;
+	char *p;
+	char buf[256];
+	char outbuf[256];
+	struct sockaddr_in addr;
+
+	sok = socket (AF_INET, SOCK_STREAM, 0);
+	if (sok == INVALID_SOCKET)
+	{
+		free (username);
+		return 0;
+	}
+
+	len = 1;
+	setsockopt (sok, SOL_SOCKET, SO_REUSEADDR, (char *) &len, sizeof (len));
+
+	memset (&addr, 0, sizeof (addr));
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons (113);
+
+	if (bind (sok, (struct sockaddr *) &addr, sizeof (addr)) == SOCKET_ERROR)
+	{
+		closesocket (sok);
+		free (username);
+		return 0;
+	}
+
+	if (listen (sok, 1) == SOCKET_ERROR)
+	{
+		closesocket (sok);
+		free (username);
+		return 0;
+	}
+
+	len = sizeof (addr);
+	read_sok = accept (sok, (struct sockaddr *) &addr, &len);
+	closesocket (sok);
+	if (read_sok == INVALID_SOCKET)
+	{
+		free (username);
+		return 0;
+	}
+
+	identd_is_running = FALSE;
+
+	snprintf (outbuf, sizeof (outbuf), "%%\tServicing ident request from %s\n",
+				 inet_ntoa (addr.sin_addr));
+	PrintText (current_sess, outbuf);
+
+	recv (read_sok, buf, sizeof (buf) - 1, 0);
+	buf[sizeof (buf) - 1] = 0;	  /* ensure null termination */
+
+	p = strchr (buf, ',');
+	if (p)
+	{
+		snprintf (outbuf, sizeof (outbuf) - 1, "%d, %d : USERID : UNIX : %s\r\n",
+					 atoi (buf), atoi (p + 1), username);
+		outbuf[sizeof (outbuf) - 1] = 0;	/* ensure null termination */
+		send (read_sok, outbuf, strlen (outbuf), 0);
+	}
+
+	sleep (1);
+	closesocket (read_sok);
+	free (username);
+
+	return 0;
+}
+
+static void
+identd_start (char *username)
+{
+	DWORD tid;
+
+	if (identd_is_running == FALSE)
+	{
+		identd_is_running = TRUE;
+		CloseHandle (CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE) identd,
+						 strdup (username), 0, &tid));
+	}
+}
diff --git a/src/common/ignore.c b/src/common/ignore.c
new file mode 100644
index 00000000..c3544f30
--- /dev/null
+++ b/src/common/ignore.c
@@ -0,0 +1,424 @@
+/* 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "xchat.h"
+#include "ignore.h"
+#include "cfgfiles.h"
+#include "fe.h"
+#include "text.h"
+#include "util.h"
+#include "xchatc.h"
+
+
+int ignored_ctcp = 0;			  /* keep a count of all we ignore */
+int ignored_priv = 0;
+int ignored_chan = 0;
+int ignored_noti = 0;
+int ignored_invi = 0;
+static int ignored_total = 0;
+
+/* ignore_exists ():
+ * returns: struct ig, if this mask is in the ignore list already
+ *          NULL, otherwise
+ */
+struct ignore *
+ignore_exists (char *mask)
+{
+	struct ignore *ig = 0;
+	GSList *list;
+
+	list = ignore_list;
+	while (list)
+	{
+		ig = (struct ignore *) list->data;
+		if (!rfc_casecmp (ig->mask, mask))
+			return ig;
+		list = list->next;
+	}
+	return NULL;
+
+}
+
+/* ignore_add(...)
+
+ * returns:
+ *            0 fail
+ *            1 success
+ *            2 success (old ignore has been changed)
+ */
+
+int
+ignore_add (char *mask, int type)
+{
+	struct ignore *ig = 0;
+	int change_only = FALSE;
+
+	/* first check if it's already ignored */
+	ig = ignore_exists (mask);
+	if (ig)
+		change_only = TRUE;
+
+	if (!change_only)
+		ig = malloc (sizeof (struct ignore));
+
+	if (!ig)
+		return 0;
+
+	ig->mask = strdup (mask);
+	ig->type = type;
+
+	if (!change_only)
+		ignore_list = g_slist_prepend (ignore_list, ig);
+	fe_ignore_update (1);
+
+	if (change_only)
+		return 2;
+
+	return 1;
+}
+
+void
+ignore_showlist (session *sess)
+{
+	struct ignore *ig;
+	GSList *list = ignore_list;
+	char tbuf[256];
+	int i = 0;
+
+	EMIT_SIGNAL (XP_TE_IGNOREHEADER, sess, 0, 0, 0, 0, 0);
+
+	while (list)
+	{
+		ig = list->data;
+		i++;
+
+		snprintf (tbuf, sizeof (tbuf), " %-25s ", ig->mask);
+		if (ig->type & IG_PRIV)
+			strcat (tbuf, _("YES  "));
+		else
+			strcat (tbuf, _("NO   "));
+		if (ig->type & IG_NOTI)
+			strcat (tbuf, _("YES  "));
+		else
+			strcat (tbuf, _("NO   "));
+		if (ig->type & IG_CHAN)
+			strcat (tbuf, _("YES  "));
+		else
+			strcat (tbuf, _("NO   "));
+		if (ig->type & IG_CTCP)
+			strcat (tbuf, _("YES  "));
+		else
+			strcat (tbuf, _("NO   "));
+		if (ig->type & IG_DCC)
+			strcat (tbuf, _("YES  "));
+		else
+			strcat (tbuf, _("NO   "));
+		if (ig->type & IG_INVI)
+			strcat (tbuf, _("YES  "));
+		else
+			strcat (tbuf, _("NO   "));
+		if (ig->type & IG_UNIG)
+			strcat (tbuf, _("YES  "));
+		else
+			strcat (tbuf, _("NO   "));
+		strcat (tbuf, "\n");
+		PrintText (sess, tbuf);
+		/*EMIT_SIGNAL (XP_TE_IGNORELIST, sess, ig->mask, 0, 0, 0, 0); */
+		/* use this later, when TE's support 7 args */
+		list = list->next;
+	}
+
+	if (!i)
+		EMIT_SIGNAL (XP_TE_IGNOREEMPTY, sess, 0, 0, 0, 0, 0);
+
+	EMIT_SIGNAL (XP_TE_IGNOREFOOTER, sess, 0, 0, 0, 0, 0);
+}
+
+/* ignore_del()
+
+ * one of the args must be NULL, use mask OR *ig, not both
+ *
+ */
+
+int
+ignore_del (char *mask, struct ignore *ig)
+{
+	if (!ig)
+	{
+		GSList *list = ignore_list;
+
+		while (list)
+		{
+			ig = (struct ignore *) list->data;
+			if (!rfc_casecmp (ig->mask, mask))
+				break;
+			list = list->next;
+			ig = 0;
+		}
+	}
+	if (ig)
+	{
+		ignore_list = g_slist_remove (ignore_list, ig);
+		free (ig->mask);
+		free (ig);
+		fe_ignore_update (1);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+/* check if a msg should be ignored by browsing our ignore list */
+
+int
+ignore_check (char *host, int type)
+{
+	struct ignore *ig;
+	GSList *list = ignore_list;
+
+	/* check if there's an UNIGNORE first, they take precendance. */
+	while (list)
+	{
+		ig = (struct ignore *) list->data;
+		if (ig->type & IG_UNIG)
+		{
+			if (ig->type & type)
+			{
+				if (match (ig->mask, host))
+					return FALSE;
+			}
+		}
+		list = list->next;
+	}
+
+	list = ignore_list;
+	while (list)
+	{
+		ig = (struct ignore *) list->data;
+
+		if (ig->type & type)
+		{
+			if (match (ig->mask, host))
+			{
+				ignored_total++;
+				if (type & IG_PRIV)
+					ignored_priv++;
+				if (type & IG_NOTI)
+					ignored_noti++;
+				if (type & IG_CHAN)
+					ignored_chan++;
+				if (type & IG_CTCP)
+					ignored_ctcp++;
+				if (type & IG_INVI)
+					ignored_invi++;
+				fe_ignore_update (2);
+				return TRUE;
+			}
+		}
+		list = list->next;
+	}
+
+	return FALSE;
+}
+
+static char *
+ignore_read_next_entry (char *my_cfg, struct ignore *ignore)
+{
+	char tbuf[1024];
+
+	/* Casting to char * done below just to satisfy compiler */
+
+	if (my_cfg)
+	{
+		my_cfg = cfg_get_str (my_cfg, "mask", tbuf, sizeof (tbuf));
+		if (!my_cfg)
+			return NULL;
+		ignore->mask = strdup (tbuf);
+	}
+	if (my_cfg)
+	{
+		my_cfg = cfg_get_str (my_cfg, "type", tbuf, sizeof (tbuf));
+		ignore->type = atoi (tbuf);
+	}
+	return my_cfg;
+}
+
+void
+ignore_load ()
+{
+	struct ignore *ignore;
+	struct stat st;
+	char *cfg, *my_cfg;
+	int fh, i;
+
+	fh = xchat_open_file ("ignore.conf", O_RDONLY, 0, 0);
+	if (fh != -1)
+	{
+		fstat (fh, &st);
+		if (st.st_size)
+		{
+			cfg = malloc (st.st_size + 1);
+			cfg[0] = '\0';
+			i = read (fh, cfg, st.st_size);
+			if (i >= 0)
+				cfg[i] = '\0';
+			my_cfg = cfg;
+			while (my_cfg)
+			{
+				ignore = malloc (sizeof (struct ignore));
+				memset (ignore, 0, sizeof (struct ignore));
+				if ((my_cfg = ignore_read_next_entry (my_cfg, ignore)))
+					ignore_list = g_slist_prepend (ignore_list, ignore);
+				else
+					free (ignore);
+			}
+			free (cfg);
+		}
+		close (fh);
+	}
+}
+
+void
+ignore_save ()
+{
+	char buf[1024];
+	int fh;
+	GSList *temp = ignore_list;
+	struct ignore *ig;
+
+	fh = xchat_open_file ("ignore.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE);
+	if (fh != -1)
+	{
+		while (temp)
+		{
+			ig = (struct ignore *) temp->data;
+			if (!(ig->type & IG_NOSAVE))
+			{
+				snprintf (buf, sizeof (buf), "mask = %s\ntype = %d\n\n",
+							 ig->mask, ig->type);
+				write (fh, buf, strlen (buf));
+			}
+			temp = temp->next;
+		}
+		close (fh);
+	}
+
+}
+
+static gboolean
+flood_autodialog_timeout (gpointer data)
+{
+	prefs.autodialog = 1;
+	return FALSE;
+}
+
+int
+flood_check (char *nick, char *ip, server *serv, session *sess, int what)	/*0=ctcp  1=priv */
+{
+	/*
+	   serv
+	   int ctcp_counter; 
+	   time_t ctcp_last_time;
+	   prefs
+	   unsigned int ctcp_number_limit;
+	   unsigned int ctcp_time_limit;
+	 */
+	char buf[512];
+	char real_ip[132];
+	int i;
+	time_t current_time;
+	current_time = time (NULL);
+
+	if (what == 0)
+	{
+		if (serv->ctcp_last_time == 0)	/*first ctcp in this server */
+		{
+			serv->ctcp_last_time = time (NULL);
+			serv->ctcp_counter++;
+		} else
+		{
+			if (difftime (current_time, serv->ctcp_last_time) < prefs.ctcp_time_limit)	/*if we got the ctcp in the seconds limit */
+			{
+				serv->ctcp_counter++;
+				if (serv->ctcp_counter == prefs.ctcp_number_limit)	/*if we reached the maximun numbers of ctcp in the seconds limits */
+				{
+					serv->ctcp_last_time = current_time;	/*we got the flood, restore all the vars for next one */
+					serv->ctcp_counter = 0;
+					for (i = 0; i < 128; i++)
+						if (ip[i] == '@')
+							break;
+					snprintf (real_ip, sizeof (real_ip), "*!*%s", &ip[i]);
+					/*ignore_add (char *mask, int priv, int noti, int chan,
+					   int ctcp, int invi, int unignore, int no_save) */
+
+					snprintf (buf, sizeof (buf),
+								 _("You are being CTCP flooded from %s, ignoring %s\n"),
+								 nick, real_ip);
+					PrintText (sess, buf);
+
+					/*FIXME: only ignore ctcp or all?, its ignoring ctcps for now */
+					ignore_add (real_ip, IG_CTCP);
+					return 0;
+				}
+			}
+		}
+	} else
+	{
+		if (serv->msg_last_time == 0)
+		{
+			serv->msg_last_time = time (NULL);
+			serv->ctcp_counter++;
+		} else
+		{
+			if (difftime (current_time, serv->msg_last_time) <
+				 prefs.msg_time_limit)
+			{
+				serv->msg_counter++;
+				if (serv->msg_counter == prefs.msg_number_limit)	/*if we reached the maximun numbers of ctcp in the seconds limits */
+				{
+					snprintf (buf, sizeof (buf),
+					 _("You are being MSG flooded from %s, setting gui_auto_open_dialog OFF.\n"),
+								 ip);
+					PrintText (sess, buf);
+					serv->msg_last_time = current_time;	/*we got the flood, restore all the vars for next one */
+					serv->msg_counter = 0;
+					/*ignore_add (char *mask, int priv, int noti, int chan,
+					   int ctcp, int invi, int unignore, int no_save) */
+
+					if (prefs.autodialog)
+					{
+						/*FIXME: only ignore ctcp or all?, its ignoring ctcps for now */
+						prefs.autodialog = 0;
+						/* turn it back on in 30 secs */
+						fe_timeout_add (30000, flood_autodialog_timeout, NULL);
+					}
+					return 0;
+				}
+			}
+		}
+	}
+	return 1;
+}
+
diff --git a/src/common/ignore.h b/src/common/ignore.h
new file mode 100644
index 00000000..3a971a86
--- /dev/null
+++ b/src/common/ignore.h
@@ -0,0 +1,38 @@
+#ifndef XCHAT_IGNORE_H
+#define XCHAT_IGNORE_H
+
+extern GSList *ignore_list;
+
+extern int ignored_ctcp;
+extern int ignored_priv;
+extern int ignored_chan;
+extern int ignored_noti;
+extern int ignored_invi;
+
+#define IG_PRIV	1
+#define IG_NOTI	2
+#define IG_CHAN	4
+#define IG_CTCP	8
+#define IG_INVI	16
+#define IG_UNIG	32
+#define IG_NOSAVE	64
+#define IG_DCC		128
+
+struct ignore
+{
+	char *mask;
+	unsigned int type;	/* one of more of IG_* ORed together */
+};
+
+struct ignore *ignore_exists (char *mask);
+int ignore_add (char *mask, int type);
+void ignore_showlist (session *sess);
+int ignore_del (char *mask, struct ignore *ig);
+int ignore_check (char *mask, int type);
+void ignore_load (void);
+void ignore_save (void);
+void ignore_gui_open (void);
+void ignore_gui_update (int level);
+int flood_check (char *nick, char *ip, server *serv, session *sess, int what);
+
+#endif
diff --git a/src/common/inbound.c b/src/common/inbound.c
new file mode 100644
index 00000000..ec7dd9d0
--- /dev/null
+++ b/src/common/inbound.c
@@ -0,0 +1,1336 @@
+/* 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 <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <time.h>
+
+#define WANTARPA
+#define WANTDNS
+#include "inet.h"
+
+#include "xchat.h"
+#include "util.h"
+#include "ignore.h"
+#include "fe.h"
+#include "modes.h"
+#include "notify.h"
+#include "outbound.h"
+#include "inbound.h"
+#include "server.h"
+#include "servlist.h"
+#include "text.h"
+#include "ctcp.h"
+#include "plugin.h"
+#include "xchatc.h"
+
+
+void
+clear_channel (session *sess)
+{
+	if (sess->channel[0])
+		strcpy (sess->waitchannel, sess->channel);
+	sess->channel[0] = 0;
+	sess->doing_who = FALSE;
+	sess->done_away_check = FALSE;
+
+	log_close (sess);
+
+	if (sess->current_modes)
+	{
+		free (sess->current_modes);
+		sess->current_modes = NULL;
+	}
+
+	if (sess->mode_timeout_tag)
+	{
+		fe_timeout_remove (sess->mode_timeout_tag);
+		sess->mode_timeout_tag = 0;
+	}
+
+	fe_clear_channel (sess);
+	userlist_clear (sess);
+	fe_set_nonchannel (sess, FALSE);
+	fe_set_title (sess);
+}
+
+void
+set_topic (session *sess, char *topic, char *stripped_topic)
+{
+	if (sess->topic)
+		free (sess->topic);
+	sess->topic = strdup (stripped_topic);
+	fe_set_topic (sess, topic, stripped_topic);
+}
+
+static session *
+find_session_from_nick (char *nick, server *serv)
+{
+	session *sess;
+	GSList *list = sess_list;
+
+	sess = find_dialog (serv, nick);
+	if (sess)
+		return sess;
+
+	if (serv->front_session)
+	{
+		if (userlist_find (serv->front_session, nick))
+			return serv->front_session;
+	}
+
+	if (current_sess && current_sess->server == serv)
+	{
+		if (userlist_find (current_sess, nick))
+			return current_sess;
+	}
+
+	while (list)
+	{
+		sess = list->data;
+		if (sess->server == serv)
+		{
+			if (userlist_find (sess, nick))
+				return sess;
+		}
+		list = list->next;
+	}
+	return 0;
+}
+
+static session *
+inbound_open_dialog (server *serv, char *from)
+{
+	session *sess;
+
+	sess = new_ircwindow (serv, from, SESS_DIALOG, 0);
+	/* for playing sounds */
+	EMIT_SIGNAL (XP_TE_OPENDIALOG, sess, NULL, NULL, NULL, NULL, 0);
+
+	return sess;
+}
+
+static void
+inbound_make_idtext (server *serv, char *idtext, int max, int id)
+{
+	idtext[0] = 0;
+	if (serv->have_idmsg)
+	{
+		if (id)
+		{
+			safe_strcpy (idtext, prefs.irc_id_ytext, max);
+		} else
+		{
+			safe_strcpy (idtext, prefs.irc_id_ntext, max);
+		}
+		/* convert codes like %C,%U to the proper ones */
+		check_special_chars (idtext, TRUE);
+	}
+}
+
+void
+inbound_privmsg (server *serv, char *from, char *ip, char *text, int id)
+{
+	session *sess;
+	char idtext[64];
+
+	sess = find_dialog (serv, from);
+
+	if (sess || prefs.autodialog)
+	{
+		/*0=ctcp  1=priv will set autodialog=0 here is flud detected */
+		if (!sess)
+		{
+			if (flood_check (from, ip, serv, current_sess, 1))
+				/* Create a dialog session */
+				sess = inbound_open_dialog (serv, from);
+			else
+				sess = serv->server_session;
+			if (!sess)
+				return; /* ?? */
+		}
+
+		if (ip && ip[0])
+		{
+			if (prefs.logging && sess->logfd != -1 &&
+				(!sess->topic || strcmp(sess->topic, ip)))
+			{
+				char tbuf[1024];
+				snprintf (tbuf, sizeof (tbuf), "[%s has address %s]\n", from, ip);
+				write (sess->logfd, tbuf, strlen (tbuf));
+			}
+			set_topic (sess, ip, ip);
+		}
+		inbound_chanmsg (serv, NULL, NULL, from, text, FALSE, id);
+		return;
+	}
+
+	inbound_make_idtext (serv, idtext, sizeof (idtext), id);
+
+	sess = find_session_from_nick (from, serv);
+	if (!sess)
+	{
+		sess = serv->front_session;
+		EMIT_SIGNAL (XP_TE_PRIVMSG, sess, from, text, idtext, NULL, 0);
+		return;
+	}
+
+	if (sess->type == SESS_DIALOG)
+		EMIT_SIGNAL (XP_TE_DPRIVMSG, sess, from, text, idtext, NULL, 0);
+	else
+		EMIT_SIGNAL (XP_TE_PRIVMSG, sess, from, text, idtext, NULL, 0);
+}
+
+/* used for Alerts section. Masks can be separated by commas and spaces. */
+
+gboolean
+alert_match_word (char *word, char *masks)
+{
+	char *p = masks;
+	char endchar;
+	int res;
+
+	if (masks[0] == 0)
+		return FALSE;
+
+	while (1)
+	{
+		/* if it's a 0, space or comma, the word has ended. */
+		if (*p == 0 || *p == ' ' || *p == ',')
+		{
+			endchar = *p;
+			*p = 0;
+			res = match (masks, word);
+			*p = endchar;
+
+			if (res)
+				return TRUE;	/* yes, matched! */
+
+			masks = p + 1;
+			if (*p == 0)
+				return FALSE;
+		}
+		p++;
+	}
+}
+
+gboolean
+alert_match_text (char *text, char *masks)
+{
+	unsigned char *p = text;
+	unsigned char endchar;
+	int res;
+
+	if (masks[0] == 0)
+		return FALSE;
+
+	while (1)
+	{
+		if (*p >= '0' && *p <= '9')
+		{
+			p++;
+			continue;
+		}
+
+		/* if it's RFC1459 <special>, it can be inside a word */
+		switch (*p)
+		{
+		case '-': case '[': case ']': case '\\':
+		case '`': case '^': case '{': case '}':
+		case '_': case '|':
+			p++;
+			continue;
+		}
+
+		/* if it's a 0, space or comma, the word has ended. */
+		if (*p == 0 || *p == ' ' || *p == ',' ||
+			/* if it's anything BUT a letter, the word has ended. */
+			 (!g_unichar_isalpha (g_utf8_get_char (p))))
+		{
+			endchar = *p;
+			*p = 0;
+			res = alert_match_word (text, masks);
+			*p = endchar;
+
+			if (res)
+				return TRUE;	/* yes, matched! */
+
+			text = p + g_utf8_skip [p[0]];
+			if (*p == 0)
+				return FALSE;
+		}
+
+		p += g_utf8_skip [p[0]];
+	}
+}
+
+static int
+is_hilight (char *from, char *text, session *sess, server *serv)
+{
+	if (alert_match_word (from, prefs.irc_no_hilight))
+		return 0;
+
+	text = strip_color (text, -1, STRIP_ALL);
+
+	if (alert_match_text (text, serv->nick) ||
+		 alert_match_text (text, prefs.irc_extra_hilight) ||
+		 alert_match_word (from, prefs.irc_nick_hilight))
+	{
+		g_free (text);
+		if (sess != current_tab)
+			sess->nick_said = TRUE;
+		fe_set_hilight (sess);
+		return 1;
+	}
+
+	g_free (text);
+	return 0;
+}
+
+void
+inbound_action (session *sess, char *chan, char *from, char *ip, char *text, int fromme, int id)
+{
+	session *def = sess;
+	server *serv = sess->server;
+	struct User *user;
+	char nickchar[2] = "\000";
+	char idtext[64];
+	int privaction = FALSE;
+
+	if (!fromme)
+	{
+		if (is_channel (serv, chan))
+		{
+			sess = find_channel (serv, chan);
+		} else
+		{
+			/* it's a private action! */
+			privaction = TRUE;
+			/* find a dialog tab for it */
+			sess = find_dialog (serv, from);
+			/* if non found, open a new one */
+			if (!sess && prefs.autodialog)
+			{
+				/* but only if it wouldn't flood */
+				if (flood_check (from, ip, serv, current_sess, 1))
+					sess = inbound_open_dialog (serv, from);
+				else
+					sess = serv->server_session;
+			}
+			if (!sess)
+			{
+				sess = find_session_from_nick (from, serv);
+				/* still not good? */
+				if (!sess)
+					sess = serv->front_session;
+			}
+		}
+	}
+
+	if (!sess)
+		sess = def;
+
+	if (sess != current_tab)
+	{
+		if (fromme)
+		{
+			sess->msg_said = FALSE;
+			sess->new_data = TRUE;
+		} else
+		{
+			sess->msg_said = TRUE;
+			sess->new_data = FALSE;
+		}
+	}
+
+	user = userlist_find (sess, from);
+	if (user)
+	{
+		nickchar[0] = user->prefix[0];
+		user->lasttalk = time (0);
+	}
+
+	inbound_make_idtext (serv, idtext, sizeof (idtext), id);
+
+	if (!fromme && !privaction)
+	{
+		if (is_hilight (from, text, sess, serv))
+		{
+			EMIT_SIGNAL (XP_TE_HCHANACTION, sess, from, text, nickchar, idtext, 0);
+			return;
+		}
+	}
+
+	if (fromme)
+		EMIT_SIGNAL (XP_TE_UACTION, sess, from, text, nickchar, idtext, 0);
+	else if (!privaction)
+		EMIT_SIGNAL (XP_TE_CHANACTION, sess, from, text, nickchar, idtext, 0);
+	else if (sess->type == SESS_DIALOG)
+		EMIT_SIGNAL (XP_TE_DPRIVACTION, sess, from, text, idtext, NULL, 0);
+	else
+		EMIT_SIGNAL (XP_TE_PRIVACTION, sess, from, text, idtext, NULL, 0);
+}
+
+void
+inbound_chanmsg (server *serv, session *sess, char *chan, char *from, char *text, char fromme, int id)
+{
+	struct User *user;
+	int hilight = FALSE;
+	char nickchar[2] = "\000";
+	char idtext[64];
+
+	if (!sess)
+	{
+		if (chan)
+		{
+			sess = find_channel (serv, chan);
+			if (!sess && !is_channel (serv, chan))
+				sess = find_dialog (serv, chan);
+		} else
+		{
+			sess = find_dialog (serv, from);
+		}
+		if (!sess)
+			return;
+	}
+
+	if (sess != current_tab)
+	{
+		sess->msg_said = TRUE;
+		sess->new_data = FALSE;
+	}
+
+	user = userlist_find (sess, from);
+	if (user)
+	{
+		nickchar[0] = user->prefix[0];
+		user->lasttalk = time (0);
+	}
+
+	if (fromme)
+	{
+  		if (prefs.auto_unmark_away && serv->is_away)
+			sess->server->p_set_back (sess->server);
+		EMIT_SIGNAL (XP_TE_UCHANMSG, sess, from, text, nickchar, NULL, 0);
+		return;
+	}
+
+	inbound_make_idtext (serv, idtext, sizeof (idtext), id);
+
+	if (is_hilight (from, text, sess, serv))
+		hilight = TRUE;
+
+	if (sess->type == SESS_DIALOG)
+		EMIT_SIGNAL (XP_TE_DPRIVMSG, sess, from, text, idtext, NULL, 0);
+	else if (hilight)
+		EMIT_SIGNAL (XP_TE_HCHANMSG, sess, from, text, nickchar, idtext, 0);
+	else
+		EMIT_SIGNAL (XP_TE_CHANMSG, sess, from, text, nickchar, idtext, 0);
+}
+
+void
+inbound_newnick (server *serv, char *nick, char *newnick, int quiet)
+{
+	int me = FALSE;
+	session *sess;
+	GSList *list = sess_list;
+
+	if (!serv->p_cmp (nick, serv->nick))
+	{
+		me = TRUE;
+		safe_strcpy (serv->nick, newnick, NICKLEN);
+	}
+
+	while (list)
+	{
+		sess = list->data;
+		if (sess->server == serv)
+		{
+			if (userlist_change (sess, nick, newnick) || (me && sess->type == SESS_SERVER))
+			{
+				if (!quiet)
+				{
+					if (me)
+						EMIT_SIGNAL (XP_TE_UCHANGENICK, sess, nick, newnick, NULL,
+										 NULL, 0);
+					else
+						EMIT_SIGNAL (XP_TE_CHANGENICK, sess, nick, newnick, NULL,
+										 NULL, 0);
+				}
+			}
+			if (sess->type == SESS_DIALOG && !serv->p_cmp (sess->channel, nick))
+			{
+				safe_strcpy (sess->channel, newnick, CHANLEN);
+				fe_set_channel (sess);
+			}
+			fe_set_title (sess);
+		}
+		list = list->next;
+	}
+
+	dcc_change_nick (serv, nick, newnick);
+
+	if (me)
+		fe_set_nick (serv, newnick);
+}
+
+/* find a "<none>" tab */
+static session *
+find_unused_session (server *serv)
+{
+	session *sess;
+	GSList *list = sess_list;
+	while (list)
+	{
+		sess = (session *) list->data;
+		if (sess->type == SESS_CHANNEL && sess->channel[0] == 0 &&
+			 sess->server == serv)
+		{
+			if (sess->waitchannel[0] == 0)
+				return sess;
+		}
+		list = list->next;
+	}
+	return 0;
+}
+
+static session *
+find_session_from_waitchannel (char *chan, struct server *serv)
+{
+	session *sess;
+	GSList *list = sess_list;
+	while (list)
+	{
+		sess = (session *) list->data;
+		if (sess->server == serv && sess->channel[0] == 0 && sess->type == SESS_CHANNEL)
+		{
+			if (!serv->p_cmp (chan, sess->waitchannel))
+				return sess;
+		}
+		list = list->next;
+	}
+	return 0;
+}
+
+void
+inbound_ujoin (server *serv, char *chan, char *nick, char *ip)
+{
+	session *sess;
+
+	/* already joined? probably a bnc */
+	sess = find_channel (serv, chan);
+	if (!sess)
+	{
+		/* see if a window is waiting to join this channel */
+		sess = find_session_from_waitchannel (chan, serv);
+		if (!sess)
+		{
+			/* find a "<none>" tab and use that */
+			sess = find_unused_session (serv);
+			if (!sess)
+				/* last resort, open a new tab/window */
+				sess = new_ircwindow (serv, chan, SESS_CHANNEL, 1);
+		}
+	}
+
+	safe_strcpy (sess->channel, chan, CHANLEN);
+
+	fe_set_channel (sess);
+	fe_set_title (sess);
+	fe_set_nonchannel (sess, TRUE);
+	userlist_clear (sess);
+
+	log_open_or_close (sess);
+
+	sess->waitchannel[0] = 0;
+	sess->ignore_date = TRUE;
+	sess->ignore_mode = TRUE;
+	sess->ignore_names = TRUE;
+	sess->end_of_names = FALSE;
+
+	/* sends a MODE */
+	serv->p_join_info (sess->server, chan);
+
+	EMIT_SIGNAL (XP_TE_UJOIN, sess, nick, chan, ip, NULL, 0);
+
+	if (prefs.userhost)
+	{
+		/* sends WHO #channel */
+		serv->p_user_list (sess->server, chan);
+		sess->doing_who = TRUE;
+	}
+}
+
+void
+inbound_ukick (server *serv, char *chan, char *kicker, char *reason)
+{
+	session *sess = find_channel (serv, chan);
+	if (sess)
+	{
+		EMIT_SIGNAL (XP_TE_UKICK, sess, serv->nick, chan, kicker, reason, 0);
+		clear_channel (sess);
+		if (prefs.autorejoin)
+		{
+			serv->p_join (serv, chan, sess->channelkey);
+			safe_strcpy (sess->waitchannel, chan, CHANLEN);
+		}
+	}
+}
+
+void
+inbound_upart (server *serv, char *chan, char *ip, char *reason)
+{
+	session *sess = find_channel (serv, chan);
+	if (sess)
+	{
+		if (*reason)
+			EMIT_SIGNAL (XP_TE_UPARTREASON, sess, serv->nick, ip, chan, reason,
+							 0);
+		else
+			EMIT_SIGNAL (XP_TE_UPART, sess, serv->nick, ip, chan, NULL, 0);
+		clear_channel (sess);
+	}
+}
+
+void
+inbound_nameslist (server *serv, char *chan, char *names)
+{
+	session *sess;
+	char name[NICKLEN];
+	int pos = 0;
+
+	sess = find_channel (serv, chan);
+	if (!sess)
+	{
+		EMIT_SIGNAL (XP_TE_USERSONCHAN, serv->server_session, chan, names, NULL,
+						 NULL, 0);
+		return;
+	}
+	if (!sess->ignore_names)
+		EMIT_SIGNAL (XP_TE_USERSONCHAN, sess, chan, names, NULL, NULL, 0);
+
+	if (sess->end_of_names)
+	{
+		sess->end_of_names = FALSE;
+		userlist_clear (sess);
+	}
+
+	while (1)
+	{
+		switch (*names)
+		{
+		case 0:
+			name[pos] = 0;
+			if (pos != 0)
+				userlist_add (sess, name, 0);
+			return;
+		case ' ':
+			name[pos] = 0;
+			pos = 0;
+			userlist_add (sess, name, 0);
+			break;
+		default:
+			name[pos] = *names;
+			if (pos < (NICKLEN-1))
+				pos++;
+		}
+		names++;
+	}
+}
+
+void
+inbound_topic (server *serv, char *chan, char *topic_text)
+{
+	session *sess = find_channel (serv, chan);
+	char *stripped_topic;
+
+	if (sess)
+	{
+		stripped_topic = strip_color (topic_text, -1, STRIP_ALL);
+		set_topic (sess, topic_text, stripped_topic);
+		g_free (stripped_topic);
+	} else
+		sess = serv->server_session;
+
+	EMIT_SIGNAL (XP_TE_TOPIC, sess, chan, topic_text, NULL, NULL, 0);
+}
+
+void
+inbound_topicnew (server *serv, char *nick, char *chan, char *topic)
+{
+	session *sess;
+	char *stripped_topic;
+
+	sess = find_channel (serv, chan);
+	if (sess)
+	{
+		stripped_topic = strip_color (topic, -1, STRIP_ALL);
+		set_topic (sess, topic, stripped_topic);
+		g_free (stripped_topic);
+		EMIT_SIGNAL (XP_TE_NEWTOPIC, sess, nick, topic, chan, NULL, 0);
+	}
+}
+
+void
+inbound_join (server *serv, char *chan, char *user, char *ip)
+{
+	session *sess = find_channel (serv, chan);
+	if (sess)
+	{
+		EMIT_SIGNAL (XP_TE_JOIN, sess, user, chan, ip, NULL, 0);
+		userlist_add (sess, user, ip);
+	}
+}
+
+void
+inbound_kick (server *serv, char *chan, char *user, char *kicker, char *reason)
+{
+	session *sess = find_channel (serv, chan);
+	if (sess)
+	{
+		EMIT_SIGNAL (XP_TE_KICK, sess, kicker, user, chan, reason, 0);
+		userlist_remove (sess, user);
+	}
+}
+
+void
+inbound_part (server *serv, char *chan, char *user, char *ip, char *reason)
+{
+	session *sess = find_channel (serv, chan);
+	if (sess)
+	{
+		if (*reason)
+			EMIT_SIGNAL (XP_TE_PARTREASON, sess, user, ip, chan, reason, 0);
+		else
+			EMIT_SIGNAL (XP_TE_PART, sess, user, ip, chan, NULL, 0);
+		userlist_remove (sess, user);
+	}
+}
+
+void
+inbound_topictime (server *serv, char *chan, char *nick, time_t stamp)
+{
+	char *tim = ctime (&stamp);
+	session *sess = find_channel (serv, chan);
+
+	if (!sess)
+		sess = serv->server_session;
+
+	tim[24] = 0;	/* get rid of the \n */
+	EMIT_SIGNAL (XP_TE_TOPICDATE, sess, chan, nick, tim, NULL, 0);
+}
+
+void
+inbound_quit (server *serv, char *nick, char *ip, char *reason)
+{
+	GSList *list = sess_list;
+	session *sess;
+	int was_on_front_session = FALSE;
+
+	while (list)
+	{
+		sess = (session *) list->data;
+		if (sess->server == serv)
+		{
+ 			if (sess == current_sess)
+ 				was_on_front_session = TRUE;
+			if (userlist_remove (sess, nick))
+			{
+				EMIT_SIGNAL (XP_TE_QUIT, sess, nick, reason, ip, NULL, 0);
+			} else if (sess->type == SESS_DIALOG && !serv->p_cmp (sess->channel, nick))
+			{
+				EMIT_SIGNAL (XP_TE_QUIT, sess, nick, reason, ip, NULL, 0);
+			}
+		}
+		list = list->next;
+	}
+
+	notify_set_offline (serv, nick, was_on_front_session);
+}
+
+void
+inbound_ping_reply (session *sess, char *timestring, char *from)
+{
+	unsigned long tim, nowtim, dif;
+	int lag = 0;
+	char outbuf[64];
+
+	if (strncmp (timestring, "LAG", 3) == 0)
+	{
+		timestring += 3;
+		lag = 1;
+	}
+
+	tim = strtoul (timestring, NULL, 10);
+	nowtim = make_ping_time ();
+	dif = nowtim - tim;
+
+	sess->server->ping_recv = time (0);
+
+	if (lag)
+	{
+		sess->server->lag_sent = 0;
+		sess->server->lag = dif / 1000;
+		fe_set_lag (sess->server, dif / 100000);
+		return;
+	}
+
+	if (atol (timestring) == 0)
+	{
+		if (sess->server->lag_sent)
+			sess->server->lag_sent = 0;
+		else
+			EMIT_SIGNAL (XP_TE_PINGREP, sess, from, "?", NULL, NULL, 0);
+	} else
+	{
+		snprintf (outbuf, sizeof (outbuf), "%ld.%ld%ld", dif / 1000000, (dif / 100000) % 10, dif % 10);
+		EMIT_SIGNAL (XP_TE_PINGREP, sess, from, outbuf, NULL, NULL, 0);
+	}
+}
+
+static session *
+find_session_from_type (int type, server *serv)
+{
+	session *sess;
+	GSList *list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+		if (sess->type == type && serv == sess->server)
+			return sess;
+		list = list->next;
+	}
+	return 0;
+}
+
+void
+inbound_notice (server *serv, char *to, char *nick, char *msg, char *ip, int id)
+{
+	char *po,*ptr=to;
+	session *sess = 0;
+	int server_notice = FALSE;
+
+	if (is_channel (serv, ptr))
+		sess = find_channel (serv, ptr);
+
+	if (!sess && ptr[0] == '@')
+	{
+		ptr++;
+		sess = find_channel (serv, ptr);
+	}
+
+	if (!sess && ptr[0] == '%')
+	{
+		ptr++;
+		sess = find_channel (serv, ptr);
+	}
+
+	if (!sess && ptr[0] == '+')
+	{
+		ptr++;
+		sess = find_channel (serv, ptr);
+	}
+
+	if (strcmp (nick, ip) == 0)
+		server_notice = TRUE;
+
+	if (!sess)
+	{
+		ptr = 0;
+		if (prefs.notices_tabs)
+		{
+			int stype = server_notice ? SESS_SNOTICES : SESS_NOTICES;
+			sess = find_session_from_type (stype, serv);
+			if (!sess)
+			{
+				if (stype == SESS_NOTICES)
+					sess = new_ircwindow (serv, "(notices)", SESS_NOTICES, 0);
+				else
+					sess = new_ircwindow (serv, "(snotices)", SESS_SNOTICES, 0);
+				fe_set_channel (sess);
+				fe_set_title (sess);
+				fe_set_nonchannel (sess, FALSE);
+				userlist_clear (sess);
+				log_open_or_close (sess);
+			}
+			/* Avoid redundancy with some Undernet notices */
+			if (!strncmp (msg, "*** Notice -- ", 14))
+				msg += 14;
+		} else
+		{
+											/* paranoia check */
+			if (msg[0] == '[' && (!serv->have_idmsg || id))
+			{
+				/* guess where chanserv meant to post this -sigh- */
+				if (!strcasecmp (nick, "ChanServ") && !find_dialog (serv, nick))
+				{
+					char *dest = strdup (msg + 1);
+					char *end = strchr (dest, ']');
+					if (end)
+					{
+						*end = 0;
+						sess = find_channel (serv, dest);
+					}
+					free (dest);
+				}
+			}
+			if (!sess)
+				sess = find_session_from_nick (nick, serv);
+		}
+		if (!sess)
+		{
+			if (server_notice)	
+				sess = serv->server_session;
+			else
+				sess = serv->front_session;
+		}
+	}
+
+	if (msg[0] == 1)
+	{
+		msg++;
+		if (!strncmp (msg, "PING", 4))
+		{
+			inbound_ping_reply (sess, msg + 5, nick);
+			return;
+		}
+	}
+	po = strchr (msg, '\001');
+	if (po)
+		po[0] = 0;
+
+	if (server_notice)
+		EMIT_SIGNAL (XP_TE_SERVNOTICE, sess, msg, nick, NULL, NULL, 0);
+	else if (ptr)
+		EMIT_SIGNAL (XP_TE_CHANNOTICE, sess, nick, to, msg, NULL, 0);
+	else
+		EMIT_SIGNAL (XP_TE_NOTICE, sess, nick, msg, NULL, NULL, 0);
+}
+
+void
+inbound_away (server *serv, char *nick, char *msg)
+{
+	struct away_msg *away = server_away_find_message (serv, nick);
+	session *sess = NULL;
+	GSList *list;
+
+	if (away && !strcmp (msg, away->message))	/* Seen the msg before? */
+	{
+		if (prefs.show_away_once && !serv->inside_whois)
+			return;
+	} else
+	{
+		server_away_save_message (serv, nick, msg);
+	}
+
+	if (prefs.irc_whois_front)
+		sess = serv->front_session;
+	else
+	{
+		if (!serv->inside_whois)
+			sess = find_session_from_nick (nick, serv);
+		if (!sess)
+			sess = serv->server_session;
+	}
+
+	/* possibly hide the output */
+	if (!serv->inside_whois || !serv->skip_next_whois)
+		EMIT_SIGNAL (XP_TE_WHOIS5, sess, nick, msg, NULL, NULL, 0);
+
+	list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+		if (sess->server == serv)
+			userlist_set_away (sess, nick, TRUE);
+		list = list->next;
+	}
+}
+
+int
+inbound_nameslist_end (server *serv, char *chan)
+{
+	session *sess;
+	GSList *list;
+
+	if (!strcmp (chan, "*"))
+	{
+		list = sess_list;
+		while (list)
+		{
+			sess = list->data;
+			if (sess->server == serv)
+			{
+				sess->end_of_names = TRUE;
+				sess->ignore_names = FALSE;
+			}
+			list = list->next;
+		}
+		return TRUE;
+	}
+	sess = find_channel (serv, chan);
+	if (sess)
+	{
+		sess->end_of_names = TRUE;
+		sess->ignore_names = FALSE;
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static gboolean
+check_autojoin_channels (server *serv)
+{
+	char *po;
+	session *sess;
+	GSList *list = sess_list;
+	int i = 0;
+	GSList *channels, *keys;
+
+	/* shouldnt really happen, the io tag is destroyed in server.c */
+	if (!is_server (serv))
+		return FALSE;
+
+	/* send auto join list */
+	if (serv->autojoin)
+	{
+		joinlist_split (serv->autojoin, &channels, &keys);
+		serv->p_join_list (serv, channels, keys);
+		joinlist_free (channels, keys);
+
+		free (serv->autojoin);
+		serv->autojoin = NULL;
+	}
+
+	/* this is really only for re-connects when you
+    * join channels not in the auto-join list. */
+	while (list)
+	{
+		sess = list->data;
+		if (sess->server == serv)
+		{
+			if (sess->willjoinchannel[0] != 0)
+			{
+				strcpy (sess->waitchannel, sess->willjoinchannel);
+				sess->willjoinchannel[0] = 0;
+				serv->p_join (serv, sess->waitchannel, sess->channelkey);
+				po = strchr (sess->waitchannel, ',');
+				if (po)
+					*po = 0;
+				po = strchr (sess->waitchannel, ' ');
+				if (po)
+					*po = 0;
+				i++;
+			}
+		}
+		list = list->next;
+	}
+	serv->joindelay_tag = 0;
+	fe_server_event (serv, FE_SE_LOGGEDIN, i);
+	return FALSE;
+}
+
+void
+inbound_next_nick (session *sess, char *nick)
+{
+	char *newnick;
+	server *serv = sess->server;
+	ircnet *net;
+
+	serv->nickcount++;
+
+	switch (serv->nickcount)
+	{
+	case 2:
+		newnick = prefs.nick2;
+		net = serv->network;
+		/* use network specific "Second choice"? */
+		if (net && !(net->flags & FLAG_USE_GLOBAL) && net->nick2)
+			newnick = net->nick2;
+		serv->p_change_nick (serv, newnick);
+		EMIT_SIGNAL (XP_TE_NICKCLASH, sess, nick, newnick, NULL, NULL, 0);
+		break;
+
+	case 3:
+		serv->p_change_nick (serv, prefs.nick3);
+		EMIT_SIGNAL (XP_TE_NICKCLASH, sess, nick, prefs.nick3, NULL, NULL, 0);
+		break;
+
+	default:
+		EMIT_SIGNAL (XP_TE_NICKFAIL, sess, NULL, NULL, NULL, NULL, 0);
+	}
+}
+
+void
+do_dns (session *sess, char *nick, char *host)
+{
+	char *po;
+	char tbuf[1024];
+
+	po = strrchr (host, '@');
+	if (po)
+		host = po + 1;
+	EMIT_SIGNAL (XP_TE_RESOLVINGUSER, sess, nick, host, NULL, NULL, 0);
+	snprintf (tbuf, sizeof (tbuf), "exec -d %s %s", prefs.dnsprogram, host);
+	handle_command (sess, tbuf, FALSE);
+}
+
+static void
+set_default_modes (server *serv)
+{
+	char modes[8];
+
+	modes[0] = '+';
+	modes[1] = '\0';
+
+	if (prefs.wallops)
+		strcat (modes, "w");
+	if (prefs.servernotice)
+		strcat (modes, "s");
+	if (prefs.invisible)
+		strcat (modes, "i");
+
+	if (modes[1] != '\0')
+	{
+		serv->p_mode (serv, serv->nick, modes);
+	}
+}
+
+void
+inbound_login_start (session *sess, char *nick, char *servname)
+{
+	inbound_newnick (sess->server, sess->server->nick, nick, TRUE);
+	server_set_name (sess->server, servname);
+	if (sess->type == SESS_SERVER)
+		log_open_or_close (sess);
+	/* reset our away status */
+	if (sess->server->reconnect_away)
+	{
+		handle_command (sess->server->server_session, "away", FALSE);
+		sess->server->reconnect_away = FALSE;
+	}
+}
+
+static void
+inbound_set_all_away_status (server *serv, char *nick, unsigned int status)
+{
+	GSList *list;
+	session *sess;
+
+	list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+		if (sess->server == serv)
+			userlist_set_away (sess, nick, status);
+		list = list->next;
+	}
+}
+
+void
+inbound_uaway (server *serv)
+{
+	serv->is_away = TRUE;
+	serv->away_time = time (NULL);
+	fe_set_away (serv);
+
+	inbound_set_all_away_status (serv, serv->nick, 1);
+}
+
+void
+inbound_uback (server *serv)
+{
+	serv->is_away = FALSE;
+	serv->reconnect_away = FALSE;
+	fe_set_away (serv);
+
+	inbound_set_all_away_status (serv, serv->nick, 0);
+}
+
+void
+inbound_foundip (session *sess, char *ip)
+{
+	struct hostent *HostAddr;
+
+	HostAddr = gethostbyname (ip);
+	if (HostAddr)
+	{
+		prefs.dcc_ip = ((struct in_addr *) HostAddr->h_addr)->s_addr;
+		EMIT_SIGNAL (XP_TE_FOUNDIP, sess,
+						 inet_ntoa (*((struct in_addr *) HostAddr->h_addr)),
+						 NULL, NULL, NULL, 0);
+	}
+}
+
+void
+inbound_user_info_start (session *sess, char *nick)
+{
+	/* set away to FALSE now, 301 may turn it back on */
+	inbound_set_all_away_status (sess->server, nick, 0);
+}
+
+/* reporting new information found about this user. chan may be NULL.
+ * away may be 0xff to indicate UNKNOWN. */
+
+void
+inbound_user_info (session *sess, char *chan, char *user, char *host,
+						 char *servname, char *nick, char *realname,
+						 unsigned int away)
+{
+	server *serv = sess->server;
+	session *who_sess;
+	GSList *list;
+	char *uhost = NULL;
+
+	if (user && host)
+	{
+		uhost = g_malloc (strlen (user) + strlen (host) + 2);
+		sprintf (uhost, "%s@%s", user, host);
+	}
+
+	if (chan)
+	{
+		who_sess = find_channel (serv, chan);
+		if (who_sess)
+			userlist_add_hostname (who_sess, nick, uhost, realname, servname, away);
+		else
+		{
+			if (serv->doing_dns && nick && host)
+				do_dns (sess, nick, host);
+		}
+	}
+	else
+	{
+		/* came from WHOIS, not channel specific */
+		for (list = sess_list; list; list = list->next)
+		{
+			sess = list->data;
+			if (sess->type == SESS_CHANNEL && sess->server == serv)
+			{
+				userlist_add_hostname (sess, nick, uhost, realname, servname, away);
+			}
+		}
+	}
+
+	g_free (uhost);
+}
+
+int
+inbound_banlist (session *sess, time_t stamp, char *chan, char *mask, char *banner, int is_exemption)
+{
+	char *time_str = ctime (&stamp);
+	server *serv = sess->server;
+
+	time_str[19] = 0;	/* get rid of the \n */
+	if (stamp == 0)
+		time_str = "";
+
+	sess = find_channel (serv, chan);
+	if (!sess)
+	{
+		sess = serv->front_session;
+		goto nowindow;
+	}
+
+   if (!fe_is_banwindow (sess))
+	{
+nowindow:
+		/* let proto-irc.c do the 'goto def' for exemptions */
+		if (is_exemption)
+			return FALSE;
+
+		EMIT_SIGNAL (XP_TE_BANLIST, sess, chan, mask, banner, time_str, 0);
+		return TRUE;
+	}
+
+	fe_add_ban_list (sess, mask, banner, time_str, is_exemption);
+	return TRUE;
+}
+
+/* execute 1 end-of-motd command */
+
+static int
+inbound_exec_eom_cmd (char *str, void *sess)
+{
+	handle_command (sess, (str[0] == '/') ? str + 1 : str, TRUE);
+	return 1;
+}
+
+void
+inbound_login_end (session *sess, char *text)
+{
+	server *serv = sess->server;
+
+	if (!serv->end_of_motd)
+	{
+		if (prefs.ip_from_server && serv->use_who)
+		{
+			serv->skip_next_userhost = TRUE;
+			serv->p_get_ip_uh (serv, serv->nick);	/* sends USERHOST mynick */
+		}
+		set_default_modes (serv);
+
+		if (serv->network)
+		{
+			/* there may be more than 1, separated by \n */
+			if (((ircnet *)serv->network)->command)
+				token_foreach (((ircnet *)serv->network)->command, '\n',
+									inbound_exec_eom_cmd, sess);
+
+			/* send nickserv password */
+			if (((ircnet *)serv->network)->nickserv)
+				serv->p_ns_identify (serv, ((ircnet *)serv->network)->nickserv);
+		}
+
+		/* send JOIN now or wait? */
+		if (serv->network && ((ircnet *)serv->network)->nickserv &&
+			 prefs.irc_join_delay)
+			serv->joindelay_tag = fe_timeout_add (prefs.irc_join_delay * 1000,
+															  check_autojoin_channels, serv);
+		else
+			check_autojoin_channels (serv);
+		if (serv->supports_watch)
+			notify_send_watches (serv);
+		serv->end_of_motd = TRUE;
+	}
+	if (prefs.skipmotd && !serv->motd_skipped)
+	{
+		serv->motd_skipped = TRUE;
+		EMIT_SIGNAL (XP_TE_MOTDSKIP, serv->server_session, NULL, NULL,
+						 NULL, NULL, 0);
+		return;
+	}
+	EMIT_SIGNAL (XP_TE_MOTD, serv->server_session, text, NULL,
+					 NULL, NULL, 0);
+}
+
+void
+inbound_identified (server *serv)	/* 'MODE +e MYSELF' on freenode */
+{
+	if (serv->joindelay_tag)
+	{
+		/* stop waiting, just auto JOIN now */
+		fe_timeout_remove (serv->joindelay_tag);
+		serv->joindelay_tag = 0;
+		check_autojoin_channels (serv);
+	}
+}
diff --git a/src/common/inbound.h b/src/common/inbound.h
new file mode 100644
index 00000000..b972981f
--- /dev/null
+++ b/src/common/inbound.h
@@ -0,0 +1,39 @@
+#ifndef XCHAT_INBOUND_H
+#define XCHAT_INBOUND_H
+
+void inbound_next_nick (session *sess, char *nick);
+void inbound_uback (server *serv);
+void inbound_uaway (server *serv);
+void inbound_part (server *serv, char *chan, char *user, char *ip, char *reason);
+void inbound_upart (server *serv, char *chan, char *ip, char *reason);
+void inbound_ukick (server *serv, char *chan, char *kicker, char *reason);
+void inbound_kick (server *serv, char *chan, char *user, char *kicker, char *reason);
+void inbound_notice (server *serv, char *to, char *nick, char *msg, char *ip, int id);
+void inbound_quit (server *serv, char *nick, char *ip, char *reason);
+void inbound_topicnew (server *serv, char *nick, char *chan, char *topic);
+void inbound_join (server *serv, char *chan, char *user, char *ip);
+void inbound_ujoin (server *serv, char *chan, char *nick, char *ip);
+void inbound_topictime (server *serv, char *chan, char *nick, time_t stamp);
+void inbound_topic (server *serv, char *chan, char *topic_text);
+void inbound_user_info_start (session *sess, char *nick);
+void inbound_user_info (session *sess, char *chan, char *user, char *host, char *servname, char *nick, char *realname, unsigned int away);
+void inbound_foundip (session *sess, char *ip);
+int inbound_banlist (session *sess, time_t stamp, char *chan, char *mask, char *banner, int is_exemption);
+void inbound_ping_reply (session *sess, char *timestring, char *from);
+void inbound_nameslist (server *serv, char *chan, char *names);
+int inbound_nameslist_end (server *serv, char *chan);
+void inbound_away (server *serv, char *nick, char *msg);
+void inbound_login_start (session *sess, char *nick, char *servname);
+void inbound_login_end (session *sess, char *text);
+void inbound_chanmsg (server *serv, session *sess, char *chan, char *from, char *text, char fromme, int id);
+void clear_channel (session *sess);
+void set_topic (session *sess, char *topic, char *stripped_topic);
+void inbound_privmsg (server *serv, char *from, char *ip, char *text, int id);
+void inbound_action (session *sess, char *chan, char *from, char *ip, char *text, int fromme, int id);
+void inbound_newnick (server *serv, char *nick, char *newnick, int quiet);
+void do_dns (session *sess, char *nick, char *host);
+void inbound_identified (server *serv);
+gboolean alert_match_word (char *word, char *masks);
+gboolean alert_match_text (char *text, char *masks);
+
+#endif
diff --git a/src/common/inet.h b/src/common/inet.h
new file mode 100644
index 00000000..b420c9c6
--- /dev/null
+++ b/src/common/inet.h
@@ -0,0 +1,43 @@
+/* include stuff for internet */
+
+#ifndef WIN32
+
+#ifdef WANTSOCKET
+#include <sys/types.h>
+#include <sys/socket.h>
+#endif
+#ifdef WANTARPA
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+#ifdef WANTDNS
+#include <netdb.h>
+#endif
+#define closesocket close
+#define set_blocking(sok) fcntl(sok, F_SETFL, 0)
+#define set_nonblocking(sok) fcntl(sok, F_SETFL, O_NONBLOCK)
+#define would_block() (errno == EAGAIN || errno == EWOULDBLOCK)
+#define sock_error() (errno)
+
+#else
+
+#ifdef USE_IPV6
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <tpipv6.h>
+#else
+#include <winsock.h>
+#endif
+
+#define set_blocking(sok)	{ \
+									unsigned long zero = 0; \
+									ioctlsocket (sok, FIONBIO, &zero); \
+									}
+#define set_nonblocking(sok)	{ \
+										unsigned long one = 1; \
+										ioctlsocket (sok, FIONBIO, &one); \
+										}
+#define would_block() (WSAGetLastError() == WSAEWOULDBLOCK)
+#define sock_error WSAGetLastError
+
+#endif
diff --git a/src/common/make-te.c b/src/common/make-te.c
new file mode 100644
index 00000000..ed87df3d
--- /dev/null
+++ b/src/common/make-te.c
@@ -0,0 +1,58 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+int main()
+{
+	char name[512];
+	char num[512];
+	char help[512];
+	char def[512];
+	char args[512];
+	char buf[512];
+	char *defines[512];
+  	int i = 0, max;
+
+	printf("/* this file is auto generated, edit textevents.in instead! */\n\nconst struct text_event te[] = {\n");
+	while(fgets(name, sizeof(name), stdin))
+	{
+		name[strlen(name)-1] = 0;
+		fgets(num, sizeof(num), stdin);
+		num[strlen(num)-1] = 0;
+		fgets(help, sizeof(help), stdin);
+		help[strlen(help)-1] = 0;
+		fgets(def, sizeof(def), stdin);
+		def[strlen(def)-1] = 0;
+		fgets(args, sizeof(args), stdin);
+		args[strlen(args)-1] = 0;
+		fgets(buf, sizeof(buf), stdin);
+
+		if (args[0] == 'n')
+			printf("\n{\"%s\", %s, %d, \n\"%s\"},\n",
+							 name, help, atoi(args+1) | 128, def);
+		else
+			printf("\n{\"%s\", %s, %d, \nN_(\"%s\")},\n",
+							 name, help, atoi(args), def);
+		defines[i] = strdup (num);
+		i++;
+	}
+
+	printf("};\n");
+	
+	fprintf(stderr, "/* this file is auto generated, edit textevents.in instead! */\n\nenum\n{\n");
+	max = i;
+	i = 0;
+	while (i < max)
+	{
+		if (i + 1 < max)
+		{
+			fprintf(stderr, "\t%s,\t\t%s,\n", defines[i], defines[i+1]);
+			i++;
+		} else
+			fprintf(stderr, "\t%s,\n", defines[i]);
+		i++;
+	}
+	fprintf(stderr, "\tNUM_XP\n};\n");
+
+	return 0;
+}
diff --git a/src/common/modes.c b/src/common/modes.c
new file mode 100644
index 00000000..1acf7f54
--- /dev/null
+++ b/src/common/modes.c
@@ -0,0 +1,836 @@
+/* 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 <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <glib.h>
+#include <glib/gprintf.h>
+
+#include "xchat.h"
+#include "xchatc.h"
+#include "modes.h"
+#include "server.h"
+#include "text.h"
+#include "fe.h"
+#include "util.h"
+#include "inbound.h"
+#ifdef HAVE_STRINGS_H
+#include <strings.h>
+#endif
+
+typedef struct
+{
+	server *serv;
+	char *op;
+	char *deop;
+	char *voice;
+	char *devoice;
+} mode_run;
+
+static int is_prefix_char (server * serv, char c);
+static void record_chan_mode (session *sess, char sign, char mode, char *arg);
+static char *mode_cat (char *str, char *addition);
+static void handle_single_mode (mode_run *mr, char sign, char mode, char *nick, char *chan, char *arg, int quiet, int is_324);
+static int mode_has_arg (server *serv, char sign, char mode);
+static void mode_print_grouped (session *sess, char *nick, mode_run *mr);
+static int mode_chanmode_type (server * serv, char mode);
+
+
+/* word[] - list of nicks.
+   wpos   - index into word[]. Where nicks really start.
+   end    - index into word[]. Last entry plus one.
+   sign   - a char, e.g. '+' or '-'
+   mode   - a mode, e.g. 'o' or 'v'	*/
+void
+send_channel_modes (session *sess, char *tbuf, char *word[], int wpos,
+						  int end, char sign, char mode, int modes_per_line)
+{
+	int usable_modes, orig_len, len, wlen, i, max;
+	server *serv = sess->server;
+
+	/* sanity check. IRC RFC says three per line. */
+	if (serv->modes_per_line < 3)
+		serv->modes_per_line = 3;
+	if (modes_per_line < 1)
+		modes_per_line = serv->modes_per_line;
+
+	/* RFC max, minus length of "MODE %s " and "\r\n" and 1 +/- sign */
+	/* 512 - 6 - 2 - 1 - strlen(chan) */
+	max = 503 - strlen (sess->channel);
+
+	while (wpos < end)
+	{
+		tbuf[0] = '\0';
+		orig_len = len = 0;
+
+		/* we'll need this many modechars too */
+		len += modes_per_line;
+
+		/* how many can we fit? */
+		for (i = 0; i < modes_per_line; i++)
+		{
+			/* no more nicks left? */
+			if (wpos + i >= end)
+				break;
+			wlen = strlen (word[wpos + i]) + 1;
+			if (wlen + len > max)
+				break;
+			len += wlen; /* length of our whole string so far */
+		}
+		if (i < 1)
+			return;
+		usable_modes = i;	/* this is how many we'll send on this line */
+
+		/* add the +/-modemodemodemode */
+		len = orig_len;
+		tbuf[len] = sign;
+		len++;
+		for (i = 0; i < usable_modes; i++)
+		{
+			tbuf[len] = mode;
+			len++;
+		}
+		tbuf[len] = 0;	/* null terminate for the strcat() to work */
+
+		/* add all the nicknames */
+		for (i = 0; i < usable_modes; i++)
+		{
+			strcat (tbuf, " ");
+			strcat (tbuf, word[wpos + i]);
+		}
+		serv->p_mode (serv, sess->channel, tbuf);
+
+		wpos += usable_modes;
+	}
+}
+
+/* does 'chan' have a valid prefix? e.g. # or & */
+
+int
+is_channel (server * serv, char *chan)
+{
+	if (strchr (serv->chantypes, chan[0]))
+		return 1;
+	return 0;
+}
+
+/* is the given char a valid nick mode char? e.g. @ or + */
+
+static int
+is_prefix_char (server * serv, char c)
+{
+	int pos = 0;
+	char *np = serv->nick_prefixes;
+
+	while (np[0])
+	{
+		if (np[0] == c)
+			return pos;
+		pos++;
+		np++;
+	}
+
+	if (serv->bad_prefix)
+	{
+		if (strchr (serv->bad_nick_prefixes, c))
+		/* valid prefix char, but mode unknown */
+			return -2;
+	}
+
+	return -1;
+}
+
+/* returns '@' for ops etc... */
+
+char
+get_nick_prefix (server * serv, unsigned int access)
+{
+	int pos;
+	char c;
+
+	for (pos = 0; pos < USERACCESS_SIZE; pos++)
+	{
+		c = serv->nick_prefixes[pos];
+		if (c == 0)
+			break;
+		if (access & (1 << pos))
+			return c;
+	}
+
+	return 0;
+}
+
+/* returns the access bitfield for a nickname. E.g.
+	@nick would return 000010 in binary
+	%nick would return 000100 in binary
+	+nick would return 001000 in binary */
+
+unsigned int
+nick_access (server * serv, char *nick, int *modechars)
+{
+	int i;
+	unsigned int access = 0;
+	char *orig = nick;
+
+	while (*nick)
+	{
+		i = is_prefix_char (serv, *nick);
+		if (i == -1)
+			break;
+
+		/* -2 == valid prefix char, but mode unknown */
+		if (i != -2)
+			access |= (1 << i);
+
+		nick++;
+	}
+
+	*modechars = nick - orig;
+
+	return access;
+}
+
+/* returns the access number for a particular mode. e.g.
+	mode 'a' returns 0
+	mode 'o' returns 1
+	mode 'h' returns 2
+	mode 'v' returns 3
+	Also puts the nick-prefix-char in 'prefix' */
+
+int
+mode_access (server * serv, char mode, char *prefix)
+{
+	int pos = 0;
+
+	while (serv->nick_modes[pos])
+	{
+		if (serv->nick_modes[pos] == mode)
+		{
+			*prefix = serv->nick_prefixes[pos];
+			return pos;
+		}
+		pos++;
+	}
+
+	*prefix = 0;
+
+	return -1;
+}
+
+static void
+record_chan_mode (session *sess, char sign, char mode, char *arg)
+{
+	/* Somebody needed to acutally update sess->current_modes, needed to
+		play nice with bouncers, and less mode calls. Also keeps modes up
+		to date for scripts */
+	server *serv = sess->server;
+	GString *current = g_string_new(sess->current_modes);
+	gint mode_pos = -1;
+	gchar *current_char = current->str;
+	gint modes_length;
+	gint argument_num = 0;
+	gint argument_offset = 0;
+	gint argument_length = 0;
+	int i = 0;
+	gchar *arguments_start;
+
+	/* find out if the mode currently exists */
+	arguments_start = g_strstr_len(current->str	, -1, " ");
+	if (arguments_start) {
+		modes_length = arguments_start - current->str;
+	}
+	else {
+		modes_length = current->len;
+		/* set this to the end of the modes */
+		arguments_start = current->str + current->len;
+	}
+
+	while (mode_pos == -1 && i < modes_length)
+	{
+		if (*current_char == mode)
+		{
+			mode_pos = i;
+		}
+		else
+		{
+			i++;
+			current_char++;
+		}
+	}
+
+	/* if the mode currently exists and has an arg, need to know where
+	 * (including leading space) */
+	if (mode_pos != -1 && mode_has_arg(serv, '+', mode))
+	{
+		current_char = current->str;
+
+		i = 0;
+		while (i <= mode_pos)
+		{
+			if (mode_has_arg(serv, '+', *current_char))
+				argument_num++;
+			current_char++;
+			i++;
+		}
+
+		/* check through arguments for where to start */
+		current_char = arguments_start;
+		i = 0;
+		while (i < argument_num && *current_char != '\0')
+		{
+			if (*current_char == ' ')
+				i++;
+			if (i != argument_num)
+				current_char++;
+		}
+		argument_offset = current_char - current->str;
+
+		/* how long the existing argument is for this key
+		 * important for malloc and strncpy */
+		if (i == argument_num)
+		{
+			argument_length++;
+			current_char++;
+			while (*current_char != '\0' && *current_char != ' ')
+			{
+				argument_length++;
+				current_char++;
+			}
+		}
+	}
+
+	/* two cases, adding and removing a mode, handled differently */
+	if (sign == '+')
+	{
+		if (mode_pos != -1)
+		{
+			/* if it already exists, only need to do something (change)
+			 * if there should be a param */
+			if (mode_has_arg(serv, sign, mode))
+			{
+				/* leave the old space there */
+				current = g_string_erase(current, argument_offset+1, argument_length-1);
+				current = g_string_insert(current, argument_offset+1, arg);
+
+				free(sess->current_modes);
+				sess->current_modes = g_string_free(current, FALSE);
+			}
+		}
+		/* mode wasn't there before */
+		else
+		{
+			/* insert the new mode character */
+			current = g_string_insert_c(current, modes_length, mode);
+
+			/* add the argument, with space if there is one */
+			if (mode_has_arg(serv, sign, mode))
+			{
+				current = g_string_append_c(current, ' ');
+				current = g_string_append(current, arg);
+			}
+
+			free(sess->current_modes);
+			sess->current_modes = g_string_free(current, FALSE);
+		}
+	}
+	else if (sign == '-' && mode_pos != -1)
+	{
+		/* remove the argument first if it has one*/
+		if (mode_has_arg(serv, '+', mode))
+			current = g_string_erase(current, argument_offset, argument_length);
+
+		/* remove the mode character */
+		current = g_string_erase(current, mode_pos, 1);
+
+		free(sess->current_modes);
+		sess->current_modes = g_string_free(current, FALSE);
+	}
+}
+
+static char *
+mode_cat (char *str, char *addition)
+{
+	int len;
+
+	if (str)
+	{
+		len = strlen (str) + strlen (addition) + 2;
+		str = realloc (str, len);
+		strcat (str, " ");
+		strcat (str, addition);
+	} else
+	{
+		str = strdup (addition);
+	}
+
+	return str;
+}
+
+/* handle one mode, e.g.
+   handle_single_mode (mr,'+','b',"elite","#warez","banneduser",) */
+
+static void
+handle_single_mode (mode_run *mr, char sign, char mode, char *nick,
+						  char *chan, char *arg, int quiet, int is_324)
+{
+	session *sess;
+	server *serv = mr->serv;
+	char outbuf[4];
+
+	outbuf[0] = sign;
+	outbuf[1] = 0;
+	outbuf[2] = mode;
+	outbuf[3] = 0;
+
+	sess = find_channel (serv, chan);
+	if (!sess || !is_channel (serv, chan))
+	{
+		/* got modes for a chan we're not in! probably nickmode +isw etc */
+		sess = serv->front_session;
+		goto genmode;
+	}
+
+	/* is this a nick mode? */
+	if (strchr (serv->nick_modes, mode))
+	{
+		/* update the user in the userlist */
+		userlist_update_mode (sess, /*nickname */ arg, mode, sign);
+	} else
+	{
+		if (!is_324 && !sess->ignore_mode && mode_chanmode_type(serv, mode) >= 1)
+			record_chan_mode (sess, sign, mode, arg);
+	}
+
+	switch (sign)
+	{
+	case '+':
+		switch (mode)
+		{
+		case 'k':
+			safe_strcpy (sess->channelkey, arg, sizeof (sess->channelkey));
+			fe_update_channel_key (sess);
+			fe_update_mode_buttons (sess, mode, sign);
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANSETKEY, sess, nick, arg, NULL, NULL, 0);
+			return;
+		case 'l':
+			sess->limit = atoi (arg);
+			fe_update_channel_limit (sess);
+			fe_update_mode_buttons (sess, mode, sign);
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANSETLIMIT, sess, nick, arg, NULL, NULL, 0);
+			return;
+		case 'o':
+			if (!quiet)
+				mr->op = mode_cat (mr->op, arg);
+			return;
+		case 'h':
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANHOP, sess, nick, arg, NULL, NULL, 0);
+			return;
+		case 'v':
+			if (!quiet)
+				mr->voice = mode_cat (mr->voice, arg);
+			return;
+		case 'b':
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANBAN, sess, nick, arg, NULL, NULL, 0);
+			return;
+		case 'e':
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANEXEMPT, sess, nick, arg, NULL, NULL, 0);
+			return;
+		case 'I':
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANINVITE, sess, nick, arg, NULL, NULL, 0);
+			return;
+		}
+		break;
+	case '-':
+		switch (mode)
+		{
+		case 'k':
+			sess->channelkey[0] = 0;
+			fe_update_channel_key (sess);
+			fe_update_mode_buttons (sess, mode, sign);
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANRMKEY, sess, nick, NULL, NULL, NULL, 0);
+			return;
+		case 'l':
+			sess->limit = 0;
+			fe_update_channel_limit (sess);
+			fe_update_mode_buttons (sess, mode, sign);
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANRMLIMIT, sess, nick, NULL, NULL, NULL, 0);
+			return;
+		case 'o':
+			if (!quiet)
+				mr->deop = mode_cat (mr->deop, arg);
+			return;
+		case 'h':
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANDEHOP, sess, nick, arg, NULL, NULL, 0);
+			return;
+		case 'v':
+			if (!quiet)
+				mr->devoice = mode_cat (mr->devoice, arg);
+			return;
+		case 'b':
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANUNBAN, sess, nick, arg, NULL, NULL, 0);
+			return;
+		case 'e':
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANRMEXEMPT, sess, nick, arg, NULL, NULL, 0);
+			return;
+		case 'I':
+			if (!quiet)
+				EMIT_SIGNAL (XP_TE_CHANRMINVITE, sess, nick, arg, NULL, NULL, 0);
+			return;
+		}
+	}
+
+	fe_update_mode_buttons (sess, mode, sign);
+
+ genmode:
+	/* Received umode +e. If we're waiting to send JOIN then send now! */
+	if (mode == 'e' && sign == '+' && !serv->p_cmp (chan, serv->nick))
+		inbound_identified (serv);
+
+	if (!quiet)
+	{
+		if (*arg)
+		{
+			char *buf = malloc (strlen (chan) + strlen (arg) + 2);
+			sprintf (buf, "%s %s", chan, arg);
+			EMIT_SIGNAL (XP_TE_CHANMODEGEN, sess, nick, outbuf, outbuf + 2, buf, 0);
+			free (buf);
+		} else
+			EMIT_SIGNAL (XP_TE_CHANMODEGEN, sess, nick, outbuf, outbuf + 2, chan, 0);
+	}
+}
+
+/* does this mode have an arg? like +b +l +o */
+
+static int
+mode_has_arg (server * serv, char sign, char mode)
+{
+	int type;
+
+	/* if it's a nickmode, it must have an arg */
+	if (strchr (serv->nick_modes, mode))
+		return 1;
+
+	type = mode_chanmode_type (serv, mode);
+	switch (type)
+	{
+	case 0:					  /* type A */
+	case 1:					  /* type B */
+		return 1;
+	case 2:					  /* type C */
+		if (sign == '+')
+			return 1;
+	case 3:					  /* type D */
+		return 0;
+	default:
+		return 0;
+	}
+
+}
+
+/* what type of chanmode is it? -1 for not in chanmode */
+static int
+mode_chanmode_type (server * serv, char mode)
+{
+	/* see what numeric 005 CHANMODES=xxx said */
+	char *cm = serv->chanmodes;
+	int type = 0;
+	int found = 0;
+
+	while (*cm && !found)
+	{
+		if (*cm == ',')
+		{
+			type++;
+		} else if (*cm == mode)
+		{
+			found = 1;
+		}
+		cm++;
+	}
+	if (found)
+		return type;
+	/* not found? -1 */
+	else
+		return -1;
+}
+
+static void
+mode_print_grouped (session *sess, char *nick, mode_run *mr)
+{
+	/* print all the grouped Op/Deops */
+	if (mr->op)
+	{
+		EMIT_SIGNAL (XP_TE_CHANOP, sess, nick, mr->op, NULL, NULL, 0);
+		free (mr->op);
+		mr->op = NULL;
+	}
+
+	if (mr->deop)
+	{
+		EMIT_SIGNAL (XP_TE_CHANDEOP, sess, nick, mr->deop, NULL, NULL, 0);
+		free (mr->deop);
+		mr->deop = NULL;
+	}
+
+	if (mr->voice)
+	{
+		EMIT_SIGNAL (XP_TE_CHANVOICE, sess, nick, mr->voice, NULL, NULL, 0);
+		free (mr->voice);
+		mr->voice = NULL;
+	}
+
+	if (mr->devoice)
+	{
+		EMIT_SIGNAL (XP_TE_CHANDEVOICE, sess, nick, mr->devoice, NULL, NULL, 0);
+		free (mr->devoice);
+		mr->devoice = NULL;
+	}
+}
+
+
+/* handle a MODE or numeric 324 from server */
+
+void
+handle_mode (server * serv, char *word[], char *word_eol[],
+				 char *nick, int numeric_324)
+{
+	session *sess;
+	char *chan;
+	char *modes;
+	char *argstr;
+	char sign;
+	int len;
+	int arg;
+	int i, num_args;
+	int num_modes;
+	int offset = 3;
+	int all_modes_have_args = FALSE;
+	int using_front_tab = FALSE;
+	mode_run mr;
+
+	mr.serv = serv;
+	mr.op = mr.deop = mr.voice = mr.devoice = NULL;
+
+	/* numeric 324 has everything 1 word later (as opposed to MODE) */
+	if (numeric_324)
+		offset++;
+
+	chan = word[offset];
+	modes = word[offset + 1];
+	if (*modes == ':')
+		modes++;
+
+	if (*modes == 0)
+		return;	/* beyondirc's blank modes */
+
+	sess = find_channel (serv, chan);
+	if (!sess)
+	{
+		sess = serv->front_session;
+		using_front_tab = TRUE;
+	}
+	/* remove trailing space */
+	len = strlen (word_eol[offset]) - 1;
+	if (word_eol[offset][len] == ' ')
+		word_eol[offset][len] = 0;
+
+	if (prefs.raw_modes && !numeric_324)
+		EMIT_SIGNAL (XP_TE_RAWMODES, sess, nick, word_eol[offset], 0, 0, 0);
+
+	if (numeric_324 && !using_front_tab)
+	{
+		if (sess->current_modes)
+			free (sess->current_modes);
+		sess->current_modes = strdup (word_eol[offset+1]);
+	}
+
+	sign = *modes;
+	modes++;
+	arg = 1;
+
+	/* count the number of arguments (e.g. after the -o+v) */
+	num_args = 0;
+	i = 1;
+	while ((i + offset + 1) < PDIWORDS)
+	{
+		i++;
+		if (!(*word[i + offset]))
+			break;
+		num_args++;
+	}
+
+	/* count the number of modes (without the -/+ chars */
+	num_modes = 0;
+	i = 0;
+	while (i < strlen (modes))
+	{
+		if (modes[i] != '+' && modes[i] != '-')
+			num_modes++;
+		i++;
+	}
+
+	if (num_args == num_modes)
+		all_modes_have_args = TRUE;
+
+	while (*modes)
+	{
+		switch (*modes)
+		{
+		case '-':
+		case '+':
+			/* print all the grouped Op/Deops */
+			mode_print_grouped (sess, nick, &mr);
+			sign = *modes;
+			break;
+		default:
+			argstr = "";
+			if ((all_modes_have_args || mode_has_arg (serv, sign, *modes)) && arg < (num_args+1))
+			{
+				arg++;
+				argstr = word[arg + offset];
+			}
+			handle_single_mode (&mr, sign, *modes, nick, chan,
+									  argstr, numeric_324 || prefs.raw_modes,
+									  numeric_324);
+		}
+
+		modes++;
+	}
+
+	/* update the title at the end, now that the mode update is internal now */
+	if (!using_front_tab)
+		fe_set_title (sess);
+
+	/* print all the grouped Op/Deops */
+	mode_print_grouped (sess, nick, &mr);
+}
+
+/* handle the 005 numeric */
+
+void
+inbound_005 (server * serv, char *word[])
+{
+	int w;
+	char *pre;
+
+	w = 4;							  /* start at the 4th word */
+	while (w < PDIWORDS && *word[w])
+	{
+		if (strncmp (word[w], "MODES=", 6) == 0)
+		{
+			serv->modes_per_line = atoi (word[w] + 6);
+		} else if (strncmp (word[w], "CHANTYPES=", 10) == 0)
+		{
+			free (serv->chantypes);
+			serv->chantypes = strdup (word[w] + 10);
+		} else if (strncmp (word[w], "CHANMODES=", 10) == 0)
+		{
+			free (serv->chanmodes);
+			serv->chanmodes = strdup (word[w] + 10);
+		} else if (strncmp (word[w], "PREFIX=", 7) == 0)
+		{
+			pre = strchr (word[w] + 7, ')');
+			if (pre)
+			{
+				pre[0] = 0;			  /* NULL out the ')' */
+				free (serv->nick_prefixes);
+				free (serv->nick_modes);
+				serv->nick_prefixes = strdup (pre + 1);
+				serv->nick_modes = strdup (word[w] + 8);
+			} else
+			{
+				/* bad! some ircds don't give us the modes. */
+				/* in this case, we use it only to strip /NAMES */
+				serv->bad_prefix = TRUE;
+				if (serv->bad_nick_prefixes)
+					free (serv->bad_nick_prefixes);
+				serv->bad_nick_prefixes = strdup (word[w] + 7);
+			}
+		} else if (strncmp (word[w], "WATCH=", 6) == 0)
+		{
+			serv->supports_watch = TRUE;
+		} else if (strncmp (word[w], "NETWORK=", 8) == 0)
+		{
+/*			if (serv->networkname)
+				free (serv->networkname);
+			serv->networkname = strdup (word[w] + 8);*/
+
+			if (serv->server_session->type == SESS_SERVER)
+			{
+				safe_strcpy (serv->server_session->channel, word[w] + 8, CHANLEN);
+				fe_set_channel (serv->server_session);
+			}
+
+			/* use /NICKSERV */
+			if (strcasecmp (word[w] + 8, "RusNet") == 0)
+				serv->nickservtype = 1;
+			else if (strcasecmp (word[w] + 8, "UniBG") == 0)
+				serv->nickservtype = 3;
+			else if (strcasecmp (word[w] + 8, "QuakeNet") == 0)
+				serv->nickservtype = 4;
+
+		} else if (strncmp (word[w], "CASEMAPPING=", 12) == 0)
+		{
+			if (strcmp (word[w] + 12, "ascii") == 0)	/* bahamut */
+				serv->p_cmp = (void *)strcasecmp;
+		} else if (strncmp (word[w], "CHARSET=", 8) == 0)
+		{
+			if (strcasecmp (word[w] + 8, "UTF-8") == 0)
+			{
+				server_set_encoding (serv, "UTF-8");
+			}
+		} else if (strcmp (word[w], "NAMESX") == 0)
+		{
+									/* 12345678901234567 */
+			tcp_send_len (serv, "PROTOCTL NAMESX\r\n", 17);
+		} else if (strcmp (word[w], "WHOX") == 0)
+		{
+			serv->have_whox = TRUE;
+		} else if (strcmp (word[w], "CAPAB") == 0)
+		{
+			serv->have_capab = TRUE;
+									/* 12345678901234567890 */
+			tcp_send_len (serv, "CAPAB IDENTIFY-MSG\r\n", 20);
+			/* now wait for numeric 290 */	
+		} else if (strcmp (word[w], "EXCEPTS") == 0)
+		{
+#ifndef WIN32
+			serv->have_except = TRUE;
+#endif
+		} else if (strncmp (word[w], "ELIST=", 6) == 0)
+		{
+			/* supports LIST >< min/max user counts? */
+			if (strchr (word[w] + 6, 'U') || strchr (word[w] + 6, 'u'))
+				serv->use_listargs = TRUE;
+		}
+
+		w++;
+	}
+}
diff --git a/src/common/modes.h b/src/common/modes.h
new file mode 100644
index 00000000..3f9c4a72
--- /dev/null
+++ b/src/common/modes.h
@@ -0,0 +1,12 @@
+#ifndef XCHAT_MODES_H
+#define XCHAT_MODES_H
+
+int is_channel (server *serv, char *chan);
+char get_nick_prefix (server *serv, unsigned int access);
+unsigned int nick_access (server *serv, char *nick, int *modechars);
+int mode_access (server *serv, char mode, char *prefix);
+void inbound_005 (server *serv, char *word[]);
+void handle_mode (server *serv, char *word[], char *word_eol[], char *nick, int numeric_324);
+void send_channel_modes (session *sess, char *tbuf, char *word[], int start, int end, char sign, char mode, int modes_per_line);
+
+#endif
diff --git a/src/common/msproxy.c b/src/common/msproxy.c
new file mode 100644
index 00000000..9c85394d
--- /dev/null
+++ b/src/common/msproxy.c
@@ -0,0 +1,467 @@
+/* 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
+ *
+ * MS Proxy (ISA server) support is (c) 2006 Pavel Fedin <sonic_amiga@rambler.ru>
+ * based on Dante source code
+ * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006
+ *      Inferno Nettverk A/S, Norway.  All rights reserved.
+ */
+
+/*#define DEBUG_MSPROXY*/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#define WANTSOCKET
+#define WANTARPA
+#include "inet.h"
+
+#include "xchat.h"
+#include "network.h"
+#include "xchatc.h"
+#include "server.h"
+#include "msproxy.h"
+
+
+#ifdef USE_MSPROXY
+#include <ntlm.h>
+
+static int
+send_msprequest(s, state, request, end)
+	int s;
+	struct msproxy_state_t *state;
+	struct msproxy_request_t *request;
+	char *end;
+{
+	ssize_t w;
+	size_t l;
+
+	request->magic25 = htonl(MSPROXY_VERSION);
+	request->serverack = state->seq_recv;
+	/* don't start incrementing sequence until we are acking packet #2. */
+	request->sequence	= (unsigned char)(request->serverack >= 2 ? state->seq_sent + 1 : 0);
+
+	memcpy(request->RWSP, "RWSP", sizeof(request->RWSP));
+
+	l = end - (char *)request;
+	/* all requests must be atleast MSPROXY_MINLENGTH it seems. */
+	if (l < MSPROXY_MINLENGTH) {
+		bzero(end, (size_t)(MSPROXY_MINLENGTH - l));
+		l = MSPROXY_MINLENGTH;
+	}
+
+	if ((w = send(s, request, l, 0)) != l) {
+#ifdef DEBUG_MSPROXY
+		printf ("send_msprequest(): send() failed (%d bytes sent instead of %d\n", w, l);
+		perror ("Error is");
+#endif
+		return -1;
+	}
+	state->seq_sent = request->sequence;
+
+	return w;
+}
+
+static int
+recv_mspresponse(s, state, response)
+	int s;
+	struct msproxy_state_t *state;
+	struct msproxy_response_t *response;
+{
+	ssize_t r;
+
+	do {
+		if ((r = recv (s, response, sizeof (*response), 0)) < MSPROXY_MINLENGTH) {
+#ifdef DEBUG_MSPROXY
+			printf ("recv_mspresponse(): expected to read atleast %d, read %d\n", MSPROXY_MINLENGTH, r);
+#endif
+			return -1;
+		}
+		if (state->seq_recv == 0)
+			break; /* not started incrementing yet. */
+#ifdef DEBUG_MSPROXY
+		if (response->sequence == state->seq_recv)
+			printf ("seq_recv: %d, dup response, seqnumber: 0x%x\n", state->seq_recv, response->sequence);
+#endif
+	} while (response->sequence == state->seq_recv);
+
+	state->seq_recv = response->sequence;
+
+	return r;
+}
+
+int
+traverse_msproxy (int sok, char *serverAddr, int port, struct msproxy_state_t *state, netstore *ns_proxy, int csok4, int csok6, int *csok, char bound)
+{
+	struct msproxy_request_t req;
+	struct msproxy_response_t res;
+	char *data, *p;
+	char hostname[NT_MAXNAMELEN];
+	char ntdomain[NT_MAXNAMELEN];
+	char challenge[8];
+	netstore *ns_client;
+	int clientport;
+	guint32 destaddr;
+	guint32 flags;
+
+	if (!prefs.proxy_auth || !prefs.proxy_user[0] || !prefs.proxy_pass[0] )
+		return 1;
+
+	/* MS proxy protocol implementation currently doesn't support IPv6 */
+	destaddr = net_getsockaddr_v4 (ns_proxy);
+	if (!destaddr)
+		return 1;
+
+	state->seq_recv = 0;
+	state->seq_sent = 0;
+
+#ifdef DEBUG_MSPROXY
+	printf ("Connecting to %s:%d via MS proxy\n", serverAddr, port);
+#endif
+
+	gethostname (hostname, NT_MAXNAMELEN);
+	p = strchr (hostname, '.');
+        if (p)
+        	*p = '\0';
+
+	bzero (&req, sizeof(req));
+	req.clientid		= htonl(0x0a000000);	/* Initial client ID is always 0x0a		*/
+	req.command		= htons(MSPROXY_HELLO);	/* HELLO command					*/
+	req.packet.hello.magic5	= htons(0x4b00);	/* Fill in magic values				*/
+	req.packet.hello.magic10	= htons(0x1400);
+	req.packet.hello.magic15	= htons(0x0400);
+	req.packet.hello.magic20	= htons(0x5704);
+	req.packet.hello.magic25	= htons(0x0004);
+	req.packet.hello.magic30	= htons(0x0100);
+	req.packet.hello.magic35	= htons(0x4a02);
+	req.packet.hello.magic40	= htons(0x3000);
+	req.packet.hello.magic45	= htons(0x4400);
+	req.packet.hello.magic50	= htons(0x3900);
+	data = req.packet.hello.data;
+	strcpy (data, prefs.proxy_user);		/* Append a username				*/
+	data += strlen (prefs.proxy_user)+2;		/* +2 automatically creates second empty string	*/
+	strcpy (data, MSPROXY_EXECUTABLE);		/* Append an application name			*/
+	data += strlen (MSPROXY_EXECUTABLE)+1;
+	strcpy (data, hostname);				/* Append a hostname				*/
+	data += strlen (hostname)+1;
+
+	if (send_msprequest(sok, state, &req, data) == -1)
+		return 1;
+
+	if (recv_mspresponse(sok, state, &res) == -1)
+		return 1;
+
+	if (strcmp(res.RWSP, "RWSP") != 0) {
+#ifdef DEBUG_MSPROXY
+		printf ("Received mailformed packet (no RWSP signature)\n");
+#endif
+		return 1;
+	}
+
+	if (ntohs(res.command) >> 8 != 0x10) {
+#ifdef DEBUG_MSPROXY
+		printf ("expected res.command = 10??, is %x", ntohs(res.command));
+#endif
+		return 1;
+	}
+
+	state->clientid	= htonl(rand());
+	state->serverid	= res.serverid;
+
+#ifdef DEBUG_MSPROXY
+	printf ("clientid: 0x%x, serverid: 0x%0x\n", state->clientid, state->serverid);
+	printf ("packet #2\n");
+#endif
+
+	/* almost identical. */
+	req.clientid	= state->clientid;
+	req.serverid	= state->serverid;
+
+	if (send_msprequest(sok, state, &req, data) == -1)
+		return 1;
+
+	if (recv_mspresponse(sok, state, &res) == -1)
+		return 1;
+
+	if (res.serverid != state->serverid) {
+#ifdef DEBUG_MSPROXY
+		printf ("expected serverid = 0x%x, is 0x%x\n",state->serverid, res.serverid);
+#endif
+		return 1;
+	}
+
+	if (res.sequence != 0x01) {
+#ifdef DEBUG_MSPROXY
+		printf ("expected res.sequence = 0x01, is 0x%x\n", res.sequence);
+#endif
+		return 1;
+	}
+
+	if (ntohs(res.command) != MSPROXY_USERINFO_ACK) {
+#ifdef DEBUG_MSPROXY
+		printf ("expected res.command = 0x%x, is 0x%x\n", MSPROXY_USERINFO_ACK, ntohs(res.command));
+#endif
+		return 1;
+	}
+
+#ifdef DEBUG_MSPROXY
+	printf ("packet #3\n");
+#endif
+
+	bzero(&req, sizeof(req));
+	req.clientid			= state->clientid;
+	req.serverid			= state->serverid;
+	req.command			= htons(MSPROXY_AUTHENTICATE);
+	memcpy(req.packet.auth.NTLMSSP, "NTLMSSP", sizeof("NTLMSSP"));
+	req.packet.auth.bindaddr	= htonl(0x02000000);
+	req.packet.auth.msgtype		= htonl(0x01000000);
+	/* NTLM flags:	0x80000000 Negotiate LAN Manager key
+			0x10000000 Negotiate sign
+			0x04000000 Request target
+			0x02000000 Negotiate OEM
+			0x00800000 Always sign
+			0x00020000 Negotiate NTLM
+	*/
+	req.packet.auth.flags		= htonl(0x06020000);
+
+	if (send_msprequest(sok, state, &req, &req.packet.auth.data) == -1)
+		return 1;
+
+	if (recv_mspresponse(sok, state, &res) == -1)
+		return 1;
+
+	if (res.serverid != state->serverid) {
+#ifdef DEBUG_MSPROXY
+		printf ("expected serverid = 0x%x, is 0x%x\n", state->serverid, res.serverid);
+#endif
+		return 1;
+	}
+
+	if (ntohs(res.command) != MSPROXY_AUTHENTICATE_ACK) {
+#ifdef DEBUG_MSPROXY
+		printf ("expected res.command = 0x%x, is 0x%x\n", MSPROXY_AUTHENTICATE_ACK, ntohs(res.command));
+#endif
+		return 1;
+	}
+
+	flags = res.packet.auth.flags & htonl(0x00020000);			/* Remember if the server supports NTLM */
+	memcpy(challenge, &res.packet.auth.challenge, sizeof(challenge));
+	memcpy(ntdomain, &res.packet.auth.NTLMSSP[res.packet.auth.target.offset], res.packet.auth.target.len);
+	ntdomain[res.packet.auth.target.len] = 0;
+
+#ifdef DEBUG_MSPROXY
+	printf ("ntdomain: \"%s\"\n", ntdomain);
+	printf ("packet #4\n");
+#endif
+
+	bzero(&req, sizeof(req));
+	req.clientid			= state->clientid;
+	req.serverid			= state->serverid;
+	req.command			= htons(MSPROXY_AUTHENTICATE_2);	/* Authentication response			*/
+	req.packet.auth2.magic3		= htons(0x0200);			/* Something					*/
+	memcpy(req.packet.auth2.NTLMSSP, "NTLMSSP", sizeof("NTLMSSP"));		/* Start of NTLM message				*/
+	req.packet.auth2.msgtype		= htonl(0x03000000);			/* Message type 2				*/
+	req.packet.auth2.flags		= flags | htonl(0x02000000);		/* Choose authentication method			*/
+	data = req.packet.auth2.data;
+	if (flags) {
+		req.packet.auth2.lm_resp.len	= 0;				/* We are here if NTLM is supported,		*/
+		req.packet.auth2.lm_resp.alloc	= 0;				/* Do not fill in insecure LM response		*/
+		req.packet.auth2.lm_resp.offset	= data - req.packet.auth2.NTLMSSP;
+		req.packet.auth2.ntlm_resp.len = 24;				/* Fill in NTLM response security buffer	*/
+		req.packet.auth2.ntlm_resp.alloc = 24;
+		req.packet.auth2.ntlm_resp.offset = data - req.packet.auth2.NTLMSSP;
+		ntlm_smb_nt_encrypt(prefs.proxy_pass, challenge, data);		/* Append an NTLM response			*/
+		data += 24;	
+	} else {
+		req.packet.auth2.lm_resp.len	= 24;				/* Fill in LM response security buffer		*/
+		req.packet.auth2.lm_resp.alloc	= 24;
+		req.packet.auth2.lm_resp.offset	= data - req.packet.auth2.NTLMSSP;
+		ntlm_smb_encrypt(prefs.proxy_pass, challenge, data);		/* Append an LM response			*/
+		data += 24;
+		req.packet.auth2.ntlm_resp.len = 0;				/* NTLM response is empty			*/
+		req.packet.auth2.ntlm_resp.alloc = 0;
+		req.packet.auth2.ntlm_resp.offset = data - req.packet.auth2.NTLMSSP;
+	}
+	req.packet.auth2.ntdomain_buf.len = strlen(ntdomain);			/* Domain name					*/
+	req.packet.auth2.ntdomain_buf.alloc = req.packet.auth2.ntdomain_buf.len;
+	req.packet.auth2.ntdomain_buf.offset = data - req.packet.auth2.NTLMSSP;
+	strcpy(data, ntdomain);
+	data += req.packet.auth2.ntdomain_buf.len;
+	req.packet.auth2.username_buf.len = strlen(prefs.proxy_user);		/* Username					*/
+	req.packet.auth2.username_buf.alloc = req.packet.auth2.username_buf.len;
+	req.packet.auth2.username_buf.offset = data - req.packet.auth2.NTLMSSP;
+	strcpy(data, prefs.proxy_user);
+	data += req.packet.auth2.username_buf.len;
+	req.packet.auth2.clienthost_buf.len = strlen(hostname);			/* Hostname					*/
+	req.packet.auth2.clienthost_buf.alloc = req.packet.auth2.clienthost_buf.len;
+	req.packet.auth2.clienthost_buf.offset = data - req.packet.auth2.NTLMSSP;
+	strcpy(data, hostname);
+	data += req.packet.auth2.clienthost_buf.len;
+	req.packet.auth2.sessionkey_buf.len = 0;				/* Session key (we don't use it)		*/
+	req.packet.auth2.sessionkey_buf.alloc = 0;
+	req.packet.auth2.sessionkey_buf.offset = data - req.packet.auth2.NTLMSSP;
+
+	if (send_msprequest(sok, state, &req, data) == -1)
+		return 1;
+
+	if (recv_mspresponse(sok, state, &res) == -1)
+		return 1;
+
+	if (res.serverid != state->serverid) {
+#ifdef DEBUG_MSPROXY
+		printf ("expected res.serverid = 0x%x, is 0x%x\n", state->serverid, res.serverid);
+#endif
+		return 1;
+	}
+
+	if (res.clientack != 0x01) {
+#ifdef DEBUG_MSPROXY
+		printf ("expected res.clientack = 0x01, is 0x%x\n", res.clientack);
+#endif
+		return 1;
+	}
+
+	if (ntohs(res.command) >> 8 != 0x47) {
+#ifdef DEBUG_MSPROXY
+		printf ("expected res.command = 47??, is 0x%x\n", ntohs(res.command));
+#endif
+		return 1;
+	}
+
+	if (ntohs(res.command) ==  MSPROXY_AUTHENTICATE_2_NAK) {
+#ifdef DEBUG_MSPROXY
+		printf ("Authentication failed\n");
+#endif
+		return -1;
+	}
+
+#ifdef DEBUG_MSPROXY
+	printf ("packet #5\n");
+#endif
+
+	bzero(&req, sizeof(req));
+	req.clientid			= state->clientid;
+	req.serverid			= state->serverid;
+	req.command			= htons(MSPROXY_CONNECT);
+	req.packet.connect.magic2	= htons(0x0200);
+	req.packet.connect.magic6	= htons(0x0200);
+	req.packet.connect.destport	= htons(port);
+	req.packet.connect.destaddr	= destaddr;
+	data = req.packet.connect.executable;
+	strcpy(data, MSPROXY_EXECUTABLE);
+	data += strlen(MSPROXY_EXECUTABLE) + 1;
+
+	/*
+	 * need to tell server what port we will connect from, so we bind our sockets.
+	 */
+	ns_client = net_store_new ();
+	if (!bound) {
+		net_store_fill_any (ns_client);
+		net_bind(ns_client, csok4, csok6);
+#ifdef DEBUG_MSPROXY
+		perror ("bind() result");
+#endif
+	}
+ 	clientport = net_getsockport(csok4, csok6);
+	if (clientport == -1) {
+#ifdef DEBUG_MSPROXY
+		printf ("Unable to obtain source port\n");
+#endif
+		return 1;
+	}
+	req.packet.connect.srcport = clientport;
+
+	if (send_msprequest(sok, state, &req, data) == -1)
+		return 1;
+
+	if (recv_mspresponse(sok, state, &res) == -1)
+		return 1;
+
+	if (ntohs(res.command) != MSPROXY_CONNECT_ACK) {
+#ifdef DEBUG_MSPROXY
+		printf ("expected res.command = 0x%x, is 0x%x\n",MSPROXY_CONNECT_ACK, ntohs(res.command));
+#endif
+		return 1;
+	}
+
+	net_store_fill_v4 (ns_client, res.packet.connect.clientaddr, res.packet.connect.clientport);
+
+#ifdef DEBUG_MSPROXY
+	printf ("Connecting...\n");
+#endif
+	if (net_connect (ns_client, csok4, csok6, csok) != 0) {
+#ifdef DEBUG_MSPROXY
+		printf ("Failed to connect to port %d\n", htons(res.packet.connect.clientport));
+#endif
+		net_store_destroy (ns_client);
+		return 1;
+	}
+	net_store_destroy (ns_client);
+#ifdef DEBUG_MSPROXY
+	printf ("packet #6\n");
+#endif
+
+	req.clientid	= state->clientid;
+	req.serverid	= state->serverid;
+	req.command	= htons(MSPROXY_USERINFO_ACK);		
+
+	if (send_msprequest(sok, state, &req, req.packet.connack.data) == -1)
+		return 1;
+
+	return 0;
+}
+
+void
+msproxy_keepalive (void)
+{
+	server *serv;
+	GSList *list = serv_list;
+	struct msproxy_request_t req;
+	struct msproxy_response_t res;
+
+	while (list)
+	{
+		serv = list->data;
+		if (serv->connected && (serv->proxy_sok != -1))
+		{
+#ifdef DEBUG_MSPROXY
+			printf ("sending MS proxy keepalive packet\n");
+#endif
+
+			bzero(&req, sizeof(req));
+			req.clientid	= serv->msp_state.clientid;
+			req.serverid	= serv->msp_state.serverid;
+			req.command	= htons(MSPROXY_HELLO);
+			
+			if (send_msprequest(serv->proxy_sok, &serv->msp_state, &req, req.packet.hello.data) == -1)
+				continue;
+
+			recv_mspresponse(serv->proxy_sok, &serv->msp_state, &res);
+
+#ifdef DEBUG_MSPROXY
+			if (ntohs(res.command) != MSPROXY_USERINFO_ACK)
+				printf ("expected res.command = 0x%x, is 0x%x\n", MSPROXY_USERINFO_ACK, ntohs(res.command));
+#endif
+		}
+		list = list->next;
+	}
+}
+
+#endif
diff --git a/src/common/msproxy.h b/src/common/msproxy.h
new file mode 100644
index 00000000..d37c81c5
--- /dev/null
+++ b/src/common/msproxy.h
@@ -0,0 +1,257 @@
+/* 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
+ *
+ * MS Proxy (ISA server) support is (c) 2006 Pavel Fedin <sonic_amiga@rambler.ru>
+ * based on Dante source code
+ * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006
+ *      Inferno Nettverk A/S, Norway.  All rights reserved.
+ */
+
+#include "network.h"
+
+#define MSPROXY_EXECUTABLE 		"xchat.exe"	/* This probably can be used for access control on the server side */
+
+#define MSPROXY_MINLENGTH		172		/* minimum length of packet.				*/
+#define NT_MAXNAMELEN			17		/* maximum name length (domain etc), comes from NetBIOS */
+#define MSPROXY_VERSION			0x00010200	/* MS Proxy v2 ?					*/
+
+/* Commands / responses */
+#define MSPROXY_HELLO			0x0500	/* packet 1 from client.			*/
+#define MSPROXY_HELLO_ACK		0x1000	/* packet 1 from server.			*/
+
+#define MSPROXY_USERINFO_ACK		0x0400	/* packet 2 from server.			*/
+
+#define MSPROXY_AUTHENTICATE		0x4700	/* authentication request		*/
+#define MSPROXY_AUTHENTICATE_ACK	0x4714	/* authentication challenge		*/
+
+#define MSPROXY_AUTHENTICATE_2		0x4701	/* authentication response		*/
+#define MSPROXY_AUTHENTICATE_2_ACK	0x4715	/* authentication passed		*/
+#define MSPROXY_AUTHENTICATE_2_NAK	0x4716	/* authentication failure		*/
+
+#define MSPROXY_CONNECT			0x071e	/* connect request.			*/
+#define MSPROXY_CONNECT_ACK		0x0703	/* connect request accepted.		*/
+
+#pragma pack(1)
+
+struct ntlm_buffer {
+	guint16	len;
+	guint16	alloc;
+	guint32	offset;
+};
+
+struct msproxy_request_t {
+	guint32					clientid;			/* 1-4							*/
+	guint32					magic25;				/* 5-8							*/
+	guint32					serverid;			/* 9-12							*/
+	unsigned char				serverack;			/* 13: ack of last server packet			*/
+	char					pad10[3];			/* 14-16						*/
+	unsigned char				sequence;			/* 17: sequence # of this packet.			*/
+	char					pad11[7];			/* 18-24						*/
+	char					RWSP[4];			/* 25-28: 0x52,0x57,0x53,0x50				*/
+	char					pad15[8];			/* 29-36						*/
+	guint16					command;				/* 37-38						*/
+
+	/* packet specifics start at 39. */
+	union {
+		struct {
+			char			pad1[18];			/* 39-56						*/
+			guint16			magic3;				/* 57-58						*/
+			char           		pad3[114];			/* 59-172						*/
+			guint16			magic5;				/* 173-174: 0x4b, 0x00					*/
+			char			pad5[2];			/* 175-176						*/
+			guint16			magic10;				/* 177-178: 0x14, 0x00					*/
+			char			pad6[2];			/* 179-180						*/
+			guint16			magic15;				/* 181-182: 0x04, 0x00					*/
+			char			pad10[2];			/* 183-184						*/
+			guint16			magic16;				/* 185-186						*/
+			char			pad11[2];			/* 187-188						*/
+			guint16			magic20;				/* 189-190: 0x57, 0x04					*/
+			guint16			magic25;				/* 191-192: 0x00, 0x04					*/
+			guint16			magic30;				/* 193-194: 0x01, 0x00					*/
+			char			pad20[2];			/* 195-196: 0x4a, 0x02					*/
+			guint16			magic35;				/* 197-198: 0x4a, 0x02					*/
+			char			pad30[10];			/* 199-208						*/
+			guint16			magic40;				/* 209-210: 0x30, 0x00					*/
+			char			pad40[2];			/* 211-212						*/
+			guint16			magic45;				/* 213-214: 0x44, 0x00					*/
+			char			pad45[2];			/* 215-216						*/
+			guint16			magic50;				/* 217-218: 0x39, 0x00					*/
+			char			pad50[2];			/* 219-220						*/
+			char			data[256];			/* 221-EOP: a sequence of NULL-terminated strings:
+											- username;
+											- empty string (just a NULL);
+											- application name;
+											- hostname					*/
+		} hello;
+
+		struct {
+			char			pad1[4];			/* 39-42						*/
+			guint16			magic2;				/* 43-44						*/
+			char			pad10[12];			/* 45-56						*/
+			guint32			bindaddr;			/* 57-60: address to bind.				*/
+			guint16			bindport;			/* 61-62: port to bind.					*/
+			char           		pad15[2];			/* 63-64						*/
+			guint16			magic3;				/* 65-66						*/
+			guint16			boundport;			/* 67-68						*/
+			char           		pad20[104];			/* 69-172						*/
+			char			NTLMSSP[sizeof("NTLMSSP")];	/* 173-180: "NTLMSSP"					*/
+			guint32			msgtype;				/* 181-184: NTLM message type = 1			*/
+			guint32			flags;				/* 185-188: NTLM message flags				*/
+			guint16			magic20;				/* 189-190: 0x28, 0x00					*/
+			char			pad30[2];			/* 191-192						*/
+			guint16			magic25;				/* 193-194: 0x96, 0x82					*/
+			guint16			magic30;				/* 195-196: 0x01, 0x00					*/
+			char			pad40[12];			/* 197-208						*/
+			guint16			magic50;				/* 209-210: 0x30, 0x00					*/
+			char			pad50[6];			/* 211-216						*/
+			guint16			magic55;				/* 217-218: 0x30, 0x00					*/
+			char			pad55[2];			/* 219-220						*/
+			char			data[0];			/* Dummy end marker, no real data required		*/
+		} auth;
+
+		struct {
+			char			pad1[4];			/* 39-42						*/
+			guint16			magic1;				/* 43-44						*/
+			guint32			magic2;				/* 45-48						*/
+			char			pad2[8];			/* 49-56						*/
+			guint16			magic3;				/* 57-58						*/
+			char			pad3[6];			/* 59-64						*/
+			guint16			magic4;				/* 65-66						*/
+			guint16			boundport;			/* 67-68						*/
+			char           		pad4[104];			/* 69-172						*/
+			char			NTLMSSP[sizeof("NTLMSSP")];	/* 173-180: "NTLMSSP"					*/
+			guint32			msgtype;				/* 181-184: NTLM message type = 3			*/
+			struct ntlm_buffer	lm_resp;				/* 185-192: LM response security buffer			*/
+			struct ntlm_buffer	ntlm_resp;			/* 193-200: NTLM response security buffer		*/
+			struct ntlm_buffer	ntdomain_buf;			/* 201-208: domain name security buffer			*/
+			struct ntlm_buffer	username_buf;			/* 209-216: username security buffer			*/
+			struct ntlm_buffer	clienthost_buf;			/* 217-224: hostname security buffer			*/
+			struct ntlm_buffer	sessionkey_buf;			/* 225-232: session key security buffer			*/
+			guint32			flags;				/* 233-236: message flags				*/
+			char			data[1024];			/* 237-EOP: data area					*/
+		} auth2;
+
+		struct {
+			guint16			magic1;				/* 39-40						*/
+			char			pad1[2];			/* 41-42						*/
+			guint16			magic2;				/* 43-44						*/
+			guint32			magic3;				/* 45-48						*/
+			char			pad5[8];			/* 48-56						*/
+			guint16			magic6;				/* 57-58: 0x0200					*/
+			guint16			destport;			/* 59-60						*/
+			guint32			destaddr;			/* 61-64						*/
+			char			pad10[4];			/* 65-68						*/
+			guint16			magic10;				/* 69-70						*/
+			char			pad15[2];			/* 71-72						*/
+			guint16			srcport;			/* 73-74: port client connects from			*/
+			char			pad20[82];			/* 75-156						*/
+			char			executable[256];		/* 76-EOP: application name				*/
+		} connect;
+
+		struct {
+			guint16			magic1;				/* 39-40						*/
+			char			pad5[2];			/* 41-42						*/
+			guint16			magic5;				/* 43-44						*/
+			guint32			magic10;				/* 45-48						*/
+			char			pad10[2];			/* 49-50						*/
+			guint16			magic15;				/* 51-52						*/
+			guint32			magic16;				/* 53-56						*/
+			guint16			magic20;				/* 57-58						*/
+			guint16			clientport;			/* 59-60: forwarded port.				*/
+			guint32			clientaddr;			/* 61-64: forwarded address.				*/
+			guint32			magic30;				/* 65-68						*/
+			guint32			magic35;				/* 69-72						*/
+			guint16			serverport;			/* 73-74: port server will connect to us from.		*/
+			guint16			srcport;			/* 75-76: connect request; port used on client behalf.	*/
+			guint16			boundport;			/* 77-78: bind request; port used on client behalf.	*/
+			guint32			boundaddr;			/* 79-82: addr used on client behalf			*/
+			char			pad30[90];			/* 83-172						*/
+			char			data[0];			/* End marker						*/
+		} connack;
+
+	} packet;
+};
+
+struct msproxy_response_t {
+	guint32					packetid;			/* 1-4							*/
+	guint32					magic5;				/* 5-8							*/
+	guint32             			serverid;			/* 9-12							*/
+	char					clientack;			/* 13: ack of last client packet.			*/
+	char					pad5[3];			/* 14-16						*/
+	unsigned char				sequence;			/* 17: sequence # of this packet.			*/
+	char					pad10[7];			/* 18-24						*/
+	char					RWSP[4];			/* 25-28: 0x52,0x57,0x53,0x50				*/
+	char					pad15[8];			/* 29-36						*/
+	guint16					command;				/* 37-38						*/
+
+	union {
+		struct {
+			char			pad5[18];			/* 39-56						*/
+			guint16			magic20;				/* 57-58: 0x02, 0x00					*/
+			char			pad10[6];			/* 59-64						*/
+			guint16			magic30;				/* 65-66: 0x74, 0x01					*/
+			char			pad15[2];			/* 67-68						*/
+			guint16			magic35;				/* 69-70: 0x0c, 0x00					*/
+			char			pad20[6];			/* 71-76						*/
+			guint16			magic50;				/* 77-78: 0x04, 0x00					*/
+			char			pad30[6];			/* 79-84						*/
+			guint16			magic60;				/* 85-86: 0x65, 0x05					*/
+			char			pad35[2];			/* 87-88						*/
+			guint16			magic65;				/* 89-90: 0x02, 0x00					*/
+			char			pad40[8];			/* 91-98						*/
+			guint16			udpport;			/* 99-100						*/
+			guint32			udpaddr;			/* 101-104						*/
+		} hello;
+
+		struct {
+			char			pad1[6];			/* 39-44						*/
+			guint32			magic10;				/* 45-48						*/
+			char			pad3[10];			/* 49-58						*/
+			guint16			boundport;			/* 59-60: port server bound for us.			*/
+			guint32			boundaddr;			/* 61-64: addr server bound for us.			*/
+			char			pad10[4];			/* 65-68						*/
+			guint16			magic15;				/* 69-70						*/
+			char			pad15[102];			/* 70-172						*/
+			char			NTLMSSP[sizeof("NTLMSSP")];	/* 173-180: "NTLMSSP"					*/
+			guint32			msgtype;				/* 181-184: NTLM message type = 2			*/
+			struct ntlm_buffer	target;				/* 185-192: target security buffer			*/
+			guint32			flags;				/* 193-196: NTLM message flags				*/
+			char			challenge[8];			/* 197-204: NTLM challenge request			*/
+			char			context[8];			/* 205-212: NTLM context				*/
+			char			data[1024];			/* 213-EOP: target information data			*/
+		} auth;
+
+		struct {
+			guint16			magic1;				/* 39-40						*/
+			char			pad5[18];			/* 41-58						*/
+			guint16			clientport;			/* 59-60: forwarded port.				*/
+			guint32			clientaddr;			/* 61-64: forwarded address.				*/
+			guint32			magic10;				/* 65-68						*/
+			guint32			magic15;				/* 69-72						*/
+			guint16			serverport;			/* 73-74: port server will connect to us from.		*/
+			guint16			srcport;			/* 75-76: connect request; port used on client behalf.	*/
+			guint16			boundport;			/* 77-78: bind request; port used on client behalf.	*/
+			guint32			boundaddr;			/* 79-82: addr used on client behalf			*/
+			char			pad10[90];			/* 83-172						*/
+		} connect;
+	} packet;
+};
+
+#pragma pack()
+
+int traverse_msproxy (int sok, char *serverAddr, int port, struct msproxy_state_t *state, netstore *ns_proxy, int csok4, int csok6, int *csok, char bound);
+void msproxy_keepalive (void);
diff --git a/src/common/network.c b/src/common/network.c
new file mode 100644
index 00000000..0c409506
--- /dev/null
+++ b/src/common/network.c
@@ -0,0 +1,383 @@
+/* X-Chat
+ * Copyright (C) 2001 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
+ */
+
+/* ipv4 and ipv6 networking functions with a common interface */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <glib.h>
+
+#include "../../config.h"				  /* grab USE_IPV6 and LOOKUPD defines */
+
+#define WANTSOCKET
+#define WANTARPA
+#define WANTDNS
+#include "inet.h"
+
+#define NETWORK_PRIVATE
+#include "network.h"
+
+#define RAND_INT(n) ((int)(rand() / (RAND_MAX + 1.0) * (n)))
+
+
+/* ================== COMMON ================= */
+
+static void
+net_set_socket_options (int sok)
+{
+	socklen_t sw;
+
+	sw = 1;
+	setsockopt (sok, SOL_SOCKET, SO_REUSEADDR, (char *) &sw, sizeof (sw));
+	sw = 1;
+	setsockopt (sok, SOL_SOCKET, SO_KEEPALIVE, (char *) &sw, sizeof (sw));
+}
+
+char *
+net_ip (guint32 addr)
+{
+	struct in_addr ia;
+
+	ia.s_addr = htonl (addr);
+	return inet_ntoa (ia);
+}
+
+void
+net_store_destroy (netstore * ns)
+{
+#ifdef USE_IPV6
+	if (ns->ip6_hostent)
+		freeaddrinfo (ns->ip6_hostent);
+#endif
+	free (ns);
+}
+
+netstore *
+net_store_new (void)
+{
+	netstore *ns;
+
+	ns = malloc (sizeof (netstore));
+	memset (ns, 0, sizeof (netstore));
+
+	return ns;
+}
+
+#ifndef USE_IPV6
+
+/* =================== IPV4 ================== */
+
+/*
+	A note about net_resolve and lookupd:
+
+	Many IRC networks rely on round-robin DNS for load balancing, rotating the list
+	of IP address on each query. However, this method breaks when DNS queries are
+	cached. Mac OS X and Darwin handle DNS lookups through the lookupd daemon, which
+	caches queries in its default configuration: thus, if we always pick the first
+	address, we will be stuck with the same host (which might be down!) until the
+	TTL reaches 0 or lookupd is reset (typically, at reboot). Therefore, we need to
+	pick a random address from the result list, instead of always using the first.
+*/
+
+char *
+net_resolve (netstore * ns, char *hostname, int port, char **real_host)
+{
+	ns->ip4_hostent = gethostbyname (hostname);
+	if (!ns->ip4_hostent)
+		return NULL;
+
+	memset (&ns->addr, 0, sizeof (ns->addr));
+#ifdef LOOKUPD
+	int count = 0;
+	while (ns->ip4_hostent->h_addr_list[count]) count++;
+	memcpy (&ns->addr.sin_addr,
+			ns->ip4_hostent->h_addr_list[RAND_INT(count)],
+			ns->ip4_hostent->h_length);
+#else
+	memcpy (&ns->addr.sin_addr, ns->ip4_hostent->h_addr,
+			  ns->ip4_hostent->h_length);
+#endif
+	ns->addr.sin_port = htons (port);
+	ns->addr.sin_family = AF_INET;
+
+	*real_host = strdup (ns->ip4_hostent->h_name);
+	return strdup (inet_ntoa (ns->addr.sin_addr));
+}
+
+int
+net_connect (netstore * ns, int sok4, int sok6, int *sok_return)
+{
+	*sok_return = sok4;
+	return connect (sok4, (struct sockaddr *) &ns->addr, sizeof (ns->addr));
+}
+
+void
+net_bind (netstore * tobindto, int sok4, int sok6)
+{
+	bind (sok4, (struct sockaddr *) &tobindto->addr, sizeof (tobindto->addr));
+}
+
+void
+net_sockets (int *sok4, int *sok6)
+{
+	*sok4 = socket (AF_INET, SOCK_STREAM, 0);
+	*sok6 = -1;
+	net_set_socket_options (*sok4);
+}
+
+void
+udp_sockets (int *sok4, int *sok6)
+{
+	*sok4 = socket (AF_INET, SOCK_DGRAM, 0);
+	*sok6 = -1;
+}
+
+void
+net_store_fill_any (netstore *ns)
+{
+	ns->addr.sin_family = AF_INET;
+	ns->addr.sin_addr.s_addr = INADDR_ANY;
+	ns->addr.sin_port = 0;
+}
+
+void
+net_store_fill_v4 (netstore *ns, guint32 addr, int port)
+{
+	ns->addr.sin_family = AF_INET;
+	ns->addr.sin_addr.s_addr = addr;
+	ns->addr.sin_port = port;
+}
+
+guint32
+net_getsockaddr_v4 (netstore *ns)
+{
+	return ns->addr.sin_addr.s_addr;
+}
+
+int
+net_getsockport (int sok4, int sok6)
+{
+	struct sockaddr_in addr;
+	int len = sizeof (addr);
+
+	if (getsockname (sok4, (struct sockaddr *)&addr, &len) == -1)
+		return -1;
+	return addr.sin_port;
+}
+
+#else
+
+/* =================== IPV6 ================== */
+
+char *
+net_resolve (netstore * ns, char *hostname, int port, char **real_host)
+{
+	struct addrinfo hints;
+	char ipstring[MAX_HOSTNAME];
+	char portstring[MAX_HOSTNAME];
+	int ret;
+
+/*	if (ns->ip6_hostent)
+		freeaddrinfo (ns->ip6_hostent);*/
+
+	sprintf (portstring, "%d", port);
+
+	memset (&hints, 0, sizeof (struct addrinfo));
+	hints.ai_family = PF_UNSPEC; /* support ipv6 and ipv4 */
+	hints.ai_flags = AI_CANONNAME;
+	hints.ai_socktype = SOCK_STREAM;
+
+	if (port == 0)
+		ret = getaddrinfo (hostname, NULL, &hints, &ns->ip6_hostent);
+	else
+		ret = getaddrinfo (hostname, portstring, &hints, &ns->ip6_hostent);
+	if (ret != 0)
+		return NULL;
+
+#ifdef LOOKUPD	/* See note about lookupd above the IPv4 version of net_resolve. */
+	struct addrinfo *tmp;
+	int count = 0;
+
+	for (tmp = ns->ip6_hostent; tmp; tmp = tmp->ai_next)
+		count ++;
+
+	count = RAND_INT(count);
+	
+	while (count--) ns->ip6_hostent = ns->ip6_hostent->ai_next;
+#endif
+
+	/* find the numeric IP number */
+	ipstring[0] = 0;
+	getnameinfo (ns->ip6_hostent->ai_addr, ns->ip6_hostent->ai_addrlen,
+					 ipstring, sizeof (ipstring), NULL, 0, NI_NUMERICHOST);
+
+	if (ns->ip6_hostent->ai_canonname)
+		*real_host = strdup (ns->ip6_hostent->ai_canonname);
+	else
+		*real_host = strdup (hostname);
+
+	return strdup (ipstring);
+}
+
+/* the only thing making this interface unclean, this shitty sok4, sok6 business */
+
+int
+net_connect (netstore * ns, int sok4, int sok6, int *sok_return)
+{
+	struct addrinfo *res, *res0;
+	int error = -1;
+
+	res0 = ns->ip6_hostent;
+
+	for (res = res0; res; res = res->ai_next)
+	{
+/*		sok = socket (res->ai_family, res->ai_socktype, res->ai_protocol);
+		if (sok < 0)
+			continue;*/
+		switch (res->ai_family)
+		{
+		case AF_INET:
+			error = connect (sok4, res->ai_addr, res->ai_addrlen);
+			*sok_return = sok4;
+			break;
+		case AF_INET6:
+			error = connect (sok6, res->ai_addr, res->ai_addrlen);
+			*sok_return = sok6;
+			break;
+		default:
+			error = 1;
+		}
+
+		if (error == 0)
+			break;
+	}
+
+	return error;
+}
+
+void
+net_bind (netstore * tobindto, int sok4, int sok6)
+{
+	bind (sok4, tobindto->ip6_hostent->ai_addr,
+			tobindto->ip6_hostent->ai_addrlen);
+	bind (sok6, tobindto->ip6_hostent->ai_addr,
+			tobindto->ip6_hostent->ai_addrlen);
+}
+
+void
+net_sockets (int *sok4, int *sok6)
+{
+	*sok4 = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
+	*sok6 = socket (AF_INET6, SOCK_STREAM, IPPROTO_TCP);
+	net_set_socket_options (*sok4);
+	net_set_socket_options (*sok6);
+}
+
+void
+udp_sockets (int *sok4, int *sok6)
+{
+	*sok4 = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+	*sok6 = socket (AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+}
+
+/* the following functions are used only by MSPROXY and are not
+   proper ipv6 implementations - do not use in new code! */
+
+void
+net_store_fill_any (netstore *ns)
+{
+	struct addrinfo *ai;
+	struct sockaddr_in *sin;
+
+	ai = ns->ip6_hostent;
+	if (!ai) {
+		ai = malloc (sizeof (struct addrinfo));
+		memset (ai, 0, sizeof (struct addrinfo));
+		ns->ip6_hostent = ai;
+	}
+	sin = (struct sockaddr_in *)ai->ai_addr;
+	if (!sin) {
+		sin = malloc (sizeof (struct sockaddr_in));
+		memset (sin, 0, sizeof (struct sockaddr_in));
+		ai->ai_addr = (struct sockaddr *)sin;
+	}
+	ai->ai_family = AF_INET;
+	ai->ai_addrlen = sizeof(struct sockaddr_in);
+	sin->sin_family = AF_INET;
+	sin->sin_addr.s_addr = INADDR_ANY;
+	sin->sin_port = 0;
+	ai->ai_next = NULL;
+}
+
+void
+net_store_fill_v4 (netstore *ns, guint32 addr, int port)
+{
+	struct addrinfo *ai;
+	struct sockaddr_in *sin;
+
+	ai = ns->ip6_hostent;
+	if (!ai) {
+		ai = malloc (sizeof (struct addrinfo));
+		memset (ai, 0, sizeof (struct addrinfo));
+		ns->ip6_hostent = ai;
+	}
+	sin = (struct sockaddr_in *)ai->ai_addr;
+	if (!sin) {
+		sin = malloc (sizeof (struct sockaddr_in));
+		memset (sin, 0, sizeof (struct sockaddr_in));
+		ai->ai_addr = (struct sockaddr *)sin;
+	}
+	ai->ai_family = AF_INET;
+	ai->ai_addrlen = sizeof(struct sockaddr_in);
+	sin->sin_family = AF_INET;
+	sin->sin_addr.s_addr = addr;
+	sin->sin_port = port;
+	ai->ai_next = NULL;
+}
+
+guint32
+net_getsockaddr_v4 (netstore *ns)
+{
+	struct addrinfo *ai;
+	struct sockaddr_in *sin;
+
+	ai = ns->ip6_hostent;
+
+	while (ai->ai_family != AF_INET) {
+		ai = ai->ai_next;
+		if (!ai)
+			return 0;
+	}
+	sin = (struct sockaddr_in *)ai->ai_addr;
+	return sin->sin_addr.s_addr;
+}
+
+int
+net_getsockport (int sok4, int sok6)
+{
+	struct sockaddr_in addr;
+	int len = sizeof (addr);
+
+	if (getsockname (sok4, (struct sockaddr *)&addr, &len) == -1)
+		return -1;
+	return addr.sin_port;
+}
+
+#endif
diff --git a/src/common/network.h b/src/common/network.h
new file mode 100644
index 00000000..f45f210a
--- /dev/null
+++ b/src/common/network.h
@@ -0,0 +1,34 @@
+#ifndef XCHAT_NETWORK_H
+#define XCHAT_NETWORK_H
+
+typedef struct netstore_
+{
+#ifdef NETWORK_PRIVATE
+#ifdef USE_IPV6
+	struct addrinfo *ip6_hostent;
+#else
+	struct hostent *ip4_hostent;
+	struct sockaddr_in addr;
+#endif
+#else
+	int _dummy;	/* some compilers don't like empty structs */
+#endif
+} netstore;
+
+#define MAX_HOSTNAME 128
+
+netstore *net_store_new (void);
+void net_store_destroy (netstore *ns);
+int net_connect (netstore *ns, int sok4, int sok6, int *sok_return);
+char *net_resolve (netstore *ns, char *hostname, int port, char **real_host);
+void net_bind (netstore *tobindto, int sok4, int sok6);
+char *net_ip (guint32 addr);
+void net_sockets (int *sok4, int *sok6);
+/* functions for MSPROXY only! */
+void udp_sockets (int *sok4, int *sok6);
+void net_store_fill_any (netstore *ns);
+void net_store_fill_v4 (netstore *ns, guint32 addr, int port);
+guint32 net_getsockaddr_v4 (netstore *ns);
+int net_getsockport(int sok4, int sok6);
+
+#endif
diff --git a/src/common/notify.c b/src/common/notify.c
new file mode 100644
index 00000000..04795849
--- /dev/null
+++ b/src/common/notify.c
@@ -0,0 +1,634 @@
+/* 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 <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+
+#include "xchat.h"
+#include "notify.h"
+#include "cfgfiles.h"
+#include "fe.h"
+#include "server.h"
+#include "text.h"
+#include "util.h"
+#include "xchatc.h"
+
+
+GSList *notify_list = 0;
+int notify_tag = 0;
+
+
+static char *
+despacify_dup (char *str)
+{
+	char *p, *res = malloc (strlen (str) + 1);
+
+	p = res;
+	while (1)
+	{
+		if (*str != ' ')
+		{
+			*p = *str;
+			if (*p == 0)
+				return res;
+			p++;
+		}
+		str++;
+	}
+}
+
+static int
+notify_netcmp (char *str, void *serv)
+{
+	char *net = despacify_dup (server_get_network (serv, TRUE));
+
+	if (rfc_casecmp (str, net) == 0)
+	{
+		free (net);
+		return 0;	/* finish & return FALSE from token_foreach() */
+	}
+
+	free (net);
+	return 1;	/* keep going... */
+}
+
+/* monitor this nick on this particular network? */
+
+static gboolean
+notify_do_network (struct notify *notify, server *serv)
+{
+	if (!notify->networks)	/* ALL networks for this nick */
+		return TRUE;
+
+	if (token_foreach (notify->networks, ',', notify_netcmp, serv))
+		return FALSE;	/* network list doesn't contain this one */
+
+	return TRUE;
+}
+
+struct notify_per_server *
+notify_find_server_entry (struct notify *notify, struct server *serv)
+{
+	GSList *list = notify->server_list;
+	struct notify_per_server *servnot;
+
+	while (list)
+	{
+		servnot = (struct notify_per_server *) list->data;
+		if (servnot->server == serv)
+			return servnot;
+		list = list->next;
+	}
+
+	/* not found, should we add it, or is this not a network where
+      we're monitoring this nick? */
+	if (!notify_do_network (notify, serv))
+		return NULL;
+
+	servnot = malloc (sizeof (struct notify_per_server));
+	if (servnot)
+	{
+		memset (servnot, 0, sizeof (struct notify_per_server));
+		servnot->server = serv;
+		servnot->notify = notify;
+		notify->server_list = g_slist_prepend (notify->server_list, servnot);
+	}
+	return servnot;
+}
+
+void
+notify_save (void)
+{
+	int fh;
+	struct notify *notify;
+	GSList *list = notify_list;
+
+	fh = xchat_open_file ("notify.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE);
+	if (fh != -1)
+	{
+		while (list)
+		{
+			notify = (struct notify *) list->data;
+			write (fh, notify->name, strlen (notify->name));
+			if (notify->networks)
+			{
+				write (fh, " ", 1);
+				write (fh, notify->networks, strlen (notify->networks));
+			}
+			write (fh, "\n", 1);
+			list = list->next;
+		}
+		close (fh);
+	}
+}
+
+void
+notify_load (void)
+{
+	int fh;
+	char buf[256];
+	char *sep;
+
+	fh = xchat_open_file ("notify.conf", O_RDONLY, 0, 0);
+	if (fh != -1)
+	{
+		while (waitline (fh, buf, sizeof buf, FALSE) != -1)
+		{
+			if (buf[0] != '#' && buf[0] != 0)
+			{
+				sep = strchr (buf, ' ');
+				if (sep)
+				{
+					sep[0] = 0;
+					notify_adduser (buf, sep + 1);					
+				}
+				else
+					notify_adduser (buf, NULL);
+			}
+		}
+		close (fh);
+	}
+}
+
+static struct notify_per_server *
+notify_find (server *serv, char *nick)
+{
+	GSList *list = notify_list;
+	struct notify_per_server *servnot;
+	struct notify *notify;
+
+	while (list)
+	{
+		notify = (struct notify *) list->data;
+
+		servnot = notify_find_server_entry (notify, serv);
+		if (!servnot)
+		{
+			list = list->next;
+			continue;
+		}
+
+		if (!serv->p_cmp (notify->name, nick))
+			return servnot;
+
+		list = list->next;
+	}
+
+	return 0;
+}
+
+static void
+notify_announce_offline (server * serv, struct notify_per_server *servnot,
+								char *nick, int quiet)
+{
+	session *sess;
+
+	sess = serv->front_session;
+
+	servnot->ison = FALSE;
+	servnot->lastoff = time (0);
+	if (!quiet)
+		EMIT_SIGNAL (XP_TE_NOTIFYOFFLINE, sess, nick, serv->servername,
+						 server_get_network (serv, TRUE), NULL, 0);
+	fe_notify_update (nick);
+	fe_notify_update (0);
+}
+
+static void
+notify_announce_online (server * serv, struct notify_per_server *servnot,
+								char *nick)
+{
+	session *sess;
+
+	sess = serv->front_session;
+
+	servnot->lastseen = time (0);
+	if (servnot->ison)
+		return;
+
+	servnot->ison = TRUE;
+	servnot->laston = time (0);
+	EMIT_SIGNAL (XP_TE_NOTIFYONLINE, sess, nick, serv->servername,
+					 server_get_network (serv, TRUE), NULL, 0);
+	fe_notify_update (nick);
+	fe_notify_update (0);
+
+	if (prefs.whois_on_notifyonline)
+	{
+
+	    /* Let's do whois with idle time (like in /quote WHOIS %s %s) */
+
+	    char *wii_str = malloc (strlen (nick) * 2 + 2);
+	    sprintf (wii_str, "%s %s", nick, nick);
+	    serv->p_whois (serv, wii_str);
+	    free (wii_str);
+	}
+}
+
+/* handles numeric 601 */
+
+void
+notify_set_offline (server * serv, char *nick, int quiet)
+{
+	struct notify_per_server *servnot;
+
+	servnot = notify_find (serv, nick);
+	if (!servnot)
+		return;
+
+	notify_announce_offline (serv, servnot, nick, quiet);
+}
+
+/* handles numeric 604 and 600 */
+
+void
+notify_set_online (server * serv, char *nick)
+{
+	struct notify_per_server *servnot;
+
+	servnot = notify_find (serv, nick);
+	if (!servnot)
+		return;
+
+	notify_announce_online (serv, servnot, nick);
+}
+
+static void
+notify_watch (server * serv, char *nick, int add)
+{
+	char tbuf[256];
+
+	snprintf (tbuf, sizeof (tbuf), "WATCH +%s", nick);
+	if (!add)
+		tbuf[6] = '-';
+	serv->p_raw (serv, tbuf);
+}
+
+static void
+notify_watch_all (struct notify *notify, int add)
+{
+	server *serv;
+	GSList *list = serv_list;
+	while (list)
+	{
+		serv = list->data;
+		if (serv->connected && serv->end_of_motd && serv->supports_watch &&
+			 notify_do_network (notify, serv))
+			notify_watch (serv, notify->name, add);
+		list = list->next;
+	}
+}
+
+static void
+notify_flush_watches (server * serv, GSList *from, GSList *end)
+{
+	char tbuf[512];
+	GSList *list;
+	struct notify *notify;
+
+	strcpy (tbuf, "WATCH");
+
+	list = from;
+	while (list != end)
+	{
+		notify = list->data;
+		strcat (tbuf, " +");
+		strcat (tbuf, notify->name);
+		list = list->next;
+	}
+	serv->p_raw (serv, tbuf);
+}
+
+/* called when logging in. e.g. when End of motd. */
+
+void
+notify_send_watches (server * serv)
+{
+	struct notify *notify;
+	GSList *list;
+	GSList *point;
+	int len;
+
+	len = 0;
+	point = list = notify_list;
+	while (list)
+	{
+		notify = list->data;
+
+		if (notify_do_network (notify, serv))
+		{
+			len += strlen (notify->name) + 2 /* + and space */;
+			if (len > 500)
+			{
+				notify_flush_watches (serv, point, list);
+				len = strlen (notify->name) + 2;
+				point = list;
+			}
+		}
+
+		list = list->next;
+	}
+
+	if (point)
+		notify_flush_watches (serv, point, NULL);
+}
+
+/* called when receiving a ISON 303 - should this func go? */
+
+void
+notify_markonline (server *serv, char *word[])
+{
+	struct notify *notify;
+	struct notify_per_server *servnot;
+	GSList *list = notify_list;
+	int i, seen;
+
+	while (list)
+	{
+		notify = (struct notify *) list->data;
+		servnot = notify_find_server_entry (notify, serv);
+		if (!servnot)
+		{
+			list = list->next;
+			continue;
+		}
+		i = 4;
+		seen = FALSE;
+		while (*word[i])
+		{
+			if (!serv->p_cmp (notify->name, word[i]))
+			{
+				seen = TRUE;
+				notify_announce_online (serv, servnot, notify->name);
+				break;
+			}
+			i++;
+			/* FIXME: word[] is only a 32 element array, limits notify list to
+			   about 27 people */
+			if (i > PDIWORDS - 5)
+			{
+				/*fprintf (stderr, _("*** XCHAT WARNING: notify list too large.\n"));*/
+				break;
+			}
+		}
+		if (!seen && servnot->ison)
+		{
+			notify_announce_offline (serv, servnot, notify->name, FALSE);
+		}
+		list = list->next;
+	}
+	fe_notify_update (0);
+}
+
+/* yuck! Old routine for ISON notify */
+
+static void
+notify_checklist_for_server (server *serv)
+{
+	char outbuf[512];
+	struct notify *notify;
+	GSList *list = notify_list;
+	int i = 0;
+
+	strcpy (outbuf, "ISON ");
+	while (list)
+	{
+		notify = list->data;
+		if (notify_do_network (notify, serv))
+		{
+			i++;
+			strcat (outbuf, notify->name);
+			strcat (outbuf, " ");
+			if (strlen (outbuf) > 460)
+			{
+				/* LAME: we can't send more than 512 bytes to the server, but     *
+				 * if we split it in two packets, our offline detection wouldn't  *
+				 work                                                           */
+				/*fprintf (stderr, _("*** XCHAT WARNING: notify list too large.\n"));*/
+				break;
+			}
+		}
+		list = list->next;
+	}
+
+	if (i)
+		serv->p_raw (serv, outbuf);
+}
+
+int
+notify_checklist (void)	/* check ISON list */
+{
+	struct server *serv;
+	GSList *list = serv_list;
+
+	while (list)
+	{
+		serv = list->data;
+		if (serv->connected && serv->end_of_motd && !serv->supports_watch)
+		{
+			notify_checklist_for_server (serv);
+		}
+		list = list->next;
+	}
+	return 1;
+}
+
+void
+notify_showlist (struct session *sess)
+{
+	char outbuf[256];
+	struct notify *notify;
+	GSList *list = notify_list;
+	struct notify_per_server *servnot;
+	int i = 0;
+
+	EMIT_SIGNAL (XP_TE_NOTIFYHEAD, sess, NULL, NULL, NULL, NULL, 0);
+	while (list)
+	{
+		i++;
+		notify = (struct notify *) list->data;
+		servnot = notify_find_server_entry (notify, sess->server);
+		if (servnot && servnot->ison)
+			snprintf (outbuf, sizeof (outbuf), _("  %-20s online\n"), notify->name);
+		else
+			snprintf (outbuf, sizeof (outbuf), _("  %-20s offline\n"), notify->name);
+		PrintText (sess, outbuf);
+		list = list->next;
+	}
+	if (i)
+	{
+		sprintf (outbuf, "%d", i);
+		EMIT_SIGNAL (XP_TE_NOTIFYNUMBER, sess, outbuf, NULL, NULL, NULL, 0);
+	} else
+		EMIT_SIGNAL (XP_TE_NOTIFYEMPTY, sess, NULL, NULL, NULL, NULL, 0);
+}
+
+int
+notify_deluser (char *name)
+{
+	struct notify *notify;
+	struct notify_per_server *servnot;
+	GSList *list = notify_list;
+
+	while (list)
+	{
+		notify = (struct notify *) list->data;
+		if (!rfc_casecmp (notify->name, name))
+		{
+			fe_notify_update (notify->name);
+			/* Remove the records for each server */
+			while (notify->server_list)
+			{
+				servnot = (struct notify_per_server *) notify->server_list->data;
+				notify->server_list =
+					g_slist_remove (notify->server_list, servnot);
+				free (servnot);
+			}
+			notify_list = g_slist_remove (notify_list, notify);
+			notify_watch_all (notify, FALSE);
+			if (notify->networks)
+				free (notify->networks);
+			free (notify->name);
+			free (notify);
+			fe_notify_update (0);
+			return 1;
+		}
+		list = list->next;
+	}
+	return 0;
+}
+
+void
+notify_adduser (char *name, char *networks)
+{
+	struct notify *notify = malloc (sizeof (struct notify));
+	if (notify)
+	{
+		memset (notify, 0, sizeof (struct notify));
+		if (strlen (name) >= NICKLEN)
+		{
+			notify->name = malloc (NICKLEN);
+			safe_strcpy (notify->name, name, NICKLEN);
+		} else
+		{
+			notify->name = strdup (name);
+		}
+		if (networks)
+			notify->networks = despacify_dup (networks);
+		notify->server_list = 0;
+		notify_list = g_slist_prepend (notify_list, notify);
+		notify_checklist ();
+		fe_notify_update (notify->name);
+		fe_notify_update (0);
+		notify_watch_all (notify, TRUE);
+	}
+}
+
+gboolean
+notify_is_in_list (server *serv, char *name)
+{
+	struct notify *notify;
+	GSList *list = notify_list;
+
+	while (list)
+	{
+		notify = (struct notify *) list->data;
+		if (!serv->p_cmp (notify->name, name))
+			return TRUE;
+		list = list->next;
+	}
+
+	return FALSE;
+}
+
+int
+notify_isnotify (struct session *sess, char *name)
+{
+	struct notify *notify;
+	struct notify_per_server *servnot;
+	GSList *list = notify_list;
+
+	while (list)
+	{
+		notify = (struct notify *) list->data;
+		if (!sess->server->p_cmp (notify->name, name))
+		{
+			servnot = notify_find_server_entry (notify, sess->server);
+			if (servnot && servnot->ison)
+				return TRUE;
+		}
+		list = list->next;
+	}
+
+	return FALSE;
+}
+
+void
+notify_cleanup ()
+{
+	GSList *list = notify_list;
+	GSList *nslist, *srvlist;
+	struct notify *notify;
+	struct notify_per_server *servnot;
+	struct server *serv;
+	int valid;
+
+	while (list)
+	{
+		/* Traverse the list of notify structures */
+		notify = (struct notify *) list->data;
+		nslist = notify->server_list;
+		while (nslist)
+		{
+			/* Look at each per-server structure */
+			servnot = (struct notify_per_server *) nslist->data;
+
+			/* Check the server is valid */
+			valid = FALSE;
+			srvlist = serv_list;
+			while (srvlist)
+			{
+				serv = (struct server *) srvlist->data;
+				if (servnot->server == serv)
+				{
+					valid = serv->connected;	/* Only valid if server is too */
+					break;
+				}
+				srvlist = srvlist->next;
+			}
+			if (!valid)
+			{
+				notify->server_list =
+					g_slist_remove (notify->server_list, servnot);
+				free (servnot);
+				nslist = notify->server_list;
+			} else
+			{
+				nslist = nslist->next;
+			}
+		}
+		list = list->next;
+	}
+	fe_notify_update (0);
+}
diff --git a/src/common/notify.h b/src/common/notify.h
new file mode 100644
index 00000000..37674fe5
--- /dev/null
+++ b/src/common/notify.h
@@ -0,0 +1,44 @@
+#ifndef XCHAT_NOTIFY_H
+#define XCHAT_NOTIFY_H
+
+struct notify
+{
+	char *name;
+	char *networks;	/* network names, comma sep */
+	GSList *server_list;
+};
+
+struct notify_per_server
+{
+	struct server *server;
+	struct notify *notify;
+	time_t laston;
+	time_t lastseen;
+	time_t lastoff;
+	unsigned int ison:1;
+};
+
+extern GSList *notify_list;
+extern int notify_tag;
+
+/* the WATCH stuff */
+void notify_set_online (server * serv, char *nick);
+void notify_set_offline (server * serv, char *nick, int quiet);
+void notify_send_watches (server * serv);
+
+/* the general stuff */
+void notify_adduser (char *name, char *networks);
+int notify_deluser (char *name);
+void notify_cleanup (void);
+void notify_load (void);
+void notify_save (void);
+void notify_showlist (session *sess);
+gboolean notify_is_in_list (server *serv, char *name);
+int notify_isnotify (session *sess, char *name);
+struct notify_per_server *notify_find_server_entry (struct notify *notify, struct server *serv);
+
+/* the old ISON stuff - remove me? */
+void notify_markonline (server *serv, char *word[]);
+int notify_checklist (void);
+
+#endif
diff --git a/src/common/outbound.c b/src/common/outbound.c
new file mode 100644
index 00000000..7c6e5e6a
--- /dev/null
+++ b/src/common/outbound.c
@@ -0,0 +1,4403 @@
+/* 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
+ */
+
+#define _GNU_SOURCE	/* for memrchr */
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <limits.h>
+#include <errno.h>
+
+#define WANTSOCKET
+#define WANTARPA
+#include "inet.h"
+
+#ifndef WIN32
+#include <sys/wait.h>
+#endif
+
+#include <unistd.h>
+#include <time.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "xchat.h"
+#include "plugin.h"
+#include "ignore.h"
+#include "util.h"
+#include "fe.h"
+#include "cfgfiles.h"			  /* xchat_fopen_file() */
+#include "network.h"				/* net_ip() */
+#include "modes.h"
+#include "notify.h"
+#include "inbound.h"
+#include "text.h"
+#include "xchatc.h"
+#include "servlist.h"
+#include "server.h"
+#include "tree.h"
+#include "outbound.h"
+
+
+#ifdef USE_DEBUG
+extern int current_mem_usage;
+#endif
+#define TBUFSIZE 4096
+
+static void help (session *sess, char *tbuf, char *helpcmd, int quiet);
+static int cmd_server (session *sess, char *tbuf, char *word[], char *word_eol[]);
+static void handle_say (session *sess, char *text, int check_spch);
+
+
+static void
+notj_msg (struct session *sess)
+{
+	PrintText (sess, _("No channel joined. Try /join #<channel>\n"));
+}
+
+void
+notc_msg (struct session *sess)
+{
+	PrintText (sess, _("Not connected. Try /server <host> [<port>]\n"));
+}
+
+static char *
+random_line (char *file_name)
+{
+	FILE *fh;
+	char buf[512];
+	int lines, ran;
+
+	if (!file_name[0])
+		goto nofile;
+
+	fh = xchat_fopen_file (file_name, "r", 0);
+	if (!fh)
+	{
+	 nofile:
+		/* reason is not a file, an actual reason! */
+		return strdup (file_name);
+	}
+
+	/* count number of lines in file */
+	lines = 0;
+	while (fgets (buf, sizeof (buf), fh))
+		lines++;
+
+	if (lines < 1)
+		goto nofile;
+
+	/* go down a random number */
+	rewind (fh);
+	ran = RAND_INT (lines);
+	do
+	{
+		fgets (buf, sizeof (buf), fh);
+		lines--;
+	}
+	while (lines > ran);
+	fclose (fh);
+	buf[strlen (buf) - 1] = 0;	  /* remove the trailing '\n' */
+	return strdup (buf);
+}
+
+void
+server_sendpart (server * serv, char *channel, char *reason)
+{
+	if (!reason)
+	{
+		reason = random_line (prefs.partreason);
+		serv->p_part (serv, channel, reason);
+		free (reason);
+	} else
+	{
+		/* reason set by /quit, /close argument */
+		serv->p_part (serv, channel, reason);
+	}
+}
+
+void
+server_sendquit (session * sess)
+{
+	char *rea, *colrea;
+
+	if (!sess->quitreason)
+	{
+		colrea = strdup (prefs.quitreason);
+		check_special_chars (colrea, FALSE);
+		rea = random_line (colrea);
+		free (colrea);
+		sess->server->p_quit (sess->server, rea);
+		free (rea);
+	} else
+	{
+		/* reason set by /quit, /close argument */
+		sess->server->p_quit (sess->server, sess->quitreason);
+	}
+}
+
+void
+process_data_init (char *buf, char *cmd, char *word[],
+						 char *word_eol[], gboolean handle_quotes,
+						 gboolean allow_escape_quotes)
+{
+	int wordcount = 2;
+	int space = FALSE;
+	int quote = FALSE;
+	int j = 0;
+	int len;
+
+	word[0] = "\000\000";
+	word_eol[0] = "\000\000";
+	word[1] = (char *)buf;
+	word_eol[1] = (char *)cmd;
+
+	while (1)
+	{
+		switch (*cmd)
+		{
+		case 0:
+			buf[j] = 0;
+			for (j = wordcount; j < PDIWORDS; j++)
+			{
+				word[j] = "\000\000";
+				word_eol[j] = "\000\000";
+			}
+			return;
+		case '\042':
+			if (!handle_quotes)
+				goto def;
+			/* two quotes turn into 1 */
+			if (allow_escape_quotes && cmd[1] == '\042')
+			{
+				cmd++;
+				goto def;
+			}
+			if (quote)
+			{
+				quote = FALSE;
+				space = FALSE;
+			} else
+				quote = TRUE;
+			cmd++;
+			break;
+		case ' ':
+			if (!quote)
+			{
+				if (!space)
+				{
+					buf[j] = 0;
+					j++;
+
+					if (wordcount < PDIWORDS)
+					{
+						word[wordcount] = &buf[j];
+						word_eol[wordcount] = cmd + 1;
+						wordcount++;
+					}
+
+					space = TRUE;
+				}
+				cmd++;
+				break;
+			}
+		default:
+def:
+			space = FALSE;
+			len = g_utf8_skip[((unsigned char *)cmd)[0]];
+			if (len == 1)
+			{
+				buf[j] = *cmd;
+				j++;
+				cmd++;
+			} else
+			{
+				/* skip past a multi-byte utf8 char */
+				memcpy (buf + j, cmd, len);
+				j += len;
+				cmd += len;
+			}
+		}
+	}
+}
+
+static int
+cmd_addbutton (struct session *sess, char *tbuf, char *word[],
+					char *word_eol[])
+{
+	if (*word[2] && *word_eol[3])
+	{
+		if (sess->type == SESS_DIALOG)
+		{
+			list_addentry (&dlgbutton_list, word_eol[3], word[2]);
+			fe_dlgbuttons_update (sess);
+		} else
+		{
+			list_addentry (&button_list, word_eol[3], word[2]);
+			fe_buttons_update (sess);
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_allchannels (session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	GSList *list = sess_list;
+
+	if (!*word_eol[2])
+		return FALSE;
+
+	while (list)
+	{
+		sess = list->data;
+		if (sess->type == SESS_CHANNEL && sess->channel[0] && sess->server->connected)
+		{
+			handle_command (sess, word_eol[2], FALSE);
+		}
+		list = list->next;
+	}
+
+	return TRUE;
+}
+
+static int
+cmd_allchannelslocal (session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	GSList *list = sess_list;
+	server *serv = sess->server;
+
+	if (!*word_eol[2])
+		return FALSE;
+
+	while (list)
+	{
+		sess = list->data;
+		if (sess->type == SESS_CHANNEL && sess->channel[0] &&
+			 sess->server->connected && sess->server == serv)
+		{
+			handle_command (sess, word_eol[2], FALSE);
+		}
+		list = list->next;
+	}
+
+	return TRUE;
+}
+
+static int
+cmd_allservers (struct session *sess, char *tbuf, char *word[],
+					 char *word_eol[])
+{
+	GSList *list;
+	server *serv;
+
+	if (!*word_eol[2])
+		return FALSE;
+
+	list = serv_list;
+	while (list)
+	{
+		serv = list->data;
+		if (serv->connected)
+			handle_command (serv->front_session, word_eol[2], FALSE);
+		list = list->next;
+	}
+
+	return TRUE;
+}
+
+static int
+cmd_away (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	GSList *list;
+	char *reason = word_eol[2];
+
+	if (!(*reason))
+	{
+		if (sess->server->is_away)
+		{
+			if (sess->server->last_away_reason)
+				PrintTextf (sess, _("Already marked away: %s\n"), sess->server->last_away_reason);
+			return FALSE;
+		}
+
+		if (sess->server->reconnect_away)
+			reason = sess->server->last_away_reason;
+		else
+			/* must manage memory pointed to by random_line() */
+			reason = random_line (prefs.awayreason);
+	}
+	sess->server->p_set_away (sess->server, reason);
+
+	if (prefs.show_away_message)
+	{
+		snprintf (tbuf, TBUFSIZE, "me is away: %s", reason);
+		for (list = sess_list; list; list = list->next)
+		{
+			/* am I the right server and not a dialog box */
+			if (((struct session *) list->data)->server == sess->server
+				 && ((struct session *) list->data)->type == SESS_CHANNEL
+				 && ((struct session *) list->data)->channel[0])
+			{
+				handle_command ((session *) list->data, tbuf, TRUE);
+			}
+		}
+	}
+
+	if (sess->server->last_away_reason != reason)
+	{
+		if (sess->server->last_away_reason)
+			free (sess->server->last_away_reason);
+
+		if (reason == word_eol[2])
+			sess->server->last_away_reason = strdup (reason);
+		else
+			sess->server->last_away_reason = reason;
+	}
+
+	if (!sess->server->connected)
+		sess->server->reconnect_away = 1;
+
+	return TRUE;
+}
+
+static int
+cmd_back (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	GSList *list;
+	unsigned int gone;
+
+	if (sess->server->is_away)
+	{
+		sess->server->p_set_back (sess->server);
+
+		if (prefs.show_away_message)
+		{
+			gone = time (NULL) - sess->server->away_time;
+			sprintf (tbuf, "me is back (gone %.2d:%.2d:%.2d)", gone / 3600,
+						(gone / 60) % 60, gone % 60);
+			for (list = sess_list; list; list = list->next)
+			{
+				/* am I the right server and not a dialog box */
+				if (((struct session *) list->data)->server == sess->server
+					 && ((struct session *) list->data)->type == SESS_CHANNEL
+					 && ((struct session *) list->data)->channel[0])
+				{
+					handle_command ((session *) list->data, tbuf, TRUE);
+				}
+			}
+		}
+	}
+	else
+	{
+		PrintText (sess, _("Already marked back.\n"));
+	}
+
+	if (sess->server->last_away_reason)
+		free (sess->server->last_away_reason);
+	sess->server->last_away_reason = NULL;
+
+	return TRUE;
+}
+
+static void
+ban (session * sess, char *tbuf, char *mask, char *bantypestr, int deop)
+{
+	int bantype;
+	struct User *user;
+	char *at, *dot, *lastdot;
+	char username[64], fullhost[128], domain[128], *mode, *p2;
+	server *serv = sess->server;
+
+	user = userlist_find (sess, mask);
+	if (user && user->hostname)  /* it's a nickname, let's find a proper ban mask */
+	{
+		if (deop)
+		{
+			mode = "-o+b ";
+			p2 = user->nick;
+		} else
+		{
+			mode = "+b";
+			p2 = "";
+		}
+
+		mask = user->hostname;
+
+		at = strchr (mask, '@');	/* FIXME: utf8 */
+		if (!at)
+			return;					  /* can't happen? */
+		*at = 0;
+
+		if (mask[0] == '~' || mask[0] == '+' ||
+		    mask[0] == '=' || mask[0] == '^' || mask[0] == '-')
+		{
+			/* the ident is prefixed with something, we replace that sign with an * */
+			safe_strcpy (username+1, mask+1, sizeof (username)-1);
+			username[0] = '*';
+		} else if (at - mask < USERNAMELEN)
+		{
+			/* we just add an * in the begining of the ident */
+			safe_strcpy (username+1, mask, sizeof (username)-1);
+			username[0] = '*';
+		} else
+		{
+			/* ident might be too long, we just ban what it gives and add an * in the end */
+			safe_strcpy (username, mask, sizeof (username));
+		}
+		*at = '@';
+		safe_strcpy (fullhost, at + 1, sizeof (fullhost));
+
+		dot = strchr (fullhost, '.');
+		if (dot)
+		{
+			safe_strcpy (domain, dot, sizeof (domain));
+		} else
+		{
+			safe_strcpy (domain, fullhost, sizeof (domain));
+		}
+
+		if (*bantypestr)
+			bantype = atoi (bantypestr);
+		else
+			bantype = prefs.bantype;
+
+		tbuf[0] = 0;
+		if (inet_addr (fullhost) != -1)	/* "fullhost" is really a IP number */
+		{
+			lastdot = strrchr (fullhost, '.');
+			if (!lastdot)
+				return;				  /* can't happen? */
+
+			*lastdot = 0;
+			strcpy (domain, fullhost);
+			*lastdot = '.';
+
+			switch (bantype)
+			{
+			case 0:
+				snprintf (tbuf, TBUFSIZE, "%s%s *!*@%s.*", mode, p2, domain);
+				break;
+
+			case 1:
+				snprintf (tbuf, TBUFSIZE, "%s%s *!*@%s", mode, p2, fullhost);
+				break;
+
+			case 2:
+				snprintf (tbuf, TBUFSIZE, "%s%s *!%s@%s.*", mode, p2, username, domain);
+				break;
+
+			case 3:
+				snprintf (tbuf, TBUFSIZE, "%s%s *!%s@%s", mode, p2, username, fullhost);
+				break;
+			}
+		} else
+		{
+			switch (bantype)
+			{
+			case 0:
+				snprintf (tbuf, TBUFSIZE, "%s%s *!*@*%s", mode, p2, domain);
+				break;
+
+			case 1:
+				snprintf (tbuf, TBUFSIZE, "%s%s *!*@%s", mode, p2, fullhost);
+				break;
+
+			case 2:
+				snprintf (tbuf, TBUFSIZE, "%s%s *!%s@*%s", mode, p2, username, domain);
+				break;
+
+			case 3:
+				snprintf (tbuf, TBUFSIZE, "%s%s *!%s@%s", mode, p2, username, fullhost);
+				break;
+			}
+		}
+
+	} else
+	{
+		snprintf (tbuf, TBUFSIZE, "+b %s", mask);
+	}
+	serv->p_mode (serv, sess->channel, tbuf);
+}
+
+static int
+cmd_ban (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *mask = word[2];
+
+	if (*mask)
+	{
+		ban (sess, tbuf, mask, word[3], 0);
+	} else
+	{
+		sess->server->p_mode (sess->server, sess->channel, "+b");	/* banlist */
+	}
+
+	return TRUE;
+}
+
+static int
+cmd_unban (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	/* Allow more than one mask in /unban -- tvk */
+	int i = 2;
+
+	while (1)
+	{
+		if (!*word[i])
+		{
+			if (i == 2)
+				return FALSE;
+			send_channel_modes (sess, tbuf, word, 2, i, '-', 'b', 0);
+			return TRUE;
+		}
+		i++;
+	}
+}
+
+static int
+cmd_chanopt (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	/* chanopt.c */
+	return chanopt_command (sess, tbuf, word, word_eol);
+}
+
+static int
+cmd_charset (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	server *serv = sess->server;
+	const char *locale = NULL;
+	int offset = 0;
+
+	if (strcmp (word[2], "-quiet") == 0)
+		offset++;
+
+	if (!word[2 + offset][0])
+	{
+		g_get_charset (&locale);
+		PrintTextf (sess, "Current charset: %s\n",
+						serv->encoding ? serv->encoding : locale);
+		return TRUE;
+	}
+
+	if (servlist_check_encoding (word[2 + offset]))
+	{
+		server_set_encoding (serv, word[2 + offset]);
+		if (offset < 1)
+			PrintTextf (sess, "Charset changed to: %s\n", word[2 + offset]);
+	} else
+	{
+		PrintTextf (sess, "\0034Unknown charset:\017 %s\n", word[2 + offset]);
+	}
+
+	return TRUE;
+}
+
+static int
+cmd_clear (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	GSList *list = sess_list;
+	char *reason = word_eol[2];
+
+	if (strcasecmp (reason, "HISTORY") == 0)
+	{
+		history_free (&sess->history);
+		return TRUE;
+	}
+
+	if (strncasecmp (reason, "all", 3) == 0)
+	{
+		while (list)
+		{
+			sess = list->data;
+			if (!sess->nick_said)
+				fe_text_clear (list->data, 0);
+			list = list->next;
+		}
+		return TRUE;
+	}
+
+	if (reason[0] != '-' && !isdigit (reason[0]) && reason[0] != 0)
+		return FALSE;
+
+	fe_text_clear (sess, atoi (reason));
+	return TRUE;
+}
+
+static int
+cmd_close (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	GSList *list;
+
+	if (strcmp (word[2], "-m") == 0)
+	{
+		list = sess_list;
+		while (list)
+		{
+			sess = list->data;
+			list = list->next;
+			if (sess->type == SESS_DIALOG)
+				fe_close_window (sess);
+		}
+	} else
+	{
+		if (*word_eol[2])
+			sess->quitreason = word_eol[2];
+		fe_close_window (sess);
+	}
+
+	return TRUE;
+}
+
+static int
+cmd_ctcp (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int mbl;
+	char *to = word[2];
+	if (*to)
+	{
+		char *msg = word_eol[3];
+		if (*msg)
+		{
+			unsigned char *cmd = (unsigned char *)msg;
+
+			/* make the first word upper case (as per RFC) */
+			while (1)
+			{
+				if (*cmd == ' ' || *cmd == 0)
+					break;
+				mbl = g_utf8_skip[*cmd];
+				if (mbl == 1)
+					*cmd = toupper (*cmd);
+				cmd += mbl;
+			}
+
+			sess->server->p_ctcp (sess->server, to, msg);
+
+			EMIT_SIGNAL (XP_TE_CTCPSEND, sess, to, msg, NULL, NULL, 0);
+
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+static int
+cmd_country (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *code = word[2];
+	if (*code)
+	{
+		/* search? */
+		if (strcmp (code, "-s") == 0)
+		{
+			country_search (word[3], sess, (void *)PrintTextf);
+			return TRUE;
+		}
+
+		/* search, but forgot the -s */
+		if (strchr (code, '*'))
+		{
+			country_search (code, sess, (void *)PrintTextf);
+			return TRUE;
+		}
+
+		sprintf (tbuf, "%s = %s\n", code, country (code));
+		PrintText (sess, tbuf);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_cycle (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *key = sess->channelkey;
+	char *chan = word[2];
+	if (!*chan)
+		chan = sess->channel;
+	if (*chan && sess->type == SESS_CHANNEL)
+	{
+		sess->server->p_cycle (sess->server, chan, key);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_dcc (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int goodtype;
+	struct DCC *dcc = 0;
+	char *type = word[2];
+	if (*type)
+	{
+		if (!strcasecmp (type, "HELP"))
+			return FALSE;
+		if (!strcasecmp (type, "CLOSE"))
+		{
+			if (*word[3] && *word[4])
+			{
+				goodtype = 0;
+				if (!strcasecmp (word[3], "SEND"))
+				{
+					dcc = find_dcc (word[4], word[5], TYPE_SEND);
+					dcc_abort (sess, dcc);
+					goodtype = TRUE;
+				}
+				if (!strcasecmp (word[3], "GET"))
+				{
+					dcc = find_dcc (word[4], word[5], TYPE_RECV);
+					dcc_abort (sess, dcc);
+					goodtype = TRUE;
+				}
+				if (!strcasecmp (word[3], "CHAT"))
+				{
+					dcc = find_dcc (word[4], "", TYPE_CHATRECV);
+					if (!dcc)
+						dcc = find_dcc (word[4], "", TYPE_CHATSEND);
+					dcc_abort (sess, dcc);
+					goodtype = TRUE;
+				}
+
+				if (!goodtype)
+					return FALSE;
+
+				if (!dcc)
+					EMIT_SIGNAL (XP_TE_NODCC, sess, NULL, NULL, NULL, NULL, 0);
+
+				return TRUE;
+
+			}
+			return FALSE;
+		}
+		if ((!strcasecmp (type, "CHAT")) || (!strcasecmp (type, "PCHAT")))
+		{
+			char *nick = word[3];
+			int passive = (!strcasecmp(type, "PCHAT")) ? 1 : 0;
+			if (*nick)
+				dcc_chat (sess, nick, passive);
+			return TRUE;
+		}
+		if (!strcasecmp (type, "LIST"))
+		{
+			dcc_show_list (sess);
+			return TRUE;
+		}
+		if (!strcasecmp (type, "GET"))
+		{
+			char *nick = word[3];
+			char *file = word[4];
+			if (!*file)
+			{
+				if (*nick)
+					dcc_get_nick (sess, nick);
+			} else
+			{
+				dcc = find_dcc (nick, file, TYPE_RECV);
+				if (dcc)
+					dcc_get (dcc);
+				else
+					EMIT_SIGNAL (XP_TE_NODCC, sess, NULL, NULL, NULL, NULL, 0);
+			}
+			return TRUE;
+		}
+		if ((!strcasecmp (type, "SEND")) || (!strcasecmp (type, "PSEND")))
+		{
+			int i = 3, maxcps;
+			char *nick, *file;
+			int passive = (!strcasecmp(type, "PSEND")) ? 1 : 0;
+
+			nick = word[i];
+			if (!*nick)
+				return FALSE;
+
+			maxcps = prefs.dcc_max_send_cps;
+			if (!strncasecmp(nick, "-maxcps=", 8))
+			{
+				maxcps = atoi(nick + 8);
+				i++;
+				nick = word[i];
+				if (!*nick)
+					return FALSE;
+			}
+
+			i++;
+
+			file = word[i];
+			if (!*file)
+			{
+				fe_dcc_send_filereq (sess, nick, maxcps, passive);
+				return TRUE;
+			}
+
+			do
+			{
+				dcc_send (sess, nick, file, maxcps, passive);
+				i++;
+				file = word[i];
+			}
+			while (*file);
+
+			return TRUE;
+		}
+
+		return FALSE;
+	}
+
+	dcc_show_list (sess);
+	return TRUE;
+}
+
+static int
+cmd_debug (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	struct session *s;
+	struct server *v;
+	GSList *list = sess_list;
+
+	PrintText (sess, "Session   T Channel    WaitChan  WillChan  Server\n");
+	while (list)
+	{
+		s = (struct session *) list->data;
+		sprintf (tbuf, "%p %1x %-10.10s %-10.10s %-10.10s %p\n",
+					s, s->type, s->channel, s->waitchannel,
+					s->willjoinchannel, s->server);
+		PrintText (sess, tbuf);
+		list = list->next;
+	}
+
+	list = serv_list;
+	PrintText (sess, "Server    Sock  Name\n");
+	while (list)
+	{
+		v = (struct server *) list->data;
+		sprintf (tbuf, "%p %-5d %s\n",
+					v, v->sok, v->servername);
+		PrintText (sess, tbuf);
+		list = list->next;
+	}
+
+	sprintf (tbuf,
+				"\nfront_session: %p\n"
+				"current_tab: %p\n\n",
+				sess->server->front_session, current_tab);
+	PrintText (sess, tbuf);
+#ifdef USE_DEBUG
+	sprintf (tbuf, "current mem: %d\n\n", current_mem_usage);
+	PrintText (sess, tbuf);
+#endif  /* !MEMORY_DEBUG */
+
+	return TRUE;
+}
+
+static int
+cmd_delbutton (struct session *sess, char *tbuf, char *word[],
+					char *word_eol[])
+{
+	if (*word[2])
+	{
+		if (sess->type == SESS_DIALOG)
+		{
+			if (list_delentry (&dlgbutton_list, word[2]))
+				fe_dlgbuttons_update (sess);
+		} else
+		{
+			if (list_delentry (&button_list, word[2]))
+				fe_buttons_update (sess);
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_dehop (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int i = 2;
+
+	while (1)
+	{
+		if (!*word[i])
+		{
+			if (i == 2)
+				return FALSE;
+			send_channel_modes (sess, tbuf, word, 2, i, '-', 'h', 0);
+			return TRUE;
+		}
+		i++;
+	}
+}
+
+static int
+cmd_deop (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int i = 2;
+
+	while (1)
+	{
+		if (!*word[i])
+		{
+			if (i == 2)
+				return FALSE;
+			send_channel_modes (sess, tbuf, word, 2, i, '-', 'o', 0);
+			return TRUE;
+		}
+		i++;
+	}
+}
+
+typedef struct
+{
+	char **nicks;
+	int i;
+	session *sess;
+	char *reason;
+	char *tbuf;
+} multidata;
+
+static int
+mdehop_cb (struct User *user, multidata *data)
+{
+	if (user->hop && !user->me)
+	{
+		data->nicks[data->i] = user->nick;
+		data->i++;
+	}
+	return TRUE;
+}
+
+static int
+cmd_mdehop (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char **nicks = malloc (sizeof (char *) * sess->hops);
+	multidata data;
+
+	data.nicks = nicks;
+	data.i = 0;
+	tree_foreach (sess->usertree, (tree_traverse_func *)mdehop_cb, &data);
+	send_channel_modes (sess, tbuf, nicks, 0, data.i, '-', 'h', 0);
+	free (nicks);
+
+	return TRUE;
+}
+
+static int
+mdeop_cb (struct User *user, multidata *data)
+{
+	if (user->op && !user->me)
+	{
+		data->nicks[data->i] = user->nick;
+		data->i++;
+	}
+	return TRUE;
+}
+
+static int
+cmd_mdeop (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char **nicks = malloc (sizeof (char *) * sess->ops);
+	multidata data;
+
+	data.nicks = nicks;
+	data.i = 0;
+	tree_foreach (sess->usertree, (tree_traverse_func *)mdeop_cb, &data);
+	send_channel_modes (sess, tbuf, nicks, 0, data.i, '-', 'o', 0);
+	free (nicks);
+
+	return TRUE;
+}
+
+GSList *menu_list = NULL;
+
+static void
+menu_free (menu_entry *me)
+{
+	free (me->path);
+	if (me->label)
+		free (me->label);
+	if (me->cmd)
+		free (me->cmd);
+	if (me->ucmd)
+		free (me->ucmd);
+	if (me->group)
+		free (me->group);
+	if (me->icon)
+		free (me->icon);
+	free (me);
+}
+
+/* strings equal? but ignore underscores */
+
+int
+menu_streq (const char *s1, const char *s2, int def)
+{
+	/* for separators */
+	if (s1 == NULL && s2 == NULL)
+		return 0;
+	if (s1 == NULL || s2 == NULL)
+		return 1;
+	while (*s1)
+	{
+		if (*s1 == '_')
+			s1++;
+		if (*s2 == '_')
+			s2++;
+		if (*s1 != *s2)
+			return 1;
+		s1++;
+		s2++;
+	}
+	if (!*s2)
+		return 0;
+	return def;
+}
+
+static menu_entry *
+menu_entry_find (char *path, char *label)
+{
+	GSList *list;
+	menu_entry *me;
+
+	list = menu_list;
+	while (list)
+	{
+		me = list->data;
+		if (!strcmp (path, me->path))
+		{
+			if (me->label && label && !strcmp (label, me->label))
+				return me;
+		}
+		list = list->next;
+	}
+	return NULL;
+}
+
+static void
+menu_del_children (char *path, char *label)
+{
+	GSList *list, *next;
+	menu_entry *me;
+	char buf[512];
+
+	if (!label)
+		label = "";
+	if (path[0])
+		snprintf (buf, sizeof (buf), "%s/%s", path, label);
+	else
+		snprintf (buf, sizeof (buf), "%s", label);
+
+	list = menu_list;
+	while (list)
+	{
+		me = list->data;
+		next = list->next;
+		if (!menu_streq (buf, me->path, 0))
+		{
+			menu_list = g_slist_remove (menu_list, me);
+			menu_free (me);
+		}
+		list = next;
+	}
+}
+
+static int
+menu_del (char *path, char *label)
+{
+	GSList *list;
+	menu_entry *me;
+
+	list = menu_list;
+	while (list)
+	{
+		me = list->data;
+		if (!menu_streq (me->label, label, 1) && !menu_streq (me->path, path, 1))
+		{
+			menu_list = g_slist_remove (menu_list, me);
+			fe_menu_del (me);
+			menu_free (me);
+			/* delete this item's children, if any */
+			menu_del_children (path, label);
+			return 1;
+		}
+		list = list->next;
+	}
+
+	return 0;
+}
+
+static char
+menu_is_mainmenu_root (char *path, gint16 *offset)
+{
+	static const char *menus[] = {"\x4$TAB","\x5$TRAY","\x4$URL","\x5$NICK","\x5$CHAN"};
+	int i;
+
+	for (i = 0; i < 5; i++)
+	{
+		if (!strncmp (path, menus[i] + 1, menus[i][0]))
+		{
+			*offset = menus[i][0] + 1;	/* number of bytes to offset the root */
+			return 0;	/* is not main menu */
+		}
+	}
+
+	*offset = 0;
+	return 1;	/* is main menu */
+}
+
+static void
+menu_add (char *path, char *label, char *cmd, char *ucmd, int pos, int state, int markup, int enable, int mod, int key, char *group, char *icon)
+{
+	menu_entry *me;
+
+	/* already exists? */
+	me = menu_entry_find (path, label);
+	if (me)
+	{
+		/* update only */
+		me->state = state;
+		me->enable = enable;
+		fe_menu_update (me);
+		return;
+	}
+
+	me = malloc (sizeof (menu_entry));
+	me->pos = pos;
+	me->modifier = mod;
+	me->is_main = menu_is_mainmenu_root (path, &me->root_offset);
+	me->state = state;
+	me->markup = markup;
+	me->enable = enable;
+	me->key = key;
+	me->path = strdup (path);
+	me->label = NULL;
+	me->cmd = NULL;
+	me->ucmd = NULL;
+	me->group = NULL;
+	me->icon = NULL;
+
+	if (label)
+		me->label = strdup (label);
+	if (cmd)
+		me->cmd = strdup (cmd);
+	if (ucmd)
+		me->ucmd = strdup (ucmd);
+	if (group)
+		me->group = strdup (group);
+	if (icon)
+		me->icon = strdup (icon);
+
+	menu_list = g_slist_append (menu_list, me);
+	label = fe_menu_add (me);
+	if (label)
+	{
+		/* FE has given us a stripped label */
+		free (me->label);
+		me->label = strdup (label);
+		g_free (label); /* this is from pango */
+	}
+}
+
+static int
+cmd_menu (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int idx = 2;
+	int len;
+	int pos = 0xffff;
+	int state;
+	int toggle = FALSE;
+	int enable = TRUE;
+	int markup = FALSE;
+	int key = 0;
+	int mod = 0;
+	char *label;
+	char *group = NULL;
+	char *icon = NULL;
+
+	if (!word[2][0] || !word[3][0])
+		return FALSE;
+
+	/* -eX enabled or not? */
+	if (word[idx][0] == '-' && word[idx][1] == 'e')
+	{
+		enable = atoi (word[idx] + 2);
+		idx++;
+	}
+
+	/* -i<ICONFILE> */
+	if (word[idx][0] == '-' && word[idx][1] == 'i')
+	{
+		icon = word[idx] + 2;
+		idx++;
+	}
+
+	/* -k<mod>,<key> key binding */
+	if (word[idx][0] == '-' && word[idx][1] == 'k')
+	{
+		char *comma = strchr (word[idx], ',');
+		if (!comma)
+			return FALSE;
+		mod = atoi (word[idx] + 2);
+		key = atoi (comma + 1);
+		idx++;
+	}
+
+	/* -m to specify PangoMarkup language */
+	if (word[idx][0] == '-' && word[idx][1] == 'm')
+	{
+		markup = TRUE;
+		idx++;
+	}
+
+	/* -pX to specify menu position */
+	if (word[idx][0] == '-' && word[idx][1] == 'p')
+	{
+		pos = atoi (word[idx] + 2);
+		idx++;
+	}
+
+	/* -rSTATE,GROUP to specify a radio item */
+	if (word[idx][0] == '-' && word[idx][1] == 'r')
+	{
+		state = atoi (word[idx] + 2);
+		group = word[idx] + 4;
+		idx++;
+	}
+
+	/* -tX to specify toggle item with default state */
+	if (word[idx][0] == '-' && word[idx][1] == 't')
+	{
+		state = atoi (word[idx] + 2);
+		idx++;
+		toggle = TRUE;
+	}
+
+	if (word[idx+1][0] == 0)
+		return FALSE;
+
+	/* the path */
+	path_part (word[idx+1], tbuf, 512);
+	len = strlen (tbuf);
+	if (len)
+		tbuf[len - 1] = 0;
+
+	/* the name of the item */
+	label = file_part (word[idx + 1]);
+	if (label[0] == '-' && label[1] == 0)
+		label = NULL;	/* separator */
+
+	if (markup)
+	{
+		char *p;	/* to force pango closing tags through */
+		for (p = label; *p; p++)
+			if (*p == 3)
+				*p = '/';
+	}
+
+	if (!strcasecmp (word[idx], "ADD"))
+	{
+		if (toggle)
+		{
+			menu_add (tbuf, label, word[idx + 2], word[idx + 3], pos, state, markup, enable, mod, key, NULL, NULL);
+		} else
+		{
+			if (word[idx + 2][0])
+				menu_add (tbuf, label, word[idx + 2], NULL, pos, state, markup, enable, mod, key, group, icon);
+			else
+				menu_add (tbuf, label, NULL, NULL, pos, state, markup, enable, mod, key, group, icon);
+		}
+		return TRUE;
+	}
+
+	if (!strcasecmp (word[idx], "DEL"))
+	{
+		menu_del (tbuf, label);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int
+mkick_cb (struct User *user, multidata *data)
+{
+	if (!user->op && !user->me)
+		data->sess->server->p_kick (data->sess->server, data->sess->channel, user->nick, data->reason);
+	return TRUE;
+}
+
+static int
+mkickops_cb (struct User *user, multidata *data)
+{
+	if (user->op && !user->me)
+		data->sess->server->p_kick (data->sess->server, data->sess->channel, user->nick, data->reason);
+	return TRUE;
+}
+
+static int
+cmd_mkick (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	multidata data;
+
+	data.sess = sess;
+	data.reason = word_eol[2];
+	tree_foreach (sess->usertree, (tree_traverse_func *)mkickops_cb, &data);
+	tree_foreach (sess->usertree, (tree_traverse_func *)mkick_cb, &data);
+
+	return TRUE;
+}
+
+static int
+cmd_devoice (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int i = 2;
+
+	while (1)
+	{
+		if (!*word[i])
+		{
+			if (i == 2)
+				return FALSE;
+			send_channel_modes (sess, tbuf, word, 2, i, '-', 'v', 0);
+			return TRUE;
+		}
+		i++;
+	}
+}
+
+static int
+cmd_discon (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	sess->server->disconnect (sess, TRUE, -1);
+	return TRUE;
+}
+
+static int
+cmd_dns (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+#ifdef WIN32
+	PrintText (sess, "DNS is not implemented in Windows.\n");
+	return TRUE;
+#else
+	char *nick = word[2];
+	struct User *user;
+
+	if (*nick)
+	{
+		if (strchr (nick, '.') == NULL)
+		{
+			user = userlist_find (sess, nick);
+			if (user && user->hostname)
+			{
+				do_dns (sess, user->nick, user->hostname);
+			} else
+			{
+				sess->server->p_get_ip (sess->server, nick);
+				sess->server->doing_dns = TRUE;
+			}
+		} else
+		{
+			snprintf (tbuf, TBUFSIZE, "exec -d %s %s", prefs.dnsprogram, nick);
+			handle_command (sess, tbuf, FALSE);
+		}
+		return TRUE;
+	}
+	return FALSE;
+#endif
+}
+
+static int
+cmd_echo (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	PrintText (sess, word_eol[2]);
+	return TRUE;
+}
+
+#ifndef WIN32
+
+static void
+exec_check_process (struct session *sess)
+{
+	int val;
+
+	if (sess->running_exec == NULL)
+		return;
+	val = waitpid (sess->running_exec->childpid, NULL, WNOHANG);
+	if (val == -1 || val > 0)
+	{
+		close (sess->running_exec->myfd);
+		fe_input_remove (sess->running_exec->iotag);
+		free (sess->running_exec);
+		sess->running_exec = NULL;
+	}
+}
+
+#ifndef __EMX__
+static int
+cmd_execs (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int r;
+
+	exec_check_process (sess);
+	if (sess->running_exec == NULL)
+	{
+		EMIT_SIGNAL (XP_TE_NOCHILD, sess, NULL, NULL, NULL, NULL, 0);
+		return FALSE;
+	}
+	r = kill (sess->running_exec->childpid, SIGSTOP);
+	if (r == -1)
+		PrintText (sess, "Error in kill(2)\n");
+
+	return TRUE;
+}
+
+static int
+cmd_execc (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int r;
+
+	exec_check_process (sess);
+	if (sess->running_exec == NULL)
+	{
+		EMIT_SIGNAL (XP_TE_NOCHILD, sess, NULL, NULL, NULL, NULL, 0);
+		return FALSE;
+	}
+	r = kill (sess->running_exec->childpid, SIGCONT);
+	if (r == -1)
+		PrintText (sess, "Error in kill(2)\n");
+
+	return TRUE;
+}
+
+static int
+cmd_execk (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int r;
+
+	exec_check_process (sess);
+	if (sess->running_exec == NULL)
+	{
+		EMIT_SIGNAL (XP_TE_NOCHILD, sess, NULL, NULL, NULL, NULL, 0);
+		return FALSE;
+	}
+	if (strcmp (word[2], "-9") == 0)
+		r = kill (sess->running_exec->childpid, SIGKILL);
+	else
+		r = kill (sess->running_exec->childpid, SIGTERM);
+	if (r == -1)
+		PrintText (sess, "Error in kill(2)\n");
+
+	return TRUE;
+}
+
+/* OS/2 Can't have the /EXECW command because it uses pipe(2) not socketpair
+   and thus it is simplex --AGL */
+static int
+cmd_execw (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int len;
+	char *temp;
+	exec_check_process (sess);
+	if (sess->running_exec == NULL)
+	{
+		EMIT_SIGNAL (XP_TE_NOCHILD, sess, NULL, NULL, NULL, NULL, 0);
+		return FALSE;
+	}
+	len = strlen(word_eol[2]);
+	temp = malloc(len + 2);
+	sprintf(temp, "%s\n", word_eol[2]);
+	PrintText(sess, temp);
+	write(sess->running_exec->myfd, temp, len + 1);
+	free(temp);
+
+	return TRUE;
+}
+#endif /* !__EMX__ */
+
+/* convert ANSI escape color codes to mIRC codes */
+
+static short escconv[] =
+/* 0 1 2 3 4 5  6 7  0 1 2 3 4  5  6  7 */
+{  1,4,3,5,2,10,6,1, 1,7,9,8,12,11,13,1 };
+
+static void
+exec_handle_colors (char *buf, int len)
+{
+	char numb[16];
+	char *nbuf;
+	int i = 0, j = 0, k = 0, firstn = 0, col, colf = 0, colb = 0;
+	int esc = FALSE, backc = FALSE, bold = FALSE;
+
+	/* any escape codes in this text? */
+	if (strchr (buf, 27) == 0)
+		return;
+
+	nbuf = malloc (len + 1);
+
+	while (i < len)
+	{
+		switch (buf[i])
+		{
+		case '\r':
+			break;
+		case 27:
+			esc = TRUE;
+			break;
+		case ';':
+			if (!esc)
+				goto norm;
+			backc = TRUE;
+			numb[k] = 0;
+			firstn = atoi (numb);
+			k = 0;
+			break;
+		case '[':
+			if (!esc)
+				goto norm;
+			break;
+		default:
+			if (esc)
+			{
+				if (buf[i] >= 'A' && buf[i] <= 'z')
+				{
+					if (buf[i] == 'm')
+					{
+						/* ^[[0m */
+						if (k == 0 || (numb[0] == '0' && k == 1))
+						{
+							nbuf[j] = '\017';
+							j++;
+							bold = FALSE;
+							goto cont;
+						}
+
+						numb[k] = 0;
+						col = atoi (numb);
+						backc = FALSE;
+
+						if (firstn == 1)
+							bold = TRUE;
+
+						if (firstn >= 30 && firstn <= 37)
+							colf = firstn - 30;
+
+						if (col >= 40)
+						{
+							colb = col - 40;
+							backc = TRUE;
+						}
+
+						if (col >= 30 && col <= 37)
+							colf = col - 30;
+
+						if (bold)
+							colf += 8;
+
+						if (backc)
+						{
+							colb = escconv[colb % 14];
+							colf = escconv[colf % 14];
+							j += sprintf (&nbuf[j], "\003%d,%02d", colf, colb);
+						} else
+						{
+							colf = escconv[colf % 14];
+							j += sprintf (&nbuf[j], "\003%02d", colf);
+						}
+					}
+cont:				esc = FALSE;
+					backc = FALSE;
+					k = 0;
+				} else
+				{
+					if (isdigit ((unsigned char) buf[i]) && k < (sizeof (numb) - 1))
+					{
+						numb[k] = buf[i];
+						k++;
+					}
+				}
+			} else
+			{
+norm:			nbuf[j] = buf[i];
+				j++;
+			}
+		}
+		i++;
+	}
+
+	nbuf[j] = 0;
+	memcpy (buf, nbuf, j + 1);
+	free (nbuf);
+}
+
+#ifndef HAVE_MEMRCHR
+static void *
+memrchr (const void *block, int c, size_t size)
+{
+	unsigned char *p;
+
+	for (p = (unsigned char *)block + size; p != block; p--)
+		if (*p == c)
+			return p;
+	return 0;
+}
+#endif
+
+static gboolean
+exec_data (GIOChannel *source, GIOCondition condition, struct nbexec *s)
+{
+	char *buf, *readpos, *rest;
+	int rd, len;
+	int sok = s->myfd;
+
+	len = s->buffill;
+	if (len) {
+		/* append new data to buffered incomplete line */
+		buf = malloc(len + 2050);
+		memcpy(buf, s->linebuf, len);
+		readpos = buf + len;
+		free(s->linebuf);
+		s->linebuf = NULL;
+	}
+	else
+		readpos = buf = malloc(2050);
+
+	rd = read (sok, readpos, 2048);
+	if (rd < 1)
+	{
+		/* The process has died */
+		kill(s->childpid, SIGKILL);
+		if (len) {
+			buf[len] = '\0';
+			exec_handle_colors(buf, len);
+			if (s->tochannel)
+			{
+				/* must turn off auto-completion temporarily */
+				unsigned int old = prefs.nickcompletion;
+				prefs.nickcompletion = 0;
+				handle_multiline (s->sess, buf, FALSE, TRUE);
+				prefs.nickcompletion = old;
+			}
+			else
+				PrintText (s->sess, buf);
+		}
+		free(buf);
+		waitpid (s->childpid, NULL, 0);
+		s->sess->running_exec = NULL;
+		fe_input_remove (s->iotag);
+		close (sok);
+		free (s);
+		return TRUE;
+	}
+	len += rd;
+	buf[len] = '\0';
+
+	rest = memrchr(buf, '\n', len);
+	if (rest)
+		rest++;
+	else
+		rest = buf;
+	if (*rest) {
+		s->buffill = len - (rest - buf); /* = strlen(rest) */
+		s->linebuf = malloc(s->buffill);
+		memcpy(s->linebuf, rest, s->buffill);
+		*rest = '\0';
+		len -= s->buffill; /* possibly 0 */
+	}
+	else
+		s->buffill = 0;
+
+	if (len) {
+		exec_handle_colors (buf, len);
+		if (s->tochannel)
+			handle_multiline (s->sess, buf, FALSE, TRUE);
+		else
+			PrintText (s->sess, buf);
+	}
+
+	free(buf);
+	return TRUE;
+}
+
+static int
+cmd_exec (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int tochannel = FALSE;
+	char *cmd = word_eol[2];
+	int fds[2], pid = 0;
+	struct nbexec *s;
+	int shell = TRUE;
+	int fd;
+
+	if (*cmd)
+	{
+		exec_check_process (sess);
+		if (sess->running_exec != NULL)
+		{
+			EMIT_SIGNAL (XP_TE_ALREADYPROCESS, sess, NULL, NULL, NULL, NULL, 0);
+			return TRUE;
+		}
+
+		if (!strcmp (word[2], "-d"))
+		{
+			if (!*word[3])
+				return FALSE;
+			cmd = word_eol[3];
+			shell = FALSE;
+		}
+		else if (!strcmp (word[2], "-o"))
+		{
+			if (!*word[3])
+				return FALSE;
+			cmd = word_eol[3];
+			tochannel = TRUE;
+		}
+
+		if (shell)
+		{
+			if (access ("/bin/sh", X_OK) != 0)
+			{
+				fe_message (_("I need /bin/sh to run!\n"), FE_MSG_ERROR);
+				return TRUE;
+			}
+		}
+
+#ifdef __EMX__						  /* if os/2 */
+		if (pipe (fds) < 0)
+		{
+			PrintText (sess, "Pipe create error\n");
+			return FALSE;
+		}
+		setmode (fds[0], O_BINARY);
+		setmode (fds[1], O_BINARY);
+#else
+		if (socketpair (PF_UNIX, SOCK_STREAM, 0, fds) == -1)
+		{
+			PrintText (sess, "socketpair(2) failed\n");
+			return FALSE;
+		}
+#endif
+		s = (struct nbexec *) malloc (sizeof (struct nbexec));
+		memset(s, 0, sizeof(*s));
+		s->myfd = fds[0];
+		s->tochannel = tochannel;
+		s->sess = sess;
+
+		pid = fork ();
+		if (pid == 0)
+		{
+			/* This is the child's context */
+			close (0);
+			close (1);
+			close (2);
+			/* Close parent's end of pipe */
+			close(s->myfd);
+			/* Copy the child end of the pipe to stdout and stderr */
+			dup2 (fds[1], 1);
+			dup2 (fds[1], 2);
+			/* Also copy it to stdin so we can write to it */
+			dup2 (fds[1], 0);
+			/* Now close all open file descriptors except stdin, stdout and stderr */
+			for (fd = 3; fd < 1024; fd++) close(fd);
+			/* Now we call /bin/sh to run our cmd ; made it more friendly -DC1 */
+			if (shell)
+			{
+				execl ("/bin/sh", "sh", "-c", cmd, NULL);
+			} else
+			{
+				char **argv;
+				int argc;
+
+				my_poptParseArgvString (cmd, &argc, &argv);
+				execvp (argv[0], argv);
+			}
+			/* not reached unless error */
+			/*printf("exec error\n");*/
+			fflush (stdout);
+			fflush (stdin);
+			_exit (0);
+		}
+		if (pid == -1)
+		{
+			/* Parent context, fork() failed */
+
+			PrintText (sess, "Error in fork(2)\n");
+			close(fds[0]);
+			close(fds[1]);
+		} else
+		{
+			/* Parent path */
+			close(fds[1]);
+			s->childpid = pid;
+			s->iotag = fe_input_add (s->myfd, FIA_READ|FIA_EX, exec_data, s);
+			sess->running_exec = s;
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+#endif
+
+static int
+cmd_flushq (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	sprintf (tbuf, "Flushing server send queue, %d bytes.\n", sess->server->sendq_len);
+	PrintText (sess, tbuf);
+	sess->server->flush_queue (sess->server);
+	return TRUE;
+}
+
+static int
+cmd_quit (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (*word_eol[2])
+		sess->quitreason = word_eol[2];
+	sess->server->disconnect (sess, TRUE, -1);
+	sess->quitreason = NULL;
+	return 2;
+}
+
+static int
+cmd_gate (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *server_name = word[2];
+	server *serv = sess->server;
+	if (*server_name)
+	{
+		char *port = word[3];
+#ifdef USE_OPENSSL
+		serv->use_ssl = FALSE;
+#endif
+		server_fill_her_up (serv);
+		if (*port)
+			serv->connect (serv, server_name, atoi (port), TRUE);
+		else
+			serv->connect (serv, server_name, 23, TRUE);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+typedef struct
+{
+	char *cmd;
+	session *sess;
+} getvalinfo;
+
+static void
+get_int_cb (int cancel, int val, getvalinfo *info)
+{
+	char buf[512];
+
+	if (!cancel)
+	{
+		snprintf (buf, sizeof (buf), "%s %d", info->cmd, val);
+		if (is_session (info->sess))
+			handle_command (info->sess, buf, FALSE);
+	}
+
+	free (info->cmd);
+	free (info);
+}
+
+static int
+cmd_getint (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	getvalinfo *info;
+
+	if (!word[4][0])
+		return FALSE;
+
+	info = malloc (sizeof (*info));
+	info->cmd = strdup (word[3]);
+	info->sess = sess;
+
+	fe_get_int (word[4], atoi (word[2]), get_int_cb, info);
+
+	return TRUE;
+}
+
+static void
+get_file_cb (char *cmd, char *file)
+{
+	char buf[1024 + 128];
+
+	/* execute the command once per file, then once more with
+      no args */
+	if (file)
+	{
+		snprintf (buf, sizeof (buf), "%s %s", cmd, file);
+		handle_command (current_sess, buf, FALSE);
+	}
+	else
+	{
+		handle_command (current_sess, cmd, FALSE);
+		free (cmd);
+	}
+}
+
+static int
+cmd_getfile (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int idx = 2;
+	int flags = 0;
+
+	if (!word[3][0])
+		return FALSE;
+
+	if (!strcmp (word[2], "-folder"))
+	{
+		flags |= FRF_CHOOSEFOLDER;
+		idx++;
+	}
+
+	if (!strcmp (word[idx], "-multi"))
+	{
+		flags |= FRF_MULTIPLE;
+		idx++;
+	}
+
+	if (!strcmp (word[idx], "-save"))
+	{
+		flags |= FRF_WRITE;
+		idx++;
+	}
+
+	fe_get_file (word[idx+1], word[idx+2], (void *)get_file_cb, strdup (word[idx]), flags);
+
+	return TRUE;
+}
+
+static void
+get_str_cb (int cancel, char *val, getvalinfo *info)
+{
+	char buf[512];
+
+	if (!cancel)
+	{
+		snprintf (buf, sizeof (buf), "%s %s", info->cmd, val);
+		if (is_session (info->sess))
+			handle_command (info->sess, buf, FALSE);
+	}
+
+	free (info->cmd);
+	free (info);
+}
+
+static int
+cmd_getstr (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	getvalinfo *info;
+
+	if (!word[4][0])
+		return FALSE;
+
+	info = malloc (sizeof (*info));
+	info->cmd = strdup (word[3]);
+	info->sess = sess;
+
+	fe_get_str (word[4], word[2], get_str_cb, info);
+
+	return TRUE;
+}
+
+static int
+cmd_ghost (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (!word[2][0])
+		return FALSE;
+
+	sess->server->p_ns_ghost (sess->server, word[2], word[3]);
+	return TRUE;
+}
+
+static int
+cmd_gui (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	switch (str_ihash (word[2]))
+	{
+	case 0x058b836e: fe_ctrl_gui (sess, 8, 0); break; /* APPLY */
+	case 0xac1eee45: fe_ctrl_gui (sess, 7, 2); break; /* ATTACH */
+	case 0x05a72f63: fe_ctrl_gui (sess, 4, atoi (word[3])); break; /* COLOR */
+	case 0xb06a1793: fe_ctrl_gui (sess, 7, 1); break; /* DETACH */
+	case 0x05cfeff0: fe_ctrl_gui (sess, 3, 0); break; /* FLASH */
+	case 0x05d154d8: fe_ctrl_gui (sess, 2, 0); break; /* FOCUS */
+	case 0x0030dd42: fe_ctrl_gui (sess, 0, 0); break; /* HIDE */
+	case 0x61addbe3: fe_ctrl_gui (sess, 5, 0); break; /* ICONIFY */
+	case 0xc0851aaa: fe_message (word[3], FE_MSG_INFO|FE_MSG_MARKUP); break; /* MSGBOX */
+	case 0x0035dafd: fe_ctrl_gui (sess, 1, 0); break; /* SHOW */
+	case 0x0033155f: /* MENU */
+		if (!strcasecmp (word[3], "TOGGLE"))
+			fe_ctrl_gui (sess, 6, 0);
+		else
+			return FALSE;
+		break;
+	default:
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+typedef struct
+{
+	int longfmt;
+	int i, t;
+	char *buf;
+} help_list;
+
+static void
+show_help_line (session *sess, help_list *hl, char *name, char *usage)
+{
+	int j, len, max;
+	char *p;
+
+	if (name[0] == '.')	/* hidden command? */
+		return;
+
+	if (hl->longfmt)	/* long format for /HELP -l */
+	{
+		if (!usage || usage[0] == 0)
+			PrintTextf (sess, "   \0034%s\003 :\n", name);
+		else
+			PrintTextf (sess, "   \0034%s\003 : %s\n", name, _(usage));
+		return;
+	}
+
+	/* append the name into buffer, but convert to uppercase */
+	len = strlen (hl->buf);
+	p = name;
+	while (*p)
+	{
+		hl->buf[len] = toupper ((unsigned char) *p);
+		len++;
+		p++;
+	}
+	hl->buf[len] = 0;
+
+	hl->t++;
+	if (hl->t == 5)
+	{
+		hl->t = 0;
+		strcat (hl->buf, "\n");
+		PrintText (sess, hl->buf);
+		hl->buf[0] = ' ';
+		hl->buf[1] = ' ';
+		hl->buf[2] = 0;
+	} else
+	{
+		/* append some spaces after the command name */
+		max = strlen (name);
+		if (max < 10)
+		{
+			max = 10 - max;
+			for (j = 0; j < max; j++)
+			{
+				hl->buf[len] = ' ';
+				len++;
+				hl->buf[len] = 0;
+			}
+		}
+	}
+}
+
+static int
+cmd_help (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int i = 0, longfmt = 0;
+	char *helpcmd = "";
+	GSList *list;
+
+	if (tbuf)
+		helpcmd = word[2];
+	if (*helpcmd && strcmp (helpcmd, "-l") == 0)
+		longfmt = 1;
+
+	if (*helpcmd && !longfmt)
+	{
+		help (sess, tbuf, helpcmd, FALSE);
+	} else
+	{
+		struct popup *pop;
+		char *buf = malloc (4096);
+		help_list hl;
+
+		hl.longfmt = longfmt;
+		hl.buf = buf;
+
+		PrintTextf (sess, "\n%s\n\n", _("Commands Available:"));
+		buf[0] = ' ';
+		buf[1] = ' ';
+		buf[2] = 0;
+		hl.t = 0;
+		hl.i = 0;
+		while (xc_cmds[i].name)
+		{
+			show_help_line (sess, &hl, xc_cmds[i].name, xc_cmds[i].help);
+			i++;
+		}
+		strcat (buf, "\n");
+		PrintText (sess, buf);
+
+		PrintTextf (sess, "\n%s\n\n", _("User defined commands:"));
+		buf[0] = ' ';
+		buf[1] = ' ';
+		buf[2] = 0;
+		hl.t = 0;
+		hl.i = 0;
+		list = command_list;
+		while (list)
+		{
+			pop = list->data;
+			show_help_line (sess, &hl, pop->name, pop->cmd);
+			list = list->next;
+		}
+		strcat (buf, "\n");
+		PrintText (sess, buf);
+
+		PrintTextf (sess, "\n%s\n\n", _("Plugin defined commands:"));
+		buf[0] = ' ';
+		buf[1] = ' ';
+		buf[2] = 0;
+		hl.t = 0;
+		hl.i = 0;
+		plugin_command_foreach (sess, &hl, (void *)show_help_line);
+		strcat (buf, "\n");
+		PrintText (sess, buf);
+		free (buf);
+
+		PrintTextf (sess, "\n%s\n\n", _("Type /HELP <command> for more information, or /HELP -l"));
+	}
+	return TRUE;
+}
+
+static int
+cmd_id (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (word[2][0])
+	{
+		sess->server->p_ns_identify (sess->server, word[2]);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int
+cmd_ignore (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int i;
+	int type = 0;
+	int quiet = 0;
+	char *mask;
+
+	if (!*word[2])
+	{
+		ignore_showlist (sess);
+		return TRUE;
+	}
+	if (!*word[3])
+		return FALSE;
+
+	i = 3;
+	while (1)
+	{
+		if (!*word[i])
+		{
+			if (type == 0)
+				return FALSE;
+
+			mask = word[2];
+			if (strchr (mask, '?') == NULL &&
+			    strchr (mask, '*') == NULL &&
+			    userlist_find (sess, mask))
+			{
+				mask = tbuf;
+				snprintf (tbuf, TBUFSIZE, "%s!*@*", word[2]);
+			}
+
+			i = ignore_add (mask, type);
+			if (quiet)
+				return TRUE;
+			switch (i)
+			{
+			case 1:
+				EMIT_SIGNAL (XP_TE_IGNOREADD, sess, mask, NULL, NULL, NULL, 0);
+				break;
+			case 2:	/* old ignore changed */
+				EMIT_SIGNAL (XP_TE_IGNORECHANGE, sess, mask, NULL, NULL, NULL, 0);
+			}
+			return TRUE;
+		}
+		if (!strcasecmp (word[i], "UNIGNORE"))
+			type |= IG_UNIG;
+		else if (!strcasecmp (word[i], "ALL"))
+			type |= IG_PRIV | IG_NOTI | IG_CHAN | IG_CTCP | IG_INVI | IG_DCC;
+		else if (!strcasecmp (word[i], "PRIV"))
+			type |= IG_PRIV;
+		else if (!strcasecmp (word[i], "NOTI"))
+			type |= IG_NOTI;
+		else if (!strcasecmp (word[i], "CHAN"))
+			type |= IG_CHAN;
+		else if (!strcasecmp (word[i], "CTCP"))
+			type |= IG_CTCP;
+		else if (!strcasecmp (word[i], "INVI"))
+			type |= IG_INVI;
+		else if (!strcasecmp (word[i], "QUIET"))
+			quiet = 1;
+		else if (!strcasecmp (word[i], "NOSAVE"))
+			type |= IG_NOSAVE;
+		else if (!strcasecmp (word[i], "DCC"))
+			type |= IG_DCC;
+		else
+		{
+			sprintf (tbuf, _("Unknown arg '%s' ignored."), word[i]);
+			PrintText (sess, tbuf);
+		}
+		i++;
+	}
+}
+
+static int
+cmd_invite (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (!*word[2])
+		return FALSE;
+	if (*word[3])
+		sess->server->p_invite (sess->server, word[3], word[2]);
+	else
+		sess->server->p_invite (sess->server, sess->channel, word[2]);
+	return TRUE;
+}
+
+static int
+cmd_join (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *chan = word[2];
+	if (*chan)
+	{
+		char *po, *pass = word[3];
+		sess->server->p_join (sess->server, chan, pass);
+		if (sess->channel[0] == 0 && sess->waitchannel[0])
+		{
+			po = strchr (chan, ',');
+			if (po)
+				*po = 0;
+			safe_strcpy (sess->waitchannel, chan, CHANLEN);
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_kick (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *nick = word[2];
+	char *reason = word_eol[3];
+	if (*nick)
+	{
+		sess->server->p_kick (sess->server, sess->channel, nick, reason);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_kickban (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *nick = word[2];
+	char *reason = word_eol[3];
+	struct User *user;
+
+	if (*nick)
+	{
+		/* if the reason is a 1 digit number, treat it as a bantype */
+
+		user = userlist_find (sess, nick);
+
+		if (isdigit ((unsigned char) reason[0]) && reason[1] == 0)
+		{
+			ban (sess, tbuf, nick, reason, (user && user->op));
+			reason[0] = 0;
+		} else
+			ban (sess, tbuf, nick, "", (user && user->op));
+
+		sess->server->p_kick (sess->server, sess->channel, nick, reason);
+
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_killall (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	xchat_exit();
+	return 2;
+}
+
+static int
+cmd_lagcheck (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	lag_check ();
+	return TRUE;
+}
+
+static void
+lastlog (session *sess, char *search, gboolean regexp)
+{
+	session *lastlog_sess;
+
+	if (!is_session (sess))
+		return;
+
+	lastlog_sess = find_dialog (sess->server, "(lastlog)");
+	if (!lastlog_sess)
+		lastlog_sess = new_ircwindow (sess->server, "(lastlog)", SESS_DIALOG, 0);
+
+	lastlog_sess->lastlog_sess = sess;
+	lastlog_sess->lastlog_regexp = regexp;	/* remember the search type */
+
+	fe_text_clear (lastlog_sess, 0);
+	fe_lastlog (sess, lastlog_sess, search, regexp);
+}
+
+static int
+cmd_lastlog (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (*word_eol[2])
+	{
+		if (!strcmp (word[2], "-r"))
+			lastlog (sess, word_eol[3], TRUE);
+		else
+			lastlog (sess, word_eol[2], FALSE);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int
+cmd_list (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	sess->server->p_list_channels (sess->server, word_eol[2], 1);
+
+	return TRUE;
+}
+
+gboolean
+load_perform_file (session *sess, char *file)
+{
+	char tbuf[1024 + 4];
+	char *nl;
+	FILE *fp;
+
+	fp = xchat_fopen_file (file, "r", XOF_FULLPATH);
+	if (!fp)
+		return FALSE;
+
+	tbuf[1024] = 0;
+	while (fgets (tbuf, 1024, fp))
+	{
+		nl = strchr (tbuf, '\n');
+		if (nl == tbuf) /* skip empty commands */
+			continue;
+		if (nl)
+			*nl = 0;
+		if (tbuf[0] == prefs.cmdchar[0])
+			handle_command (sess, tbuf + 1, TRUE);
+		else
+			handle_command (sess, tbuf, TRUE);
+	}
+	fclose (fp);
+	return TRUE;
+}
+
+static int
+cmd_load (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *error, *arg, *file;
+	int len;
+
+	if (!word[2][0])
+		return FALSE;
+
+	if (strcmp (word[2], "-e") == 0)
+	{
+		file = expand_homedir (word[3]);
+		if (!load_perform_file (sess, file))
+		{
+			PrintTextf (sess, _("Cannot access %s\n"), file);
+			PrintText (sess, errorstring (errno));
+		}
+		free (file);
+		return TRUE;
+	}
+
+#ifdef USE_PLUGIN
+	len = strlen (word[2]);
+#ifdef WIN32
+	if (len > 4 && strcasecmp (".dll", word[2] + len - 4) == 0)
+#else
+#if defined(__hpux)
+	if (len > 3 && strcasecmp (".sl", word[2] + len - 3) == 0)
+#else
+	if (len > 3 && strcasecmp (".so", word[2] + len - 3) == 0)
+#endif
+#endif
+	{
+		arg = NULL;
+		if (word_eol[3][0])
+			arg = word_eol[3];
+
+		file = expand_homedir (word[2]);
+		error = plugin_load (sess, file, arg);
+		free (file);
+
+		if (error)
+			PrintText (sess, error);
+
+		return TRUE;
+	}
+#endif
+
+	sprintf (tbuf, "Unknown file type %s. Maybe you need to install the Perl or Python plugin?\n", word[2]);
+	PrintText (sess, tbuf);
+
+	return FALSE;
+}
+
+static int
+cmd_me (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *act = word_eol[2];
+
+	if (!(*act))
+		return FALSE;
+
+	if (sess->type == SESS_SERVER)
+	{
+		notj_msg (sess);
+		return TRUE;
+	}
+
+	snprintf (tbuf, TBUFSIZE, "\001ACTION %s\001\r", act);
+	/* first try through DCC CHAT */
+	if (dcc_write_chat (sess->channel, tbuf))
+	{
+		/* print it to screen */
+		inbound_action (sess, sess->channel, sess->server->nick, "", act, TRUE, FALSE);
+	} else
+	{
+		/* DCC CHAT failed, try through server */
+		if (sess->server->connected)
+		{
+			sess->server->p_action (sess->server, sess->channel, act);
+			/* print it to screen */
+			inbound_action (sess, sess->channel, sess->server->nick, "", act, TRUE, FALSE);
+		} else
+		{
+			notc_msg (sess);
+		}
+	}
+
+	return TRUE;
+}
+
+static int
+cmd_mode (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	/* +channel channels are dying, let those servers whine about modes.
+	 * return info about current channel if available and no info is given */
+	if ((*word[2] == '+') || (*word[2] == 0) || (!is_channel(sess->server, word[2]) &&
+				!(rfc_casecmp(sess->server->nick, word[2]) == 0)))
+	{
+		if(sess->channel[0] == 0)
+			return FALSE;
+		sess->server->p_mode (sess->server, sess->channel, word_eol[2]);
+	}
+	else
+		sess->server->p_mode (sess->server, word[2], word_eol[3]);
+	return TRUE;
+}
+
+static int
+mop_cb (struct User *user, multidata *data)
+{
+	if (!user->op)
+	{
+		data->nicks[data->i] = user->nick;
+		data->i++;
+	}
+	return TRUE;
+}
+
+static int
+cmd_mop (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char **nicks = malloc (sizeof (char *) * (sess->total - sess->ops));
+	multidata data;
+
+	data.nicks = nicks;
+	data.i = 0;
+	tree_foreach (sess->usertree, (tree_traverse_func *)mop_cb, &data);
+	send_channel_modes (sess, tbuf, nicks, 0, data.i, '+', 'o', 0);
+
+	free (nicks);
+
+	return TRUE;
+}
+
+static int
+cmd_msg (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *nick = word[2];
+	char *msg = word_eol[3];
+	struct session *newsess;
+
+	if (*nick)
+	{
+		if (*msg)
+		{
+			if (strcmp (nick, ".") == 0)
+			{							  /* /msg the last nick /msg'ed */
+				if (sess->lastnick[0])
+					nick = sess->lastnick;
+			} else
+			{
+				safe_strcpy (sess->lastnick, nick, NICKLEN);	/* prime the last nick memory */
+			}
+
+			if (*nick == '=')
+			{
+				nick++;
+				if (!dcc_write_chat (nick, msg))
+				{
+					EMIT_SIGNAL (XP_TE_NODCC, sess, NULL, NULL, NULL, NULL, 0);
+					return TRUE;
+				}
+			} else
+			{
+				if (!sess->server->connected)
+				{
+					notc_msg (sess);
+					return TRUE;
+				}
+				sess->server->p_message (sess->server, nick, msg);
+			}
+			newsess = find_dialog (sess->server, nick);
+			if (!newsess)
+				newsess = find_channel (sess->server, nick);
+			if (newsess)
+				inbound_chanmsg (newsess->server, NULL, newsess->channel,
+									  newsess->server->nick, msg, TRUE, FALSE);
+			else
+			{
+				/* mask out passwords */
+				if (strcasecmp (nick, "nickserv") == 0 &&
+					 strncasecmp (msg, "identify ", 9) == 0)
+					msg = "identify ****";
+				EMIT_SIGNAL (XP_TE_MSGSEND, sess, nick, msg, NULL, NULL, 0);
+			}
+
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+static int
+cmd_names (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (*word[2])
+	  	sess->server->p_names (sess->server, word[2]);
+	else
+		sess->server->p_names (sess->server, sess->channel);
+	return TRUE;
+}
+
+static int
+cmd_nctcp (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (*word_eol[3])
+	{
+		sess->server->p_nctcp (sess->server, word[2], word_eol[3]);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_newserver (struct session *sess, char *tbuf, char *word[],
+					char *word_eol[])
+{
+	if (strcmp (word[2], "-noconnect") == 0)
+	{
+		new_ircwindow (NULL, word[3], SESS_SERVER, 0);
+		return TRUE;
+	}
+	
+	sess = new_ircwindow (NULL, NULL, SESS_SERVER, 0);
+	cmd_server (sess, tbuf, word, word_eol);
+	return TRUE;
+}
+
+static int
+cmd_nick (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *nick = word[2];
+	if (*nick)
+	{
+		if (sess->server->connected)
+			sess->server->p_change_nick (sess->server, nick);
+		else
+			inbound_newnick (sess->server, sess->server->nick, nick, TRUE);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_notice (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (*word[2] && *word_eol[3])
+	{
+		sess->server->p_notice (sess->server, word[2], word_eol[3]);
+		EMIT_SIGNAL (XP_TE_NOTICESEND, sess, word[2], word_eol[3], NULL, NULL, 0);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_notify (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int i = 1;
+	char *net = NULL;
+
+	if (*word[2])
+	{
+		if (strcmp (word[2], "-n") == 0)	/* comma sep network list */
+		{
+			net = word[3];
+			i += 2;
+		}
+
+		while (1)
+		{
+			i++;
+			if (!*word[i])
+				break;
+			if (notify_deluser (word[i]))
+			{
+				EMIT_SIGNAL (XP_TE_DELNOTIFY, sess, word[i], NULL, NULL, NULL, 0);
+				return TRUE;
+			}
+
+			if (net && strcmp (net, "ASK") == 0)
+				fe_notify_ask (word[i], NULL);
+			else
+			{
+				notify_adduser (word[i], net);
+				EMIT_SIGNAL (XP_TE_ADDNOTIFY, sess, word[i], NULL, NULL, NULL, 0);
+			}
+		}
+	} else
+		notify_showlist (sess);
+	return TRUE;
+}
+
+static int
+cmd_op (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int i = 2;
+
+	while (1)
+	{
+		if (!*word[i])
+		{
+			if (i == 2)
+				return FALSE;
+			send_channel_modes (sess, tbuf, word, 2, i, '+', 'o', 0);
+			return TRUE;
+		}
+		i++;
+	}
+}
+
+static int
+cmd_part (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *chan = word[2];
+	char *reason = word_eol[3];
+	if (!*chan)
+		chan = sess->channel;
+	if ((*chan) && is_channel (sess->server, chan))
+	{
+		if (reason[0] == 0)
+			reason = NULL;
+		server_sendpart (sess->server, chan, reason);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_ping (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char timestring[64];
+	unsigned long tim;
+	char *to = word[2];
+
+	tim = make_ping_time ();
+
+	snprintf (timestring, sizeof (timestring), "%lu", tim);
+	sess->server->p_ping (sess->server, to, timestring);
+
+	return TRUE;
+}
+
+void
+open_query (server *serv, char *nick, gboolean focus_existing)
+{
+	session *sess;
+
+	sess = find_dialog (serv, nick);
+	if (!sess)
+		new_ircwindow (serv, nick, SESS_DIALOG, 1);
+	else if (focus_existing)
+		fe_ctrl_gui (sess, 2, 0);	/* bring-to-front */
+}
+
+static int
+cmd_query (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *nick = word[2];
+	gboolean focus = TRUE;
+
+	if (strcmp (word[2], "-nofocus") == 0)
+	{
+		nick = word[3];
+		focus = FALSE;
+	}
+
+	if (*nick && !is_channel (sess->server, nick))
+	{
+		open_query (sess->server, nick, focus);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_quote (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *raw = word_eol[2];
+
+	return sess->server->p_raw (sess->server, raw);
+}
+
+static int
+cmd_reconnect (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int tmp = prefs.recon_delay;
+	GSList *list;
+	server *serv = sess->server;
+
+	prefs.recon_delay = 0;
+
+	if (!strcasecmp (word[2], "ALL"))
+	{
+		list = serv_list;
+		while (list)
+		{
+			serv = list->data;
+			if (serv->connected)
+				serv->auto_reconnect (serv, TRUE, -1);
+			list = list->next;
+		}
+	}
+	/* If it isn't "ALL" and there is something
+	there it *should* be a server they are trying to connect to*/
+	else if (*word[2])
+	{
+		int offset = 0;
+#ifdef USE_OPENSSL
+		int use_ssl = FALSE;
+
+		if (strcmp (word[2], "-ssl") == 0)
+		{
+			use_ssl = TRUE;
+			offset++;	/* args move up by 1 word */
+		}
+		serv->use_ssl = use_ssl;
+		serv->accept_invalid_cert = TRUE;
+#endif
+
+		if (*word[4+offset])
+			safe_strcpy (serv->password, word[4+offset], sizeof (serv->password));
+		if (*word[3+offset])
+			serv->port = atoi (word[3+offset]);
+		safe_strcpy (serv->hostname, word[2+offset], sizeof (serv->hostname));
+		serv->auto_reconnect (serv, TRUE, -1);
+	}
+	else
+	{
+		serv->auto_reconnect (serv, TRUE, -1);
+	}
+	prefs.recon_delay = tmp;
+
+	return TRUE;
+}
+
+static int
+cmd_recv (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (*word_eol[2])
+	{
+		sess->server->p_inline (sess->server, word_eol[2], strlen (word_eol[2]));
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int
+cmd_say (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	char *speech = word_eol[2];
+	if (*speech)
+	{
+		handle_say (sess, speech, FALSE);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_send (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	guint32 addr;
+	socklen_t len;
+	struct sockaddr_in SAddr;
+
+	if (!word[2][0])
+		return FALSE;
+
+	addr = dcc_get_my_address ();
+	if (addr == 0)
+	{
+		/* use the one from our connected server socket */
+		memset (&SAddr, 0, sizeof (struct sockaddr_in));
+		len = sizeof (SAddr);
+		getsockname (sess->server->sok, (struct sockaddr *) &SAddr, &len);
+		addr = SAddr.sin_addr.s_addr;
+	}
+	addr = ntohl (addr);
+
+	if ((addr & 0xffff0000) == 0xc0a80000 ||	/* 192.168.x.x */
+		 (addr & 0xff000000) == 0x0a000000)		/* 10.x.x.x */
+		/* we got a private net address, let's PSEND or it'll fail */
+		snprintf (tbuf, 512, "DCC PSEND %s", word_eol[2]);
+	else
+		snprintf (tbuf, 512, "DCC SEND %s", word_eol[2]);
+
+	handle_command (sess, tbuf, FALSE);
+
+	return TRUE;
+}
+
+static int
+cmd_setcursor (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int delta = FALSE;
+
+	if (*word[2])
+	{
+		if (word[2][0] == '-' || word[2][0] == '+')
+			delta = TRUE;
+		fe_set_inputbox_cursor (sess, delta, atoi (word[2]));
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int
+cmd_settab (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (*word_eol[2])
+	{
+		strcpy (tbuf, sess->channel);
+		safe_strcpy (sess->channel, word_eol[2], CHANLEN);
+		fe_set_channel (sess);
+		strcpy (sess->channel, tbuf);
+	}
+
+	return TRUE;
+}
+
+static int
+cmd_settext (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	fe_set_inputbox_contents (sess, word_eol[2]);
+	return TRUE;
+}
+
+static int
+cmd_splay (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (*word[2])
+	{
+		sound_play (word[2], FALSE);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int
+parse_irc_url (char *url, char *server_name[], char *port[], char *channel[], int *use_ssl)
+{
+	char *co;
+#ifdef USE_OPENSSL
+	if (strncasecmp ("ircs://", url, 7) == 0)
+	{
+		*use_ssl = TRUE;
+		*server_name = url + 7;
+		goto urlserv;
+	}
+#endif
+
+	if (strncasecmp ("irc://", url, 6) == 0)
+	{
+		*server_name = url + 6;
+#ifdef USE_OPENSSL
+urlserv:
+#endif
+		/* check for port */
+		co = strchr (*server_name, ':');
+		if (co)
+		{
+			*port = co + 1;
+			*co = 0;
+		} else
+			co = *server_name;
+		/* check for channel - mirc style */
+		co = strchr (co + 1, '/');
+		if (co)
+		{
+			*co = 0;
+			co++;
+			if (*co == '#')
+				*channel = co+1;
+			else
+				*channel = co;
+			
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_server (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int offset = 0;
+	char *server_name = NULL;
+	char *port = NULL;
+	char *pass = NULL;
+	char *channel = NULL;
+	int use_ssl = FALSE;
+	int is_url = TRUE;
+	server *serv = sess->server;
+
+#ifdef USE_OPENSSL
+	/* BitchX uses -ssl, mIRC uses -e, let's support both */
+	if (strcmp (word[2], "-ssl") == 0 || strcmp (word[2], "-e") == 0)
+	{
+		use_ssl = TRUE;
+		offset++;	/* args move up by 1 word */
+	}
+#endif
+
+	if (!parse_irc_url (word[2 + offset], &server_name, &port, &channel, &use_ssl))
+	{
+		is_url = FALSE;
+		server_name = word[2 + offset];
+	}
+	if (port)
+		pass = word[3 + offset];
+	else
+	{
+		port = word[3 + offset];
+		pass = word[4 + offset];
+	}
+	
+	if (!(*server_name))
+		return FALSE;
+
+	sess->server->network = NULL;
+
+	/* dont clear it for /servchan */
+	if (strncasecmp (word_eol[1], "SERVCHAN ", 9))
+		sess->willjoinchannel[0] = 0;
+
+	if (channel)
+	{
+		sess->willjoinchannel[0] = '#';
+		safe_strcpy ((sess->willjoinchannel + 1), channel, (CHANLEN - 1));
+	}
+
+	/* support +7000 style ports like mIRC */
+	if (port[0] == '+')
+	{
+		port++;
+#ifdef USE_OPENSSL
+		use_ssl = TRUE;
+#endif
+	}
+
+	if (*pass)
+	{
+		safe_strcpy (serv->password, pass, sizeof (serv->password));
+	}
+#ifdef USE_OPENSSL
+	serv->use_ssl = use_ssl;
+	serv->accept_invalid_cert = TRUE;
+#endif
+
+	/* try to connect by Network name */
+	if (servlist_connect_by_netname (sess, server_name, !is_url))
+		return TRUE;
+
+	if (*port)
+	{
+		serv->connect (serv, server_name, atoi (port), FALSE);
+	} else
+	{
+		/* -1 for default port */
+		serv->connect (serv, server_name, -1, FALSE);
+	}
+
+	/* try to associate this connection with a listed network */
+	if (!serv->network)
+		/* search for this hostname in the entire server list */
+		serv->network = servlist_net_find_from_server (server_name);
+		/* may return NULL, but that's OK */
+
+	return TRUE;
+}
+
+static int
+cmd_servchan (struct session *sess, char *tbuf, char *word[],
+				  char *word_eol[])
+{
+	int offset = 0;
+
+#ifdef USE_OPENSSL
+	if (strcmp (word[2], "-ssl") == 0)
+		offset++;
+#endif
+
+	if (*word[4 + offset])
+	{
+		safe_strcpy (sess->willjoinchannel, word[4 + offset], CHANLEN);
+		return cmd_server (sess, tbuf, word, word_eol);
+	}
+
+	return FALSE;
+}
+
+static int
+cmd_topic (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (word[2][0] && is_channel (sess->server, word[2]))
+		sess->server->p_topic (sess->server, word[2], word_eol[3]);
+	else
+		sess->server->p_topic (sess->server, sess->channel, word_eol[2]);
+	return TRUE;
+}
+
+static int
+cmd_tray (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (strcmp (word[2], "-b") == 0)
+	{
+		fe_tray_set_balloon (word[3], word[4][0] ? word[4] : NULL);
+		return TRUE;
+	}
+
+	if (strcmp (word[2], "-t") == 0)
+	{
+		fe_tray_set_tooltip (word[3][0] ? word[3] : NULL);
+		return TRUE;
+	}
+
+	if (strcmp (word[2], "-i") == 0)
+	{
+		fe_tray_set_icon (atoi (word[3]));
+		return TRUE;
+	}
+
+	if (strcmp (word[2], "-f") != 0)
+		return FALSE;
+
+	if (!word[3][0])
+	{
+		fe_tray_set_file (NULL);	/* default xchat icon */
+		return TRUE;
+	}
+
+	if (!word[4][0])
+	{
+		fe_tray_set_file (word[3]);	/* fixed custom icon */
+		return TRUE;
+	}
+
+	/* flash between 2 icons */
+	fe_tray_set_flash (word[4], word[5][0] ? word[5] : NULL, atoi (word[3]));
+	return TRUE;
+}
+
+static int
+cmd_unignore (struct session *sess, char *tbuf, char *word[],
+				  char *word_eol[])
+{
+	char *mask = word[2];
+	char *arg = word[3];
+	if (*mask)
+	{
+		if (ignore_del (mask, NULL))
+		{
+			if (strcasecmp (arg, "QUIET"))
+				EMIT_SIGNAL (XP_TE_IGNOREREMOVE, sess, mask, NULL, NULL, NULL, 0);
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_unload (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+#ifdef USE_PLUGIN
+	int len, by_file = FALSE;
+
+	len = strlen (word[2]);
+#ifdef WIN32
+	if (len > 4 && strcasecmp (word[2] + len - 4, ".dll") == 0)
+#else
+#if defined(__hpux)
+	if (len > 3 && strcasecmp (word[2] + len - 3, ".sl") == 0)
+#else
+	if (len > 3 && strcasecmp (word[2] + len - 3, ".so") == 0)
+#endif
+#endif
+		by_file = TRUE;
+
+	switch (plugin_kill (word[2], by_file))
+	{
+	case 0:
+			PrintText (sess, _("No such plugin found.\n"));
+			break;
+	case 1:
+			return TRUE;
+	case 2:
+			PrintText (sess, _("That plugin is refusing to unload.\n"));
+			break;
+	}
+#endif
+
+	return FALSE;
+}
+
+static server *
+find_server_from_hostname (char *hostname)
+{
+	GSList *list = serv_list;
+	server *serv;
+
+	while (list)
+	{
+		serv = list->data;
+		if (!strcasecmp (hostname, serv->hostname) && serv->connected)
+			return serv;
+		list = list->next;
+	}
+
+	return NULL;
+}
+
+static server *
+find_server_from_net (void *net)
+{
+	GSList *list = serv_list;
+	server *serv;
+
+	while (list)
+	{
+		serv = list->data;
+		if (serv->network == net && serv->connected)
+			return serv;
+		list = list->next;
+	}
+
+	return NULL;
+}
+
+static void
+url_join_only (server *serv, char *tbuf, char *channel)
+{
+	/* already connected, JOIN only. FIXME: support keys? */
+	tbuf[0] = '#';
+	/* tbuf is 4kb */
+	safe_strcpy ((tbuf + 1), channel, 256);
+	serv->p_join (serv, tbuf, "");
+}
+
+static int
+cmd_url (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	if (word[2][0])
+	{
+		char *server_name = NULL;
+		char *port = NULL;
+		char *channel = NULL;
+		char *url = g_strdup (word[2]);
+		int use_ssl = FALSE;
+		void *net;
+		server *serv;
+
+		if (parse_irc_url (url, &server_name, &port, &channel, &use_ssl))
+		{
+			/* maybe we're already connected to this net */
+
+			/* check for "FreeNode" */
+			net = servlist_net_find (server_name, NULL, strcasecmp);
+			/* check for "irc.eu.freenode.net" */
+			if (!net)
+				net = servlist_net_find_from_server (server_name);
+
+			if (net)
+			{
+				/* found the network, but are we connected? */
+				serv = find_server_from_net (net);
+				if (serv)
+				{
+					url_join_only (serv, tbuf, channel);
+					g_free (url);
+					return TRUE;
+				}
+			}
+			else
+			{
+				/* an un-listed connection */
+				serv = find_server_from_hostname (server_name);
+				if (serv)
+				{
+					url_join_only (serv, tbuf, channel);
+					g_free (url);
+					return TRUE;
+				}
+			}
+
+			/* not connected to this net, open new window */
+			cmd_newserver (sess, tbuf, word, word_eol);
+
+		} else
+			fe_open_url (word[2]);
+		g_free (url);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int
+userlist_cb (struct User *user, session *sess)
+{
+	time_t lt;
+
+	if (!user->lasttalk)
+		lt = 0;
+	else
+		lt = time (0) - user->lasttalk;
+	PrintTextf (sess,
+				"\00306%s\t\00314[\00310%-38s\00314] \017ov\0033=\017%d%d away=%u lt\0033=\017%d\n",
+				user->nick, user->hostname, user->op, user->voice, user->away, lt);
+
+	return TRUE;
+}
+
+static int
+cmd_uselect (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int idx = 2;
+	int clear = TRUE;
+	int scroll = FALSE;
+
+	if (strcmp (word[2], "-a") == 0)	/* ADD (don't clear selections) */
+	{
+		clear = FALSE;
+		idx++;
+	}
+	if (strcmp (word[idx], "-s") == 0)	/* SCROLL TO */
+	{
+		scroll = TRUE;
+		idx++;
+	}
+	/* always valid, no args means clear the selection list */
+	fe_uselect (sess, word + idx, clear, scroll);
+	return TRUE;
+}
+
+static int
+cmd_userlist (struct session *sess, char *tbuf, char *word[],
+				  char *word_eol[])
+{
+	tree_foreach (sess->usertree, (tree_traverse_func *)userlist_cb, sess);
+	return TRUE;
+}
+
+static int
+wallchop_cb (struct User *user, multidata *data)
+{
+	if (user->op)
+	{
+		if (data->i)
+			strcat (data->tbuf, ",");
+		strcat (data->tbuf, user->nick);
+		data->i++;
+	}
+	if (data->i == 5)
+	{
+		data->i = 0;
+		sprintf (data->tbuf + strlen (data->tbuf),
+					" :[@%s] %s", data->sess->channel, data->reason);
+		data->sess->server->p_raw (data->sess->server, data->tbuf);
+		strcpy (data->tbuf, "NOTICE ");
+	}
+
+	return TRUE;
+}
+
+static int
+cmd_wallchop (struct session *sess, char *tbuf, char *word[],
+				  char *word_eol[])
+{
+	multidata data;
+
+	if (!(*word_eol[2]))
+		return FALSE;
+
+	strcpy (tbuf, "NOTICE ");
+
+	data.reason = word_eol[2];
+	data.tbuf = tbuf;
+	data.i = 0;
+	data.sess = sess;
+	tree_foreach (sess->usertree, (tree_traverse_func*)wallchop_cb, &data);
+
+	if (data.i)
+	{
+		sprintf (tbuf + strlen (tbuf),
+					" :[@%s] %s", sess->channel, word_eol[2]);
+		sess->server->p_raw (sess->server, tbuf);
+	}
+
+	return TRUE;
+}
+
+static int
+cmd_wallchan (struct session *sess, char *tbuf, char *word[],
+				  char *word_eol[])
+{
+	GSList *list;
+
+	if (*word_eol[2])
+	{
+		list = sess_list;
+		while (list)
+		{
+			sess = list->data;
+			if (sess->type == SESS_CHANNEL)
+			{
+				inbound_chanmsg (sess->server, NULL, sess->channel,
+									  sess->server->nick, word_eol[2], TRUE, FALSE);
+				sess->server->p_message (sess->server, sess->channel, word_eol[2]);
+			}
+			list = list->next;
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static int
+cmd_hop (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int i = 2;
+
+	while (1)
+	{
+		if (!*word[i])
+		{
+			if (i == 2)
+				return FALSE;
+			send_channel_modes (sess, tbuf, word, 2, i, '+', 'h', 0);
+			return TRUE;
+		}
+		i++;
+	}
+}
+
+static int
+cmd_voice (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+	int i = 2;
+
+	while (1)
+	{
+		if (!*word[i])
+		{
+			if (i == 2)
+				return FALSE;
+			send_channel_modes (sess, tbuf, word, 2, i, '+', 'v', 0);
+			return TRUE;
+		}
+		i++;
+	}
+}
+
+/* *MUST* be kept perfectly sorted for the bsearch to work */
+const struct commands xc_cmds[] = {
+	{"ADDBUTTON", cmd_addbutton, 0, 0, 1,
+	 N_("ADDBUTTON <name> <action>, adds a button under the user-list")},
+	{"ALLCHAN", cmd_allchannels, 0, 0, 1,
+	 N_("ALLCHAN <cmd>, sends a command to all channels you're in")},
+	{"ALLCHANL", cmd_allchannelslocal, 0, 0, 1,
+	 N_("ALLCHANL <cmd>, sends a command to all channels you're in")},
+	{"ALLSERV", cmd_allservers, 0, 0, 1,
+	 N_("ALLSERV <cmd>, sends a command to all servers you're in")},
+	{"AWAY", cmd_away, 1, 0, 1, N_("AWAY [<reason>], sets you away")},
+	{"BACK", cmd_back, 1, 0, 1, N_("BACK, sets you back (not away)")},
+	{"BAN", cmd_ban, 1, 1, 1,
+	 N_("BAN <mask> [<bantype>], bans everyone matching the mask from the current channel. If they are already on the channel this doesn't kick them (needs chanop)")},
+	{"CHANOPT", cmd_chanopt, 0, 0, 1, N_("CHANOPT [-quiet] <variable> [<value>]")},
+	{"CHARSET", cmd_charset, 0, 0, 1, 0},
+	{"CLEAR", cmd_clear, 0, 0, 1, N_("CLEAR [ALL|HISTORY], Clears the current text window or command history")},
+	{"CLOSE", cmd_close, 0, 0, 1, N_("CLOSE, Closes the current window/tab")},
+
+	{"COUNTRY", cmd_country, 0, 0, 1,
+	 N_("COUNTRY [-s] <code|wildcard>, finds a country code, eg: au = australia")},
+	{"CTCP", cmd_ctcp, 1, 0, 1,
+	 N_("CTCP <nick> <message>, send the CTCP message to nick, common messages are VERSION and USERINFO")},
+	{"CYCLE", cmd_cycle, 1, 1, 1,
+	 N_("CYCLE [<channel>], parts the current or given channel and immediately rejoins")},
+	{"DCC", cmd_dcc, 0, 0, 1,
+	 N_("\n"
+	 "DCC GET <nick>                      - accept an offered file\n"
+	 "DCC SEND [-maxcps=#] <nick> [file]  - send a file to someone\n"
+	 "DCC PSEND [-maxcps=#] <nick> [file] - send a file using passive mode\n"
+	 "DCC LIST                            - show DCC list\n"
+	 "DCC CHAT <nick>                     - offer DCC CHAT to someone\n"
+	 "DCC PCHAT <nick>                    - offer DCC CHAT using passive mode\n"
+	 "DCC CLOSE <type> <nick> <file>         example:\n"
+	 "         /dcc close send johnsmith file.tar.gz")},
+	{"DEBUG", cmd_debug, 0, 0, 1, 0},
+
+	{"DEHOP", cmd_dehop, 1, 1, 1,
+	 N_("DEHOP <nick>, removes chanhalf-op status from the nick on the current channel (needs chanop)")},
+	{"DELBUTTON", cmd_delbutton, 0, 0, 1,
+	 N_("DELBUTTON <name>, deletes a button from under the user-list")},
+	{"DEOP", cmd_deop, 1, 1, 1,
+	 N_("DEOP <nick>, removes chanop status from the nick on the current channel (needs chanop)")},
+	{"DEVOICE", cmd_devoice, 1, 1, 1,
+	 N_("DEVOICE <nick>, removes voice status from the nick on the current channel (needs chanop)")},
+	{"DISCON", cmd_discon, 0, 0, 1, N_("DISCON, Disconnects from server")},
+	{"DNS", cmd_dns, 0, 0, 1, N_("DNS <nick|host|ip>, Finds a users IP number")},
+	{"ECHO", cmd_echo, 0, 0, 1, N_("ECHO <text>, Prints text locally")},
+#ifndef WIN32
+	{"EXEC", cmd_exec, 0, 0, 1,
+	 N_("EXEC [-o] <command>, runs the command. If -o flag is used then output is sent to current channel, else is printed to current text box")},
+#ifndef __EMX__
+	{"EXECCONT", cmd_execc, 0, 0, 1, N_("EXECCONT, sends the process SIGCONT")},
+#endif
+	{"EXECKILL", cmd_execk, 0, 0, 1,
+	 N_("EXECKILL [-9], kills a running exec in the current session. If -9 is given the process is SIGKILL'ed")},
+#ifndef __EMX__
+	{"EXECSTOP", cmd_execs, 0, 0, 1, N_("EXECSTOP, sends the process SIGSTOP")},
+	{"EXECWRITE", cmd_execw, 0, 0, 1, N_("EXECWRITE, sends data to the processes stdin")},
+#endif
+#endif
+	{"FLUSHQ", cmd_flushq, 0, 0, 1,
+	 N_("FLUSHQ, flushes the current server's send queue")},
+	{"GATE", cmd_gate, 0, 0, 1,
+	 N_("GATE <host> [<port>], proxies through a host, port defaults to 23")},
+	{"GETFILE", cmd_getfile, 0, 0, 1, "GETFILE [-folder] [-multi] [-save] <command> <title> [<initial>]"},
+	{"GETINT", cmd_getint, 0, 0, 1, "GETINT <default> <command> <prompt>"},
+	{"GETSTR", cmd_getstr, 0, 0, 1, "GETSTR <default> <command> <prompt>"},
+	{"GHOST", cmd_ghost, 1, 0, 1, N_("GHOST <nick> [password], Kills a ghosted nickname")},
+	{"GUI", cmd_gui, 0, 0, 1, "GUI [APPLY|ATTACH|DETACH|SHOW|HIDE|FOCUS|FLASH|ICONIFY|COLOR <n>]\n"
+									  "       GUI [MSGBOX <text>|MENU TOGGLE]"},
+	{"HELP", cmd_help, 0, 0, 1, 0},
+	{"HOP", cmd_hop, 1, 1, 1,
+	 N_("HOP <nick>, gives chanhalf-op status to the nick (needs chanop)")},
+	{"ID", cmd_id, 1, 0, 1, N_("ID <password>, identifies yourself to nickserv")},
+	{"IGNORE", cmd_ignore, 0, 0, 1,
+	 N_("IGNORE <mask> <types..> <options..>\n"
+	 "    mask - host mask to ignore, eg: *!*@*.aol.com\n"
+	 "    types - types of data to ignore, one or all of:\n"
+	 "            PRIV, CHAN, NOTI, CTCP, DCC, INVI, ALL\n"
+	 "    options - NOSAVE, QUIET")},
+
+	{"INVITE", cmd_invite, 1, 0, 1,
+	 N_("INVITE <nick> [<channel>], invites someone to a channel, by default the current channel (needs chanop)")},
+	{"JOIN", cmd_join, 1, 0, 0, N_("JOIN <channel>, joins the channel")},
+	{"KICK", cmd_kick, 1, 1, 1,
+	 N_("KICK <nick>, kicks the nick from the current channel (needs chanop)")},
+	{"KICKBAN", cmd_kickban, 1, 1, 1,
+	 N_("KICKBAN <nick>, bans then kicks the nick from the current channel (needs chanop)")},
+	{"KILLALL", cmd_killall, 0, 0, 1, "KILLALL, immediately exit"},
+	{"LAGCHECK", cmd_lagcheck, 0, 0, 1,
+	 N_("LAGCHECK, forces a new lag check")},
+	{"LASTLOG", cmd_lastlog, 0, 0, 1,
+	 N_("LASTLOG <string>, searches for a string in the buffer")},
+	{"LIST", cmd_list, 1, 0, 1, 0},
+	{"LOAD", cmd_load, 0, 0, 1, N_("LOAD [-e] <file>, loads a plugin or script")},
+
+	{"MDEHOP", cmd_mdehop, 1, 1, 1,
+	 N_("MDEHOP, Mass deop's all chanhalf-ops in the current channel (needs chanop)")},
+	{"MDEOP", cmd_mdeop, 1, 1, 1,
+	 N_("MDEOP, Mass deop's all chanops in the current channel (needs chanop)")},
+	{"ME", cmd_me, 0, 0, 1,
+	 N_("ME <action>, sends the action to the current channel (actions are written in the 3rd person, like /me jumps)")},
+	{"MENU", cmd_menu, 0, 0, 1, "MENU [-eX] [-i<ICONFILE>] [-k<mod>,<key>] [-m] [-pX] [-r<X,group>] [-tX] {ADD|DEL} <path> [command] [unselect command]\n"
+										 "       See http://xchat.org/docs/menu/ for more details."},
+	{"MKICK", cmd_mkick, 1, 1, 1,
+	 N_("MKICK, Mass kicks everyone except you in the current channel (needs chanop)")},
+	{"MODE", cmd_mode, 1, 0, 1, 0},
+	{"MOP", cmd_mop, 1, 1, 1,
+	 N_("MOP, Mass op's all users in the current channel (needs chanop)")},
+	{"MSG", cmd_msg, 0, 0, 1, N_("MSG <nick> <message>, sends a private message")},
+
+	{"NAMES", cmd_names, 1, 0, 1,
+	 N_("NAMES, Lists the nicks on the current channel")},
+	{"NCTCP", cmd_nctcp, 1, 0, 1,
+	 N_("NCTCP <nick> <message>, Sends a CTCP notice")},
+	{"NEWSERVER", cmd_newserver, 0, 0, 1, N_("NEWSERVER [-noconnect] <hostname> [<port>]")},
+	{"NICK", cmd_nick, 0, 0, 1, N_("NICK <nickname>, sets your nick")},
+
+	{"NOTICE", cmd_notice, 1, 0, 1,
+	 N_("NOTICE <nick/channel> <message>, sends a notice. Notices are a type of message that should be auto reacted to")},
+	{"NOTIFY", cmd_notify, 0, 0, 1,
+	 N_("NOTIFY [-n network1[,network2,...]] [<nick>], displays your notify list or adds someone to it")},
+	{"OP", cmd_op, 1, 1, 1,
+	 N_("OP <nick>, gives chanop status to the nick (needs chanop)")},
+	{"PART", cmd_part, 1, 1, 0,
+	 N_("PART [<channel>] [<reason>], leaves the channel, by default the current one")},
+	{"PING", cmd_ping, 1, 0, 1,
+	 N_("PING <nick | channel>, CTCP pings nick or channel")},
+	{"QUERY", cmd_query, 0, 0, 1,
+	 N_("QUERY [-nofocus] <nick>, opens up a new privmsg window to someone")},
+	{"QUIT", cmd_quit, 0, 0, 1,
+	 N_("QUIT [<reason>], disconnects from the current server")},
+	{"QUOTE", cmd_quote, 1, 0, 1,
+	 N_("QUOTE <text>, sends the text in raw form to the server")},
+#ifdef USE_OPENSSL
+	{"RECONNECT", cmd_reconnect, 0, 0, 1,
+	 N_("RECONNECT [-ssl] [<host>] [<port>] [<password>], Can be called just as /RECONNECT to reconnect to the current server or with /RECONNECT ALL to reconnect to all the open servers")},
+#else
+	{"RECONNECT", cmd_reconnect, 0, 0, 1,
+	 N_("RECONNECT [<host>] [<port>] [<password>], Can be called just as /RECONNECT to reconnect to the current server or with /RECONNECT ALL to reconnect to all the open servers")},
+#endif
+	{"RECV", cmd_recv, 1, 0, 1, N_("RECV <text>, send raw data to xchat, as if it was received from the irc server")},
+
+	{"SAY", cmd_say, 0, 0, 1,
+	 N_("SAY <text>, sends the text to the object in the current window")},
+	{"SEND", cmd_send, 0, 0, 1, N_("SEND <nick> [<file>]")},
+#ifdef USE_OPENSSL
+	{"SERVCHAN", cmd_servchan, 0, 0, 1,
+	 N_("SERVCHAN [-ssl] <host> <port> <channel>, connects and joins a channel")},
+#else
+	{"SERVCHAN", cmd_servchan, 0, 0, 1,
+	 N_("SERVCHAN <host> <port> <channel>, connects and joins a channel")},
+#endif
+#ifdef USE_OPENSSL
+	{"SERVER", cmd_server, 0, 0, 1,
+	 N_("SERVER [-ssl] <host> [<port>] [<password>], connects to a server, the default port is 6667 for normal connections, and 9999 for ssl connections")},
+#else
+	{"SERVER", cmd_server, 0, 0, 1,
+	 N_("SERVER <host> [<port>] [<password>], connects to a server, the default port is 6667")},
+#endif
+	{"SET", cmd_set, 0, 0, 1, N_("SET [-e] [-off|-on] [-quiet] <variable> [<value>]")},
+	{"SETCURSOR", cmd_setcursor, 0, 0, 1, N_("SETCURSOR [-|+]<position>")},
+	{"SETTAB", cmd_settab, 0, 0, 1, 0},
+	{"SETTEXT", cmd_settext, 0, 0, 1, 0},
+	{"SPLAY", cmd_splay, 0, 0, 1, "SPLAY <soundfile>"},
+	{"TOPIC", cmd_topic, 1, 1, 1,
+	 N_("TOPIC [<topic>], sets the topic if one is given, else shows the current topic")},
+	{"TRAY", cmd_tray, 0, 0, 1,
+	 N_("\nTRAY -f <timeout> <file1> [<file2>] Blink tray between two icons.\n"
+		   "TRAY -f <filename>                  Set tray to a fixed icon.\n"
+			"TRAY -i <number>                    Blink tray with an internal icon.\n"
+			"TRAY -t <text>                      Set the tray tooltip.\n"
+			"TRAY -b <title> <text>              Set the tray balloon."
+			)},
+	{"UNBAN", cmd_unban, 1, 1, 1,
+	 N_("UNBAN <mask> [<mask>...], unbans the specified masks.")},
+	{"UNIGNORE", cmd_unignore, 0, 0, 1, N_("UNIGNORE <mask> [QUIET]")},
+	{"UNLOAD", cmd_unload, 0, 0, 1, N_("UNLOAD <name>, unloads a plugin or script")},
+	{"URL", cmd_url, 0, 0, 1, N_("URL <url>, opens a URL in your browser")},
+	{"USELECT", cmd_uselect, 0, 1, 0,
+	 N_("USELECT [-a] [-s] <nick1> <nick2> etc, highlights nick(s) in channel userlist")},
+	{"USERLIST", cmd_userlist, 1, 1, 1, 0},
+	{"VOICE", cmd_voice, 1, 1, 1,
+	 N_("VOICE <nick>, gives voice status to someone (needs chanop)")},
+	{"WALLCHAN", cmd_wallchan, 1, 1, 1,
+	 N_("WALLCHAN <message>, writes the message to all channels")},
+	{"WALLCHOP", cmd_wallchop, 1, 1, 1,
+	 N_("WALLCHOP <message>, sends the message to all chanops on the current channel")},
+	{0, 0, 0, 0, 0, 0}
+};
+
+
+static int
+command_compare (const void *a, const void *b)
+{
+	return strcasecmp (a, ((struct commands *)b)->name);
+}
+
+static struct commands *
+find_internal_command (char *name)
+{
+	/* the "-1" is to skip the NULL terminator */
+	return bsearch (name, xc_cmds, (sizeof (xc_cmds) /
+				sizeof (xc_cmds[0])) - 1, sizeof (xc_cmds[0]), command_compare);
+}
+
+static void
+help (session *sess, char *tbuf, char *helpcmd, int quiet)
+{
+	struct commands *cmd;
+
+	if (plugin_show_help (sess, helpcmd))
+		return;
+
+	cmd = find_internal_command (helpcmd);
+
+	if (cmd)
+	{
+		if (cmd->help)
+		{
+			snprintf (tbuf, TBUFSIZE, _("Usage: %s\n"), _(cmd->help));
+			PrintText (sess, tbuf);
+		} else
+		{
+			if (!quiet)
+				PrintText (sess, _("\nNo help available on that command.\n"));
+		}
+		return;
+	}
+
+	if (!quiet)
+		PrintText (sess, _("No such command.\n"));
+}
+
+/* inserts %a, %c, %d etc into buffer. Also handles &x %x for word/word_eol. *
+ *   returns 2 on buffer overflow
+ *   returns 1 on success                                                    *
+ *   returns 0 on bad-args-for-user-command                                  *
+ * - word/word_eol args might be NULL                                        *
+ * - this beast is used for UserCommands, UserlistButtons and CTCP replies   */
+
+int
+auto_insert (char *dest, int destlen, unsigned char *src, char *word[],
+				 char *word_eol[], char *a, char *c, char *d, char *e, char *h,
+				 char *n, char *s)
+{
+	int num;
+	char buf[32];
+	time_t now;
+	struct tm *tm_ptr;
+	char *utf;
+	gsize utf_len;
+	char *orig = dest;
+
+	destlen--;
+
+	while (src[0])
+	{
+		if (src[0] == '%' || src[0] == '&')
+		{
+			if (isdigit ((unsigned char) src[1]))
+			{
+				if (isdigit ((unsigned char) src[2]) && isdigit ((unsigned char) src[3]))
+				{
+					buf[0] = src[1];
+					buf[1] = src[2];
+					buf[2] = src[3];
+					buf[3] = 0;
+					dest[0] = atoi (buf);
+					utf = g_locale_to_utf8 (dest, 1, 0, &utf_len, 0);
+					if (utf)
+					{
+						if ((dest - orig) + utf_len >= destlen)
+						{
+							g_free (utf);
+							return 2;
+						}
+
+						memcpy (dest, utf, utf_len);
+						g_free (utf);
+						dest += utf_len;
+					}
+					src += 3;
+				} else
+				{
+					if (word)
+					{
+						src++;
+						num = src[0] - '0';	/* ascii to decimal */
+						if (*word[num] == 0)
+							return 0;
+
+						if (src[-1] == '%')
+							utf = word[num];
+						else
+							utf = word_eol[num];
+
+						/* avoid recusive usercommand overflow */
+						if ((dest - orig) + strlen (utf) >= destlen)
+							return 2;
+
+						strcpy (dest, utf);
+						dest += strlen (dest);
+					}
+				}
+			} else
+			{
+				if (src[0] == '&')
+					goto lamecode;
+				src++;
+				utf = NULL;
+				switch (src[0])
+				{
+				case '%':
+					if ((dest - orig) + 2 >= destlen)
+						return 2;
+					dest[0] = '%';
+					dest[1] = 0;
+					dest++;
+					break;
+				case 'a':
+					utf = a; break;
+				case 'c':
+					utf = c; break;
+				case 'd':
+					utf = d; break;
+				case 'e':
+					utf = e; break;
+				case 'h':
+					utf = h; break;
+				case 'm':
+					utf = get_cpu_str (); break;
+				case 'n':
+					utf = n; break;
+				case 's':
+					utf = s; break;
+				case 't':
+					now = time (0);
+					utf = ctime (&now);
+					utf[19] = 0;
+					break;
+				case 'v':
+					utf = PACKAGE_VERSION; break;
+					break;
+				case 'y':
+					now = time (0);
+					tm_ptr = localtime (&now);
+					snprintf (buf, sizeof (buf), "%4d%02d%02d", 1900 +
+								 tm_ptr->tm_year, 1 + tm_ptr->tm_mon, tm_ptr->tm_mday);
+					utf = buf;
+					break;
+				default:
+					src--;
+					goto lamecode;
+				}
+
+				if (utf)
+				{
+					if ((dest - orig) + strlen (utf) >= destlen)
+						return 2;
+					strcpy (dest, utf);
+					dest += strlen (dest);
+				}
+
+			}
+			src++;
+		} else
+		{
+			utf_len = g_utf8_skip[src[0]];
+
+			if ((dest - orig) + utf_len >= destlen)
+				return 2;
+
+			if (utf_len == 1)
+			{
+		 lamecode:
+				dest[0] = src[0];
+				dest++;
+				src++;
+			} else
+			{
+				memcpy (dest, src, utf_len);
+				dest += utf_len;
+				src += utf_len;
+			}
+		}
+	}
+
+	dest[0] = 0;
+
+	return 1;
+}
+
+void
+check_special_chars (char *cmd, int do_ascii) /* check for %X */
+{
+	int occur = 0;
+	int len = strlen (cmd);
+	char *buf, *utf;
+	char tbuf[4];
+	int i = 0, j = 0;
+	gsize utf_len;
+
+	if (!len)
+		return;
+
+	buf = malloc (len + 1);
+
+	if (buf)
+	{
+		while (cmd[j])
+		{
+			switch (cmd[j])
+			{
+			case '%':
+				occur++;
+				if (	do_ascii &&
+						j + 3 < len &&
+						(isdigit ((unsigned char) cmd[j + 1]) && isdigit ((unsigned char) cmd[j + 2]) &&
+						isdigit ((unsigned char) cmd[j + 3])))
+				{
+					tbuf[0] = cmd[j + 1];
+					tbuf[1] = cmd[j + 2];
+					tbuf[2] = cmd[j + 3];
+					tbuf[3] = 0;
+					buf[i] = atoi (tbuf);
+					utf = g_locale_to_utf8 (buf + i, 1, 0, &utf_len, 0);
+					if (utf)
+					{
+						memcpy (buf + i, utf, utf_len);
+						g_free (utf);
+						i += (utf_len - 1);
+					}
+					j += 3;
+				} else
+				{
+					switch (cmd[j + 1])
+					{
+					case 'R':
+						buf[i] = '\026';
+						break;
+					case 'U':
+						buf[i] = '\037';
+						break;
+					case 'B':
+						buf[i] = '\002';
+						break;
+					case 'C':
+						buf[i] = '\003';
+						break;
+					case 'O':
+						buf[i] = '\017';
+						break;
+					case 'H':	/* CL: invisible text code */
+						buf[i] = HIDDEN_CHAR;
+						break;
+					case '%':
+						buf[i] = '%';
+						break;
+					default:
+						buf[i] = '%';
+						j--;
+						break;
+					}
+					j++;
+					break;
+			default:
+					buf[i] = cmd[j];
+				}
+			}
+			j++;
+			i++;
+		}
+		buf[i] = 0;
+		if (occur)
+			strcpy (cmd, buf);
+		free (buf);
+	}
+}
+
+typedef struct
+{
+	char *nick;
+	int len;
+	struct User *best;
+	int bestlen;
+	char *space;
+	char *tbuf;
+} nickdata;
+
+static int
+nick_comp_cb (struct User *user, nickdata *data)
+{
+	int lenu;
+
+	if (!rfc_ncasecmp (user->nick, data->nick, data->len))
+	{
+		lenu = strlen (user->nick);
+		if (lenu == data->len)
+		{
+			snprintf (data->tbuf, TBUFSIZE, "%s%s", user->nick, data->space);
+			data->len = -1;
+			return FALSE;
+		} else if (lenu < data->bestlen)
+		{
+			data->bestlen = lenu;
+			data->best = user;
+		}
+	}
+
+	return TRUE;
+}
+
+static void
+perform_nick_completion (struct session *sess, char *cmd, char *tbuf)
+{
+	int len;
+	char *space = strchr (cmd, ' ');
+	if (space && space != cmd)
+	{
+		if (space[-1] == prefs.nick_suffix[0] && space - 1 != cmd)
+		{
+			len = space - cmd - 1;
+			if (len < NICKLEN)
+			{
+				char nick[NICKLEN];
+				nickdata data;
+
+				memcpy (nick, cmd, len);
+				nick[len] = 0;
+
+				data.nick = nick;
+				data.len = len;
+				data.bestlen = INT_MAX;
+				data.best = NULL;
+				data.tbuf = tbuf;
+				data.space = space - 1;
+				tree_foreach (sess->usertree, (tree_traverse_func *)nick_comp_cb, &data);
+
+				if (data.len == -1)
+					return;
+
+				if (data.best)
+				{
+					snprintf (tbuf, TBUFSIZE, "%s%s", data.best->nick, space - 1);
+					return;
+				}
+			}
+		}
+	}
+
+	strcpy (tbuf, cmd);
+}
+
+static void
+user_command (session * sess, char *tbuf, char *cmd, char *word[],
+				  char *word_eol[])
+{
+	if (!auto_insert (tbuf, 2048, cmd, word, word_eol, "", sess->channel, "",
+							server_get_network (sess->server, TRUE), "",
+							sess->server->nick, ""))
+	{
+		PrintText (sess, _("Bad arguments for user command.\n"));
+		return;
+	}
+
+	handle_command (sess, tbuf, TRUE);
+}
+
+/* handle text entered without a CMDchar prefix */
+
+static void
+handle_say (session *sess, char *text, int check_spch)
+{
+	struct DCC *dcc;
+	char *word[PDIWORDS+1];
+	char *word_eol[PDIWORDS+1];
+	char pdibuf_static[1024];
+	char newcmd_static[1024];
+	char *pdibuf = pdibuf_static;
+	char *newcmd = newcmd_static;
+	int len;
+	int newcmdlen = sizeof newcmd_static;
+
+	if (strcmp (sess->channel, "(lastlog)") == 0)
+	{
+		lastlog (sess->lastlog_sess, text, sess->lastlog_regexp);
+		return;
+	}
+
+	len = strlen (text);
+	if (len >= sizeof pdibuf_static)
+		pdibuf = malloc (len + 1);
+
+	if (len + NICKLEN >= newcmdlen)
+		newcmd = malloc (newcmdlen = len + NICKLEN + 1);
+
+	if (check_spch && prefs.perc_color)
+		check_special_chars (text, prefs.perc_ascii);
+
+	/* Python relies on this */
+	word[PDIWORDS] = NULL;
+	word_eol[PDIWORDS] = NULL;
+
+	/* split the text into words and word_eol */
+	process_data_init (pdibuf, text, word, word_eol, TRUE, FALSE);
+
+	/* a command of "" can be hooked for non-commands */
+	if (plugin_emit_command (sess, "", word, word_eol))
+		goto xit;
+
+	/* incase a plugin did /close */
+	if (!is_session (sess))
+		goto xit;
+
+	if (!sess->channel[0] || sess->type == SESS_SERVER || sess->type == SESS_NOTICES || sess->type == SESS_SNOTICES)
+	{
+		notj_msg (sess);
+		goto xit;
+	}
+
+	if (prefs.nickcompletion)
+		perform_nick_completion (sess, text, newcmd);
+	else
+		safe_strcpy (newcmd, text, newcmdlen);
+
+	text = newcmd;
+
+	if (sess->type == SESS_DIALOG)
+	{
+		/* try it via dcc, if possible */
+		dcc = dcc_write_chat (sess->channel, text);
+		if (dcc)
+		{
+			inbound_chanmsg (sess->server, NULL, sess->channel,
+								  sess->server->nick, text, TRUE, FALSE);
+			set_topic (sess, net_ip (dcc->addr), net_ip (dcc->addr));
+			goto xit;
+		}
+	}
+
+	if (sess->server->connected)
+	{
+		unsigned int max;
+		unsigned char t = 0;
+
+		/* maximum allowed message text */
+		/* :nickname!username@host.com PRIVMSG #channel :text\r\n */
+		max = 512;
+		max -= 16;	/* :, !, @, " PRIVMSG ", " ", :, \r, \n */
+		max -= strlen (sess->server->nick);
+		max -= strlen (sess->channel);
+		if (sess->me && sess->me->hostname)
+			max -= strlen (sess->me->hostname);
+		else
+		{
+			max -= 9;	/* username */
+			max -= 65;	/* max possible hostname and '@' */
+		}
+
+		if (strlen (text) > max)
+		{
+			int i = 0, size;
+
+			/* traverse the utf8 string and find the nearest cut point that
+				doesn't split 1 char in half */
+			while (1)
+			{
+				size = g_utf8_skip[((unsigned char *)text)[i]];
+				if ((i + size) >= max)
+					break;
+				i += size;
+			}
+			max = i;
+			t = text[max];
+			text[max] = 0;			  /* insert a NULL terminator to shorten it */
+		}
+
+		inbound_chanmsg (sess->server, sess, sess->channel, sess->server->nick,
+							  text, TRUE, FALSE);
+		sess->server->p_message (sess->server, sess->channel, text);
+
+		if (t)
+		{
+			text[max] = t;
+			handle_say (sess, text + max, FALSE);
+		}
+
+	} else
+	{
+		notc_msg (sess);
+	}
+
+xit:
+	if (pdibuf != pdibuf_static)
+		free (pdibuf);
+
+	if (newcmd != newcmd_static)
+		free (newcmd);
+}
+
+/* handle a command, without the '/' prefix */
+
+int
+handle_command (session *sess, char *cmd, int check_spch)
+{
+	struct popup *pop;
+	int user_cmd = FALSE;
+	GSList *list;
+	char *word[PDIWORDS+1];
+	char *word_eol[PDIWORDS+1];
+	static int command_level = 0;
+	struct commands *int_cmd;
+	char pdibuf_static[1024];
+	char tbuf_static[TBUFSIZE];
+	char *pdibuf;
+	char *tbuf;
+	int len;
+	int ret = TRUE;
+
+	if (command_level > 99)
+	{
+		fe_message (_("Too many recursive usercommands, aborting."), FE_MSG_ERROR);
+		return TRUE;
+	}
+	command_level++;
+	/* anything below MUST DEC command_level before returning */
+
+	len = strlen (cmd);
+	if (len >= sizeof (pdibuf_static))
+		pdibuf = malloc (len + 1);
+	else
+		pdibuf = pdibuf_static;
+
+	if ((len * 2) >= sizeof (tbuf_static))
+		tbuf = malloc ((len * 2) + 1);
+	else
+		tbuf = tbuf_static;
+
+	/* split the text into words and word_eol */
+	process_data_init (pdibuf, cmd, word, word_eol, TRUE, TRUE);
+
+	/* ensure an empty string at index 32 for cmd_deop etc */
+	/* (internal use only, plugins can still only read 1-31). */
+	word[PDIWORDS] = "\000\000";
+	word_eol[PDIWORDS] = "\000\000";
+
+	int_cmd = find_internal_command (word[1]);
+	/* redo it without quotes processing, for some commands like /JOIN */
+	if (int_cmd && !int_cmd->handle_quotes)
+		process_data_init (pdibuf, cmd, word, word_eol, FALSE, FALSE);
+
+	if (check_spch && prefs.perc_color)
+		check_special_chars (cmd, prefs.perc_ascii);
+
+	if (plugin_emit_command (sess, word[1], word, word_eol))
+		goto xit;
+
+	/* incase a plugin did /close */
+	if (!is_session (sess))
+		goto xit;
+
+	/* first see if it's a userCommand */
+	list = command_list;
+	while (list)
+	{
+		pop = (struct popup *) list->data;
+		if (!strcasecmp (pop->name, word[1]))
+		{
+			user_command (sess, tbuf, pop->cmd, word, word_eol);
+			user_cmd = TRUE;
+		}
+		list = list->next;
+	}
+
+	if (user_cmd)
+		goto xit;
+
+	/* now check internal commands */
+	int_cmd = find_internal_command (word[1]);
+
+	if (int_cmd)
+	{
+		if (int_cmd->needserver && !sess->server->connected)
+		{
+			notc_msg (sess);
+		} else if (int_cmd->needchannel && !sess->channel[0])
+		{
+			notj_msg (sess);
+		} else
+		{
+			switch (int_cmd->callback (sess, tbuf, word, word_eol))
+			{
+			case FALSE:
+				help (sess, tbuf, int_cmd->name, TRUE);
+				break;
+			case 2:
+				ret = FALSE;
+				goto xit;
+			}
+		}
+	} else
+	{
+		/* unknown command, just send it to the server and hope */
+		if (!sess->server->connected)
+			PrintText (sess, _("Unknown Command. Try /help\n"));
+		else
+			sess->server->p_raw (sess->server, cmd);
+	}
+
+xit:
+	command_level--;
+
+	if (pdibuf != pdibuf_static)
+		free (pdibuf);
+
+	if (tbuf != tbuf_static)
+		free (tbuf);
+
+	return ret;
+}
+
+/* handle one line entered into the input box */
+
+static int
+handle_user_input (session *sess, char *text, int history, int nocommand)
+{
+	if (*text == '\0')
+		return 1;
+
+	if (history)
+		history_add (&sess->history, text);
+
+	/* is it NOT a command, just text? */
+	if (nocommand || text[0] != prefs.cmdchar[0])
+	{
+		handle_say (sess, text, TRUE);
+		return 1;
+	}
+
+	/* check for // */
+	if (text[0] == prefs.cmdchar[0] && text[1] == prefs.cmdchar[0])
+	{
+		handle_say (sess, text + 1, TRUE);
+		return 1;
+	}
+
+	if (prefs.cmdchar[0] == '/')
+	{
+		int i;
+		const char *unix_dirs [] = {
+			"/bin/", "/boot/", "/dev/",
+			"/etc/", "/home/", "/lib/",
+			"/lost+found/", "/mnt/", "/opt/",
+			"/proc/", "/root/", "/sbin/",
+			"/tmp/", "/usr/", "/var/",
+			"/gnome/", NULL};
+		for (i = 0; unix_dirs[i] != NULL; i++)
+			if (strncmp (text, unix_dirs[i], strlen (unix_dirs[i]))==0)
+			{
+				handle_say (sess, text, TRUE);
+				return 1;
+			}
+	}
+
+	return handle_command (sess, text + 1, TRUE);
+}
+
+/* changed by Steve Green. Macs sometimes paste with imbedded \r */
+void
+handle_multiline (session *sess, char *cmd, int history, int nocommand)
+{
+	while (*cmd)
+	{
+		char *cr = cmd + strcspn (cmd, "\n\r");
+		int end_of_string = *cr == 0;
+		*cr = 0;
+		if (!handle_user_input (sess, cmd, history, nocommand))
+			return;
+		if (end_of_string)
+			break;
+		cmd = cr + 1;
+	}
+}
+
+/*void
+handle_multiline (session *sess, char *cmd, int history, int nocommand)
+{
+	char *cr;
+
+	cr = strchr (cmd, '\n');
+	if (cr)
+	{
+		while (1)
+		{
+			if (cr)
+				*cr = 0;
+			if (!handle_user_input (sess, cmd, history, nocommand))
+				return;
+			if (!cr)
+				break;
+			cmd = cr + 1;
+			if (*cmd == 0)
+				break;
+			cr = strchr (cmd, '\n');
+		}
+	} else
+	{
+		handle_user_input (sess, cmd, history, nocommand);
+	}
+}*/
diff --git a/src/common/outbound.h b/src/common/outbound.h
new file mode 100644
index 00000000..a2047845
--- /dev/null
+++ b/src/common/outbound.h
@@ -0,0 +1,20 @@
+#ifndef XCHAT_OUTBOUND_H
+#define XCHAT_OUTBOUND_H
+
+extern const struct commands xc_cmds[];
+extern GSList *menu_list;
+
+int auto_insert (char *dest, int destlen, unsigned char *src, char *word[], char *word_eol[],
+				 char *a, char *c, char *d, char *e, char *h, char *n, char *s);
+int handle_command (session *sess, char *cmd, int check_spch);
+void process_data_init (char *buf, char *cmd, char *word[], char *word_eol[], gboolean handle_quotes, gboolean allow_escape_quotes);
+void handle_multiline (session *sess, char *cmd, int history, int nocommand);
+void check_special_chars (char *cmd, int do_ascii);
+void notc_msg (session *sess);
+void server_sendpart (server * serv, char *channel, char *reason);
+void server_sendquit (session * sess);
+int menu_streq (const char *s1, const char *s2, int def);
+void open_query (server *serv, char *nick, gboolean focus_existing);
+gboolean load_perform_file (session *sess, char *file);
+
+#endif
diff --git a/src/common/plugin-timer.c b/src/common/plugin-timer.c
new file mode 100644
index 00000000..f09074a8
--- /dev/null
+++ b/src/common/plugin-timer.c
@@ -0,0 +1,208 @@
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+#include "xchat-plugin.h"
+
+#ifdef WIN32
+#define strcasecmp stricmp
+#endif
+
+static xchat_plugin *ph;	/* plugin handle */
+static GSList *timer_list = NULL;
+
+#define STATIC
+#define HELP \
+"Usage: TIMER [-refnum <num>] [-repeat <num>] <seconds> <command>\n" \
+"       TIMER [-quiet] -delete <num>"
+
+typedef struct
+{
+	xchat_hook *hook;
+	xchat_context *context;
+	char *command;
+	int ref;
+	int repeat;
+	float timeout;
+	unsigned int forever:1;
+} timer;
+
+static void
+timer_del (timer *tim)
+{
+	timer_list = g_slist_remove (timer_list, tim);
+	free (tim->command);
+	xchat_unhook (ph, tim->hook);
+	free (tim);
+}
+
+static void
+timer_del_ref (int ref, int quiet)
+{
+	GSList *list;
+	timer *tim;
+
+	list = timer_list;
+	while (list)
+	{
+		tim = list->data;
+		if (tim->ref == ref)
+		{
+			timer_del (tim);
+			if (!quiet)
+				xchat_printf (ph, "Timer %d deleted.\n", ref);
+			return;
+		}
+		list = list->next;
+	}
+	if (!quiet)
+		xchat_print (ph, "No such ref number found.\n");
+}
+
+static int
+timeout_cb (timer *tim)
+{
+	if (xchat_set_context (ph, tim->context))
+	{
+		xchat_command (ph, tim->command);
+
+		if (tim->forever)
+			return 1;
+
+		tim->repeat--;
+		if (tim->repeat > 0)
+			return 1;
+	}
+
+	timer_del (tim);
+	return 0;
+}
+
+static void
+timer_add (int ref, float timeout, int repeat, char *command)
+{
+	timer *tim;
+	GSList *list;
+
+	if (ref == 0)
+	{
+		ref = 1;
+		list = timer_list;
+		while (list)
+		{
+			tim = list->data;
+			if (tim->ref >= ref)
+				ref = tim->ref + 1;
+			list = list->next;
+		}
+	}
+
+	tim = malloc (sizeof (timer));
+	tim->ref = ref;
+	tim->repeat = repeat;
+	tim->timeout = timeout;
+	tim->command = strdup (command);
+	tim->context = xchat_get_context (ph);
+	tim->forever = FALSE;
+
+	if (repeat == 0)
+		tim->forever = TRUE;
+
+	tim->hook = xchat_hook_timer (ph, timeout * 1000.0, (void *)timeout_cb, tim);
+	timer_list = g_slist_append (timer_list, tim);
+}
+
+static void
+timer_showlist (void)
+{
+	GSList *list;
+	timer *tim;
+
+	if (timer_list == NULL)
+	{
+		xchat_print (ph, "No timers installed.\n");
+		xchat_print (ph, HELP);
+		return;
+	}
+							 /*  00000 00000000 0000000 abc */
+	xchat_print (ph, "\026 Ref#  Seconds  Repeat  Command \026\n");
+	list = timer_list;
+	while (list)
+	{
+		tim = list->data;
+		xchat_printf (ph, "%5d %8.1f %7d  %s\n", tim->ref, tim->timeout,
+						  tim->repeat, tim->command);
+		list = list->next;
+	}
+}
+
+static int
+timer_cb (char *word[], char *word_eol[], void *userdata)
+{
+	int repeat = 1;
+	float timeout;
+	int offset = 0;
+	int ref = 0;
+	int quiet = FALSE;
+	char *command;
+
+	if (!word[2][0])
+	{
+		timer_showlist ();
+		return XCHAT_EAT_XCHAT;
+	}
+
+	if (strcasecmp (word[2], "-quiet") == 0)
+	{
+		quiet = TRUE;
+		offset++;
+	}
+
+	if (strcasecmp (word[2 + offset], "-delete") == 0)
+	{
+		timer_del_ref (atoi (word[3 + offset]), quiet);
+		return XCHAT_EAT_XCHAT;
+	}
+
+	if (strcasecmp (word[2 + offset], "-refnum") == 0)
+	{
+		ref = atoi (word[3 + offset]);
+		offset += 2;
+	}
+
+	if (strcasecmp (word[2 + offset], "-repeat") == 0)
+	{
+		repeat = atoi (word[3 + offset]);
+		offset += 2;
+	}
+
+	timeout = atof (word[2 + offset]);
+	command = word_eol[3 + offset];
+
+	if (timeout < 0.1 || !command[0])
+		xchat_print (ph, HELP);
+	else
+		timer_add (ref, timeout, repeat, command);
+
+	return XCHAT_EAT_XCHAT;
+}
+
+int
+#ifdef STATIC
+timer_plugin_init
+#else
+xchat_plugin_init
+#endif
+				(xchat_plugin *plugin_handle, char **plugin_name,
+				char **plugin_desc, char **plugin_version, char *arg)
+{
+	/* we need to save this for use with any xchat_* functions */
+	ph = plugin_handle;
+
+	*plugin_name = "Timer";
+	*plugin_desc = "IrcII style /TIMER command";
+	*plugin_version = "";
+
+	xchat_hook_command (ph, "TIMER", XCHAT_PRI_NORM, timer_cb, HELP, 0);
+
+	return 1;       /* return 1 for success */
+}
diff --git a/src/common/plugin-timer.h b/src/common/plugin-timer.h
new file mode 100644
index 00000000..e3530c8d
--- /dev/null
+++ b/src/common/plugin-timer.h
@@ -0,0 +1,7 @@
+#ifndef XCHAT_PLUGIN_TIMER_H
+#define XCHAT_PLUGIN_TIMER_H
+
+int timer_plugin_init (xchat_plugin *plugin_handle, char **plugin_name,
+				char **plugin_desc, char **plugin_version, char *arg);
+
+#endif
diff --git a/src/common/plugin.c b/src/common/plugin.c
new file mode 100644
index 00000000..ada4d3be
--- /dev/null
+++ b/src/common/plugin.c
@@ -0,0 +1,1569 @@
+/* X-Chat
+ * Copyright (C) 2002 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 <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "xchat.h"
+#include "fe.h"
+#include "util.h"
+#include "outbound.h"
+#include "cfgfiles.h"
+#include "ignore.h"
+#include "server.h"
+#include "servlist.h"
+#include "modes.h"
+#include "notify.h"
+#include "text.h"
+#define PLUGIN_C
+typedef struct session xchat_context;
+#include "xchat-plugin.h"
+#include "plugin.h"
+
+
+#include "xchatc.h"
+
+/* the USE_PLUGIN define only removes libdl stuff */
+
+#ifdef USE_PLUGIN
+#ifdef USE_GMODULE
+#include <gmodule.h>
+#else
+#include <dlfcn.h>
+#endif
+#endif
+
+#define DEBUG(x) {x;}
+
+/* crafted to be an even 32 bytes */
+struct _xchat_hook
+{
+	xchat_plugin *pl;	/* the plugin to which it belongs */
+	char *name;			/* "xdcc" */
+	void *callback;	/* pointer to xdcc_callback */
+	char *help_text;	/* help_text for commands only */
+	void *userdata;	/* passed to the callback */
+	int tag;				/* for timers & FDs only */
+	int type;			/* HOOK_* */
+	int pri;	/* fd */	/* priority / fd for HOOK_FD only */
+};
+
+struct _xchat_list
+{
+	int type;			/* LIST_* */
+	GSList *pos;		/* current pos */
+	GSList *next;		/* next pos */
+	GSList *head;		/* for LIST_USERS only */
+	struct notify_per_server *notifyps;	/* notify_per_server * */
+};
+
+typedef int (xchat_cmd_cb) (char *word[], char *word_eol[], void *user_data);
+typedef int (xchat_serv_cb) (char *word[], char *word_eol[], void *user_data);
+typedef int (xchat_print_cb) (char *word[], void *user_data);
+typedef int (xchat_fd_cb) (int fd, int flags, void *user_data);
+typedef int (xchat_timer_cb) (void *user_data);
+typedef int (xchat_init_func) (xchat_plugin *, char **, char **, char **, char *);
+typedef int (xchat_deinit_func) (xchat_plugin *);
+
+enum
+{
+	LIST_CHANNELS,
+	LIST_DCC,
+	LIST_IGNORE,
+	LIST_NOTIFY,
+	LIST_USERS
+};
+
+enum
+{
+	HOOK_COMMAND,	/* /command */
+	HOOK_SERVER,	/* PRIVMSG, NOTICE, numerics */
+	HOOK_PRINT,		/* All print events */
+	HOOK_TIMER,		/* timeouts */
+	HOOK_FD,			/* sockets & fds */
+	HOOK_DELETED	/* marked for deletion */
+};
+
+GSList *plugin_list = NULL;	/* export for plugingui.c */
+static GSList *hook_list = NULL;
+
+extern const struct prefs vars[];	/* cfgfiles.c */
+
+
+/* unload a plugin and remove it from our linked list */
+
+static int
+plugin_free (xchat_plugin *pl, int do_deinit, int allow_refuse)
+{
+	GSList *list, *next;
+	xchat_hook *hook;
+	xchat_deinit_func *deinit_func;
+
+	/* fake plugin added by xchat_plugingui_add() */
+	if (pl->fake)
+		goto xit;
+
+	/* run the plugin's deinit routine, if any */
+	if (do_deinit && pl->deinit_callback != NULL)
+	{
+		deinit_func = pl->deinit_callback;
+		if (!deinit_func (pl) && allow_refuse)
+			return FALSE;
+	}
+
+	/* remove all of this plugin's hooks */
+	list = hook_list;
+	while (list)
+	{
+		hook = list->data;
+		next = list->next;
+		if (hook->pl == pl)
+			xchat_unhook (NULL, hook);
+		list = next;
+	}
+
+#ifdef USE_PLUGIN
+	if (pl->handle)
+#ifdef USE_GMODULE
+		g_module_close (pl->handle);
+#else
+		dlclose (pl->handle);
+#endif
+#endif
+
+xit:
+	if (pl->free_strings)
+	{
+		if (pl->name)
+			free (pl->name);
+		if (pl->desc)
+			free (pl->desc);
+		if (pl->version)
+			free (pl->version);
+	}
+	if (pl->filename)
+		free ((char *)pl->filename);
+	free (pl);
+
+	plugin_list = g_slist_remove (plugin_list, pl);
+
+#ifdef USE_PLUGIN
+	fe_pluginlist_update ();
+#endif
+
+	return TRUE;
+}
+
+static xchat_plugin *
+plugin_list_add (xchat_context *ctx, char *filename, const char *name,
+					  const char *desc, const char *version, void *handle,
+					  void *deinit_func, int fake, int free_strings)
+{
+	xchat_plugin *pl;
+
+	pl = malloc (sizeof (xchat_plugin));
+	pl->handle = handle;
+	pl->filename = filename;
+	pl->context = ctx;
+	pl->name = (char *)name;
+	pl->desc = (char *)desc;
+	pl->version = (char *)version;
+	pl->deinit_callback = deinit_func;
+	pl->fake = fake;
+	pl->free_strings = free_strings;	/* free() name,desc,version? */
+
+	plugin_list = g_slist_prepend (plugin_list, pl);
+
+	return pl;
+}
+
+static void *
+xchat_dummy (xchat_plugin *ph)
+{
+	return NULL;
+}
+
+#ifdef WIN32
+static int
+xchat_read_fd (xchat_plugin *ph, GIOChannel *source, char *buf, int *len)
+{
+	return g_io_channel_read (source, buf, *len, len);
+}
+#endif
+
+/* Load a static plugin */
+
+void
+plugin_add (session *sess, char *filename, void *handle, void *init_func,
+				void *deinit_func, char *arg, int fake)
+{
+	xchat_plugin *pl;
+	char *file;
+
+	file = NULL;
+	if (filename)
+		file = strdup (filename);
+
+	pl = plugin_list_add (sess, file, file, NULL, NULL, handle, deinit_func,
+								 fake, FALSE);
+
+	if (!fake)
+	{
+		/* win32 uses these because it doesn't have --export-dynamic! */
+		pl->xchat_hook_command = xchat_hook_command;
+		pl->xchat_hook_server = xchat_hook_server;
+		pl->xchat_hook_print = xchat_hook_print;
+		pl->xchat_hook_timer = xchat_hook_timer;
+		pl->xchat_hook_fd = xchat_hook_fd;
+		pl->xchat_unhook = xchat_unhook;
+		pl->xchat_print = xchat_print;
+		pl->xchat_printf = xchat_printf;
+		pl->xchat_command = xchat_command;
+		pl->xchat_commandf = xchat_commandf;
+		pl->xchat_nickcmp = xchat_nickcmp;
+		pl->xchat_set_context = xchat_set_context;
+		pl->xchat_find_context = xchat_find_context;
+		pl->xchat_get_context = xchat_get_context;
+		pl->xchat_get_info = xchat_get_info;
+		pl->xchat_get_prefs = xchat_get_prefs;
+		pl->xchat_list_get = xchat_list_get;
+		pl->xchat_list_free = xchat_list_free;
+		pl->xchat_list_fields = xchat_list_fields;
+		pl->xchat_list_str = xchat_list_str;
+		pl->xchat_list_next = xchat_list_next;
+		pl->xchat_list_int = xchat_list_int;
+		pl->xchat_plugingui_add = xchat_plugingui_add;
+		pl->xchat_plugingui_remove = xchat_plugingui_remove;
+		pl->xchat_emit_print = xchat_emit_print;
+#ifdef WIN32
+		pl->xchat_read_fd = (void *) xchat_read_fd;
+#else
+		pl->xchat_read_fd = xchat_dummy;
+#endif
+		pl->xchat_list_time = xchat_list_time;
+		pl->xchat_gettext = xchat_gettext;
+		pl->xchat_send_modes = xchat_send_modes;
+		pl->xchat_strip = xchat_strip;
+		pl->xchat_free = xchat_free;
+
+		/* incase new plugins are loaded on older xchat */
+		pl->xchat_dummy4 = xchat_dummy;
+		pl->xchat_dummy3 = xchat_dummy;
+		pl->xchat_dummy2 = xchat_dummy;
+		pl->xchat_dummy1 = xchat_dummy;
+
+		/* run xchat_plugin_init, if it returns 0, close the plugin */
+		if (((xchat_init_func *)init_func) (pl, &pl->name, &pl->desc, &pl->version, arg) == 0)
+		{
+			plugin_free (pl, FALSE, FALSE);
+			return;
+		}
+	}
+
+#ifdef USE_PLUGIN
+	fe_pluginlist_update ();
+#endif
+}
+
+/* kill any plugin by the given (file) name (used by /unload) */
+
+int
+plugin_kill (char *name, int by_filename)
+{
+	GSList *list;
+	xchat_plugin *pl;
+
+	list = plugin_list;
+	while (list)
+	{
+		pl = list->data;
+		/* static-plugins (plugin-timer.c) have a NULL filename */
+		if ((by_filename && pl->filename && strcasecmp (name, pl->filename) == 0) ||
+			 (by_filename && pl->filename && strcasecmp (name, file_part (pl->filename)) == 0) ||
+			(!by_filename && strcasecmp (name, pl->name) == 0))
+		{
+			/* statically linked plugins have a NULL filename */
+			if (pl->filename != NULL && !pl->fake)
+			{
+				if (plugin_free (pl, TRUE, TRUE))
+					return 1;
+				return 2;
+			}
+		}
+		list = list->next;
+	}
+
+	return 0;
+}
+
+/* kill all running plugins (at shutdown) */
+
+void
+plugin_kill_all (void)
+{
+	GSList *list, *next;
+	xchat_plugin *pl;
+
+	list = plugin_list;
+	while (list)
+	{
+		pl = list->data;
+		next = list->next;
+		if (!pl->fake)
+			plugin_free (list->data, TRUE, FALSE);
+		list = next;
+	}
+}
+
+#ifdef USE_PLUGIN
+
+/* load a plugin from a filename. Returns: NULL-success or an error string */
+
+char *
+plugin_load (session *sess, char *filename, char *arg)
+{
+	void *handle;
+	xchat_init_func *init_func;
+	xchat_deinit_func *deinit_func;
+
+#ifdef USE_GMODULE
+	/* load the plugin */
+	handle = g_module_open (filename, 0);
+	if (handle == NULL)
+		return (char *)g_module_error ();
+
+	/* find the init routine xchat_plugin_init */
+	if (!g_module_symbol (handle, "xchat_plugin_init", (gpointer *)&init_func))
+	{
+		g_module_close (handle);
+		return _("No xchat_plugin_init symbol; is this really an xchat plugin?");
+	}
+
+	/* find the plugin's deinit routine, if any */
+	if (!g_module_symbol (handle, "xchat_plugin_deinit", (gpointer *)&deinit_func))
+		deinit_func = NULL;
+
+#else
+	char *error;
+	char *filepart;
+
+/* OpenBSD lacks this! */
+#ifndef RTLD_GLOBAL
+#define RTLD_GLOBAL 0
+#endif
+
+#ifndef RTLD_NOW
+#define RTLD_NOW 0
+#endif
+
+	/* get the filename without path */
+	filepart = file_part (filename);
+
+	/* load the plugin */
+	if (filepart &&
+		 /* xsys draws in libgtk-1.2, causing crashes, so force RTLD_LOCAL */
+		 (strstr (filepart, "local") || strncmp (filepart, "libxsys-1", 9) == 0)
+		)
+		handle = dlopen (filename, RTLD_NOW);
+	else
+		handle = dlopen (filename, RTLD_GLOBAL | RTLD_NOW);
+	if (handle == NULL)
+		return (char *)dlerror ();
+	dlerror ();		/* Clear any existing error */
+
+	/* find the init routine xchat_plugin_init */
+	init_func = dlsym (handle, "xchat_plugin_init");
+	error = (char *)dlerror ();
+	if (error != NULL)
+	{
+		dlclose (handle);
+		return _("No xchat_plugin_init symbol; is this really an xchat plugin?");
+	}
+
+	/* find the plugin's deinit routine, if any */
+	deinit_func = dlsym (handle, "xchat_plugin_deinit");
+	error = (char *)dlerror ();
+#endif
+
+	/* add it to our linked list */
+	plugin_add (sess, filename, handle, init_func, deinit_func, arg, FALSE);
+
+	return NULL;
+}
+
+static session *ps;
+
+static void
+plugin_auto_load_cb (char *filename)
+{
+	char *pMsg;
+
+#ifndef WIN32	/* black listed */
+	if (!strcmp (file_part (filename), "dbus.so"))
+		return;
+#endif
+
+	pMsg = plugin_load (ps, filename, NULL);
+	if (pMsg)
+	{
+		PrintTextf (ps, "AutoLoad failed for: %s\n", filename);
+		PrintText (ps, pMsg);
+	}
+}
+
+void
+plugin_auto_load (session *sess)
+{
+	ps = sess;
+#ifdef WIN32
+	for_files ("./plugins", "*.dll", plugin_auto_load_cb);
+	for_files (get_xdir_fs (), "*.dll", plugin_auto_load_cb);
+#else
+#if defined(__hpux)
+	for_files (XCHATLIBDIR"/plugins", "*.sl", plugin_auto_load_cb);
+	for_files (get_xdir_fs (), "*.sl", plugin_auto_load_cb);
+#else
+	for_files (XCHATLIBDIR"/plugins", "*.so", plugin_auto_load_cb);
+	for_files (get_xdir_fs (), "*.so", plugin_auto_load_cb);
+#endif
+#endif
+}
+
+#endif
+
+static GSList *
+plugin_hook_find (GSList *list, int type, char *name)
+{
+	xchat_hook *hook;
+
+	while (list)
+	{
+		hook = list->data;
+		if (hook->type == type)
+		{
+			if (strcasecmp (hook->name, name) == 0)
+				return list;
+
+			if (type == HOOK_SERVER)
+			{
+				if (strcasecmp (hook->name, "RAW LINE") == 0)
+					return list;
+			}
+		}
+		list = list->next;
+	}
+
+	return NULL;
+}
+
+/* check for plugin hooks and run them */
+
+static int
+plugin_hook_run (session *sess, char *name, char *word[], char *word_eol[], int type)
+{
+	GSList *list, *next;
+	xchat_hook *hook;
+	int ret, eat = 0;
+
+	list = hook_list;
+	while (1)
+	{
+		list = plugin_hook_find (list, type, name);
+		if (!list)
+			goto xit;
+
+		hook = list->data;
+		next = list->next;
+		hook->pl->context = sess;
+
+		/* run the plugin's callback function */
+		switch (type)
+		{
+		case HOOK_COMMAND:
+			ret = ((xchat_cmd_cb *)hook->callback) (word, word_eol, hook->userdata);
+			break;
+		case HOOK_SERVER:
+			ret = ((xchat_serv_cb *)hook->callback) (word, word_eol, hook->userdata);
+			break;
+		default: /*case HOOK_PRINT:*/
+			ret = ((xchat_print_cb *)hook->callback) (word, hook->userdata);
+			break;
+		}
+
+		if ((ret & XCHAT_EAT_XCHAT) && (ret & XCHAT_EAT_PLUGIN))
+		{
+			eat = 1;
+			goto xit;
+		}
+		if (ret & XCHAT_EAT_PLUGIN)
+			goto xit;	/* stop running plugins */
+		if (ret & XCHAT_EAT_XCHAT)
+			eat = 1;	/* eventually we'll return 1, but continue running plugins */
+
+		list = next;
+	}
+
+xit:
+	/* really remove deleted hooks now */
+	list = hook_list;
+	while (list)
+	{
+		hook = list->data;
+		next = list->next;
+		if (hook->type == HOOK_DELETED)
+		{
+			hook_list = g_slist_remove (hook_list, hook);
+			free (hook);
+		}
+		list = next;
+	}
+
+	return eat;
+}
+
+/* execute a plugged in command. Called from outbound.c */
+
+int
+plugin_emit_command (session *sess, char *name, char *word[], char *word_eol[])
+{
+	return plugin_hook_run (sess, name, word, word_eol, HOOK_COMMAND);
+}
+
+/* got a server PRIVMSG, NOTICE, numeric etc... */
+
+int
+plugin_emit_server (session *sess, char *name, char *word[], char *word_eol[])
+{
+	return plugin_hook_run (sess, name, word, word_eol, HOOK_SERVER);
+}
+
+/* see if any plugins are interested in this print event */
+
+int
+plugin_emit_print (session *sess, char *word[])
+{
+	return plugin_hook_run (sess, word[0], word, NULL, HOOK_PRINT);
+}
+
+int
+plugin_emit_dummy_print (session *sess, char *name)
+{
+	char *word[32];
+	int i;
+
+	word[0] = name;
+	for (i = 1; i < 32; i++)
+		word[i] = "\000";
+
+	return plugin_hook_run (sess, name, word, NULL, HOOK_PRINT);
+}
+
+int
+plugin_emit_keypress (session *sess, unsigned int state, unsigned int keyval,
+							 int len, char *string)
+{
+	char *word[PDIWORDS];
+	char keyval_str[16];
+	char state_str[16];
+	char len_str[16];
+	int i;
+
+	if (!hook_list)
+		return 0;
+
+	sprintf (keyval_str, "%u", keyval);
+	sprintf (state_str, "%u", state);
+	sprintf (len_str, "%d", len);
+
+	word[0] = "Key Press";
+	word[1] = keyval_str;
+	word[2] = state_str;
+	word[3] = string;
+	word[4] = len_str;
+	for (i = 5; i < PDIWORDS; i++)
+		word[i] = "\000";
+
+	return plugin_hook_run (sess, word[0], word, NULL, HOOK_PRINT);
+}
+
+static int
+plugin_timeout_cb (xchat_hook *hook)
+{
+	int ret;
+
+	/* timer_cb's context starts as front-most-tab */
+	hook->pl->context = current_sess;
+
+	/* call the plugin's timeout function */
+	ret = ((xchat_timer_cb *)hook->callback) (hook->userdata);
+
+	/* the callback might have already unhooked it! */
+	if (!g_slist_find (hook_list, hook) || hook->type == HOOK_DELETED)
+		return 0;
+
+	if (ret == 0)
+	{
+		hook->tag = 0;	/* avoid fe_timeout_remove, returning 0 is enough! */
+		xchat_unhook (hook->pl, hook);
+	}
+
+	return ret;
+}
+
+/* insert a hook into hook_list according to its priority */
+
+static void
+plugin_insert_hook (xchat_hook *new_hook)
+{
+	GSList *list;
+	xchat_hook *hook;
+
+	list = hook_list;
+	while (list)
+	{
+		hook = list->data;
+		if (hook->type == new_hook->type && hook->pri <= new_hook->pri)
+		{
+			hook_list = g_slist_insert_before (hook_list, list, new_hook);
+			return;
+		}
+		list = list->next;
+	}
+
+	hook_list = g_slist_append (hook_list, new_hook);
+}
+
+static gboolean
+plugin_fd_cb (GIOChannel *source, GIOCondition condition, xchat_hook *hook)
+{
+	int flags = 0, ret;
+	typedef int (xchat_fd_cb2) (int fd, int flags, void *user_data, GIOChannel *);
+
+	if (condition & G_IO_IN)
+		flags |= XCHAT_FD_READ;
+	if (condition & G_IO_OUT)
+		flags |= XCHAT_FD_WRITE;
+	if (condition & G_IO_PRI)
+		flags |= XCHAT_FD_EXCEPTION;
+
+	ret = ((xchat_fd_cb2 *)hook->callback) (hook->pri, flags, hook->userdata, source);
+
+	/* the callback might have already unhooked it! */
+	if (!g_slist_find (hook_list, hook) || hook->type == HOOK_DELETED)
+		return 0;
+
+	if (ret == 0)
+	{
+		hook->tag = 0; /* avoid fe_input_remove, returning 0 is enough! */
+		xchat_unhook (hook->pl, hook);
+	}
+
+	return ret;
+}
+
+/* allocate and add a hook to our list. Used for all 4 types */
+
+static xchat_hook *
+plugin_add_hook (xchat_plugin *pl, int type, int pri, const char *name,
+					  const  char *help_text, void *callb, int timeout, void *userdata)
+{
+	xchat_hook *hook;
+
+	hook = malloc (sizeof (xchat_hook));
+	memset (hook, 0, sizeof (xchat_hook));
+
+	hook->type = type;
+	hook->pri = pri;
+	if (name)
+		hook->name = strdup (name);
+	if (help_text)
+		hook->help_text = strdup (help_text);
+	hook->callback = callb;
+	hook->pl = pl;
+	hook->userdata = userdata;
+
+	/* insert it into the linked list */
+	plugin_insert_hook (hook);
+
+	if (type == HOOK_TIMER)
+		hook->tag = fe_timeout_add (timeout, plugin_timeout_cb, hook);
+
+	return hook;
+}
+
+GList *
+plugin_command_list(GList *tmp_list)
+{
+	xchat_hook *hook;
+	GSList *list = hook_list;
+
+	while (list)
+	{
+		hook = list->data;
+		if (hook->type == HOOK_COMMAND)
+			tmp_list = g_list_prepend(tmp_list, hook->name);
+		list = list->next;
+	}
+	return tmp_list;
+}
+
+void
+plugin_command_foreach (session *sess, void *userdata,
+			void (*cb) (session *sess, void *userdata, char *name, char *help))
+{
+	GSList *list;
+	xchat_hook *hook;
+
+	list = hook_list;
+	while (list)
+	{
+		hook = list->data;
+		if (hook->type == HOOK_COMMAND && hook->name[0])
+		{
+			cb (sess, userdata, hook->name, hook->help_text);
+		}
+		list = list->next;
+	}
+}
+
+int
+plugin_show_help (session *sess, char *cmd)
+{
+	GSList *list;
+	xchat_hook *hook;
+
+	list = plugin_hook_find (hook_list, HOOK_COMMAND, cmd);
+	if (list)
+	{
+		hook = list->data;
+		if (hook->help_text)
+		{
+			PrintText (sess, hook->help_text);
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+/* ========================================================= */
+/* ===== these are the functions plugins actually call ===== */
+/* ========================================================= */
+
+void *
+xchat_unhook (xchat_plugin *ph, xchat_hook *hook)
+{
+	/* perl.c trips this */
+	if (!g_slist_find (hook_list, hook) || hook->type == HOOK_DELETED)
+		return NULL;
+
+	if (hook->type == HOOK_TIMER && hook->tag != 0)
+		fe_timeout_remove (hook->tag);
+
+	if (hook->type == HOOK_FD && hook->tag != 0)
+		fe_input_remove (hook->tag);
+
+	hook->type = HOOK_DELETED;	/* expunge later */
+
+	if (hook->name)
+		free (hook->name);	/* NULL for timers & fds */
+	if (hook->help_text)
+		free (hook->help_text);	/* NULL for non-commands */
+
+	return hook->userdata;
+}
+
+xchat_hook *
+xchat_hook_command (xchat_plugin *ph, const char *name, int pri,
+						  xchat_cmd_cb *callb, const char *help_text, void *userdata)
+{
+	return plugin_add_hook (ph, HOOK_COMMAND, pri, name, help_text, callb, 0,
+									userdata);
+}
+
+xchat_hook *
+xchat_hook_server (xchat_plugin *ph, const char *name, int pri,
+						 xchat_serv_cb *callb, void *userdata)
+{
+	return plugin_add_hook (ph, HOOK_SERVER, pri, name, 0, callb, 0, userdata);
+}
+
+xchat_hook *
+xchat_hook_print (xchat_plugin *ph, const char *name, int pri,
+						xchat_print_cb *callb, void *userdata)
+{
+	return plugin_add_hook (ph, HOOK_PRINT, pri, name, 0, callb, 0, userdata);
+}
+
+xchat_hook *
+xchat_hook_timer (xchat_plugin *ph, int timeout, xchat_timer_cb *callb,
+					   void *userdata)
+{
+	return plugin_add_hook (ph, HOOK_TIMER, 0, 0, 0, callb, timeout, userdata);
+}
+
+xchat_hook *
+xchat_hook_fd (xchat_plugin *ph, int fd, int flags,
+					xchat_fd_cb *callb, void *userdata)
+{
+	xchat_hook *hook;
+
+	hook = plugin_add_hook (ph, HOOK_FD, 0, 0, 0, callb, 0, userdata);
+	hook->pri = fd;
+	/* plugin hook_fd flags correspond exactly to FIA_* flags (fe.h) */
+	hook->tag = fe_input_add (fd, flags, plugin_fd_cb, hook);
+
+	return hook;
+}
+
+void
+xchat_print (xchat_plugin *ph, const char *text)
+{
+	if (!is_session (ph->context))
+	{
+		DEBUG(PrintTextf(0, "%s\txchat_print called without a valid context.\n", ph->name));
+		return;
+	}
+
+	PrintText (ph->context, (char *)text);
+}
+
+void
+xchat_printf (xchat_plugin *ph, const char *format, ...)
+{
+	va_list args;
+	char *buf;
+
+	va_start (args, format);
+	buf = g_strdup_vprintf (format, args);
+	va_end (args);
+
+	xchat_print (ph, buf);
+	g_free (buf);
+}
+
+void
+xchat_command (xchat_plugin *ph, const char *command)
+{
+	char *conv;
+	int len = -1;
+
+	if (!is_session (ph->context))
+	{
+		DEBUG(PrintTextf(0, "%s\txchat_command called without a valid context.\n", ph->name));
+		return;
+	}
+
+	/* scripts/plugins continue to send non-UTF8... *sigh* */
+	conv = text_validate ((char **)&command, &len);
+	handle_command (ph->context, (char *)command, FALSE);
+	g_free (conv);
+}
+
+void
+xchat_commandf (xchat_plugin *ph, const char *format, ...)
+{
+	va_list args;
+	char *buf;
+
+	va_start (args, format);
+	buf = g_strdup_vprintf (format, args);
+	va_end (args);
+
+	xchat_command (ph, buf);
+	g_free (buf);
+}
+
+int
+xchat_nickcmp (xchat_plugin *ph, const char *s1, const char *s2)
+{
+	return ((session *)ph->context)->server->p_cmp (s1, s2);
+}
+
+xchat_context *
+xchat_get_context (xchat_plugin *ph)
+{
+	return ph->context;
+}
+
+int
+xchat_set_context (xchat_plugin *ph, xchat_context *context)
+{
+	if (is_session (context))
+	{
+		ph->context = context;
+		return 1;
+	}
+	return 0;
+}
+
+xchat_context *
+xchat_find_context (xchat_plugin *ph, const char *servname, const char *channel)
+{
+	GSList *slist, *clist, *sessions = NULL;
+	server *serv;
+	session *sess;
+	char *netname;
+
+	if (servname == NULL && channel == NULL)
+		return current_sess;
+
+	slist = serv_list;
+	while (slist)
+	{
+		serv = slist->data;
+		netname = server_get_network (serv, TRUE);
+
+		if (servname == NULL ||
+			 rfc_casecmp (servname, serv->servername) == 0 ||
+			 strcasecmp (servname, serv->hostname) == 0 ||
+			 strcasecmp (servname, netname) == 0)
+		{
+			if (channel == NULL)
+				return serv->front_session;
+
+			clist = sess_list;
+			while (clist)
+			{
+				sess = clist->data;
+				if (sess->server == serv)
+				{
+					if (rfc_casecmp (channel, sess->channel) == 0)
+					{
+						if (sess->server == ph->context->server)
+						{
+							g_slist_free (sessions);
+							return sess;
+						} else
+						{
+							sessions = g_slist_prepend (sessions, sess);
+						}
+					}
+				}
+				clist = clist->next;
+			}
+		}
+		slist = slist->next;
+	}
+
+	if (sessions)
+	{
+		sessions = g_slist_reverse (sessions);
+		sess = sessions->data;
+		g_slist_free (sessions);
+		return sess;
+	}
+
+	return NULL;
+}
+
+const char *
+xchat_get_info (xchat_plugin *ph, const char *id)
+{
+	session *sess;
+	guint32 hash;
+
+	/*                 1234567890 */
+	if (!strncmp (id, "event_text", 10))
+	{
+		char *e = (char *)id + 10;
+		if (*e == ' ') e++;	/* 2.8.0 only worked without a space */
+		return text_find_format_string (e);
+	}
+
+	hash = str_hash (id);
+	/* do the session independant ones first */
+	switch (hash)
+	{
+	case 0x325acab5:	/* libdirfs */
+		return XCHATLIBDIR;
+
+	case 0x14f51cd8: /* version */
+		return PACKAGE_VERSION;
+
+	case 0xdd9b1abd:	/* xchatdir */
+		return get_xdir_utf8 ();
+
+	case 0xe33f6c4a:	/* xchatdirfs */
+		return get_xdir_fs ();
+	}
+
+	sess = ph->context;
+	if (!is_session (sess))
+	{
+		DEBUG(PrintTextf(0, "%s\txchat_get_info called without a valid context.\n", ph->name));
+		return NULL;
+	}
+
+	switch (hash)
+	{
+	case 0x2de2ee: /* away */
+		if (sess->server->is_away)
+			return sess->server->last_away_reason;
+		return NULL;
+
+  	case 0x2c0b7d03: /* channel */
+		return sess->channel;
+
+	case 0x2c0d614c: /* charset */
+		{
+			const char *locale;
+
+			if (sess->server->encoding)
+				return sess->server->encoding;
+
+			locale = NULL;
+			g_get_charset (&locale);
+			return locale;
+		}
+
+	case 0x30f5a8: /* host */
+		return sess->server->hostname;
+
+	case 0x1c0e99c1: /* inputbox */
+		return fe_get_inputbox_contents (sess);
+
+	case 0x633fb30:	/* modes */
+		return sess->current_modes;
+
+	case 0x6de15a2e:	/* network */
+		return server_get_network (sess->server, FALSE);
+
+	case 0x339763: /* nick */
+		return sess->server->nick;
+
+	case 0x438fdf9: /* nickserv */
+		if (sess->server->network)
+			return ((ircnet *)sess->server->network)->nickserv;
+		return NULL;
+
+	case 0xca022f43: /* server */
+		if (!sess->server->connected)
+			return NULL;
+		return sess->server->servername;
+
+	case 0x696cd2f: /* topic */
+		return sess->topic;
+
+	case 0x3419f12d: /* gtkwin_ptr */
+		return fe_gui_info_ptr (sess, 1);
+
+	case 0x506d600b: /* native win_ptr */
+		return fe_gui_info_ptr (sess, 0);
+
+	case 0x6d3431b5: /* win_status */
+		switch (fe_gui_info (sess, 0))	/* check window status */
+		{
+		case 0: return "normal";
+		case 1: return "active";
+		case 2: return "hidden";
+		}
+		return NULL;
+	}
+
+	return NULL;
+}
+
+int
+xchat_get_prefs (xchat_plugin *ph, const char *name, const char **string, int *integer)
+{
+	int i = 0;
+
+	/* some special run-time info (not really prefs, but may aswell throw it in here) */
+	switch (str_hash (name))
+	{
+		case 0xf82136c4: /* state_cursor */
+			*integer = fe_get_inputbox_cursor (ph->context);
+			return 2;
+
+		case 0xd1b: /* id */
+			*integer = ph->context->server->id;
+			return 2;
+	}
+	
+	do
+	{
+		if (!strcasecmp (name, vars[i].name))
+		{
+			switch (vars[i].type)
+			{
+			case TYPE_STR:
+				*string = ((char *) &prefs + vars[i].offset);
+				return 1;
+
+			case TYPE_INT:
+				*integer = *((int *) &prefs + vars[i].offset);
+				return 2;
+
+			default:
+			/*case TYPE_BOOL:*/
+				if (*((int *) &prefs + vars[i].offset))
+					*integer = 1;
+				else
+					*integer = 0;
+				return 3;
+			}
+		}
+		i++;
+	}
+	while (vars[i].name);
+
+	return 0;
+}
+
+xchat_list *
+xchat_list_get (xchat_plugin *ph, const char *name)
+{
+	xchat_list *list;
+
+	list = malloc (sizeof (xchat_list));
+	list->pos = NULL;
+
+	switch (str_hash (name))
+	{
+	case 0x556423d0: /* channels */
+		list->type = LIST_CHANNELS;
+		list->next = sess_list;
+		break;
+
+	case 0x183c4:	/* dcc */
+		list->type = LIST_DCC;
+		list->next = dcc_list;
+		break;
+
+	case 0xb90bfdd2:	/* ignore */
+		list->type = LIST_IGNORE;
+		list->next = ignore_list;
+		break;
+
+	case 0xc2079749:	/* notify */
+		list->type = LIST_NOTIFY;
+		list->next = notify_list;
+		list->head = (void *)ph->context;	/* reuse this pointer */
+		break;
+
+	case 0x6a68e08: /* users */
+		if (is_session (ph->context))
+		{
+			list->type = LIST_USERS;
+			list->head = list->next = userlist_flat_list (ph->context);
+			fe_userlist_set_selected (ph->context);
+			break;
+		}	/* fall through */
+
+	default:
+		free (list);
+		return NULL;
+	}
+
+	return list;
+}
+
+void
+xchat_list_free (xchat_plugin *ph, xchat_list *xlist)
+{
+	if (xlist->type == LIST_USERS)
+		g_slist_free (xlist->head);
+	free (xlist);
+}
+
+int
+xchat_list_next (xchat_plugin *ph, xchat_list *xlist)
+{
+	if (xlist->next == NULL)
+		return 0;
+
+	xlist->pos = xlist->next;
+	xlist->next = xlist->pos->next;
+
+	/* NOTIFY LIST: Find the entry which matches the context
+		of the plugin when list_get was originally called. */
+	if (xlist->type == LIST_NOTIFY)
+	{
+		xlist->notifyps = notify_find_server_entry (xlist->pos->data,
+													((session *)xlist->head)->server);
+		if (!xlist->notifyps)
+			return 0;
+	}
+
+	return 1;
+}
+
+const char * const *
+xchat_list_fields (xchat_plugin *ph, const char *name)
+{
+	static const char * const dcc_fields[] =
+	{
+		"iaddress32","icps",		"sdestfile","sfile",		"snick",	"iport",
+		"ipos", "iposhigh", "iresume", "iresumehigh", "isize", "isizehigh", "istatus", "itype", NULL
+	};
+	static const char * const channels_fields[] =
+	{
+		"schannel",	"schantypes", "pcontext", "iflags", "iid", "ilag", "imaxmodes",
+		"snetwork", "snickmodes", "snickprefixes", "iqueue", "sserver", "itype", "iusers",
+		NULL
+	};
+	static const char * const ignore_fields[] =
+	{
+		"iflags", "smask", NULL
+	};
+	static const char * const notify_fields[] =
+	{
+		"iflags", "snetworks", "snick", "toff", "ton", "tseen", NULL
+	};
+	static const char * const users_fields[] =
+	{
+		"iaway", "shost", "tlasttalk", "snick", "sprefix", "srealname", "iselected", NULL
+	};
+	static const char * const list_of_lists[] =
+	{
+		"channels",	"dcc", "ignore", "notify", "users", NULL
+	};
+
+	switch (str_hash (name))
+	{
+	case 0x556423d0:	/* channels */
+		return channels_fields;
+	case 0x183c4:		/* dcc */
+		return dcc_fields;
+	case 0xb90bfdd2:	/* ignore */
+		return ignore_fields;
+	case 0xc2079749:	/* notify */
+		return notify_fields;
+	case 0x6a68e08:	/* users */
+		return users_fields;
+	case 0x6236395:	/* lists */
+		return list_of_lists;
+	}
+
+	return NULL;
+}
+
+time_t
+xchat_list_time (xchat_plugin *ph, xchat_list *xlist, const char *name)
+{
+	guint32 hash = str_hash (name);
+	gpointer data;
+
+	switch (xlist->type)
+	{
+	case LIST_NOTIFY:
+		if (!xlist->notifyps)
+			return (time_t) -1;
+		switch (hash)
+		{
+		case 0x1ad6f:	/* off */
+			return xlist->notifyps->lastoff;
+		case 0xddf:	/* on */
+			return xlist->notifyps->laston;
+		case 0x35ce7b:	/* seen */
+			return xlist->notifyps->lastseen;
+		}
+		break;
+
+	case LIST_USERS:
+		data = xlist->pos->data;
+		switch (hash)
+		{
+		case 0xa9118c42:	/* lasttalk */
+			return ((struct User *)data)->lasttalk;
+		}
+	}
+
+	return (time_t) -1;
+}
+
+const char *
+xchat_list_str (xchat_plugin *ph, xchat_list *xlist, const char *name)
+{
+	guint32 hash = str_hash (name);
+	gpointer data = ph->context;
+	int type = LIST_CHANNELS;
+
+	/* a NULL xlist is a shortcut to current "channels" context */
+	if (xlist)
+	{
+		data = xlist->pos->data;
+		type = xlist->type;
+	}
+
+	switch (type)
+	{
+	case LIST_CHANNELS:
+		switch (hash)
+		{
+		case 0x2c0b7d03: /* channel */
+			return ((session *)data)->channel;
+		case 0x577e0867: /* chantypes */
+			return ((session *)data)->server->chantypes;
+		case 0x38b735af: /* context */
+			return data;	/* this is a session * */
+		case 0x6de15a2e: /* network */
+			return server_get_network (((session *)data)->server, FALSE);
+		case 0x8455e723: /* nickprefixes */
+			return ((session *)data)->server->nick_prefixes;
+		case 0x829689ad: /* nickmodes */
+			return ((session *)data)->server->nick_modes;
+		case 0xca022f43: /* server */
+			return ((session *)data)->server->servername;
+		}
+		break;
+
+	case LIST_DCC:
+		switch (hash)
+		{
+		case 0x3d9ad31e:	/* destfile */
+			return ((struct DCC *)data)->destfile;
+		case 0x2ff57c:	/* file */
+			return ((struct DCC *)data)->file;
+		case 0x339763: /* nick */
+			return ((struct DCC *)data)->nick;
+		}
+		break;
+
+	case LIST_IGNORE:
+		switch (hash)
+		{
+		case 0x3306ec:	/* mask */
+			return ((struct ignore *)data)->mask;
+		}
+		break;
+
+	case LIST_NOTIFY:
+		switch (hash)
+		{
+		case 0x4e49ec05:	/* networks */
+			return ((struct notify *)data)->networks;
+		case 0x339763: /* nick */
+			return ((struct notify *)data)->name;
+		}
+		break;
+
+	case LIST_USERS:
+		switch (hash)
+		{
+		case 0x339763: /* nick */
+			return ((struct User *)data)->nick;
+		case 0x30f5a8: /* host */
+			return ((struct User *)data)->hostname;
+		case 0xc594b292: /* prefix */
+			return ((struct User *)data)->prefix;
+		case 0xccc6d529: /* realname */
+			return ((struct User *)data)->realname;
+		}
+		break;
+	}
+
+	return NULL;
+}
+
+int
+xchat_list_int (xchat_plugin *ph, xchat_list *xlist, const char *name)
+{
+	guint32 hash = str_hash (name);
+	gpointer data = ph->context;
+	int tmp, type = LIST_CHANNELS;
+
+	/* a NULL xlist is a shortcut to current "channels" context */
+	if (xlist)
+	{
+		data = xlist->pos->data;
+		type = xlist->type;
+	}
+
+	switch (type)
+	{
+	case LIST_DCC:
+		switch (hash)
+		{
+		case 0x34207553: /* address32 */
+			return ((struct DCC *)data)->addr;
+		case 0x181a6: /* cps */
+			return ((struct DCC *)data)->cps;
+		case 0x349881: /* port */
+			return ((struct DCC *)data)->port;
+		case 0x1b254: /* pos */
+			return ((struct DCC *)data)->pos & 0xffffffff;
+		case 0xe8a945f6: /* poshigh */
+			return (((struct DCC *)data)->pos >> 32) & 0xffffffff;
+		case 0xc84dc82d: /* resume */
+			return ((struct DCC *)data)->resumable & 0xffffffff;
+		case 0xded4c74f: /* resumehigh */
+			return (((struct DCC *)data)->resumable >> 32) & 0xffffffff;
+		case 0x35e001: /* size */
+			return ((struct DCC *)data)->size & 0xffffffff;
+		case 0x3284d523: /* sizehigh */
+			return (((struct DCC *)data)->size >> 32) & 0xffffffff;
+		case 0xcacdcff2: /* status */
+			return ((struct DCC *)data)->dccstat;
+		case 0x368f3a: /* type */
+			return ((struct DCC *)data)->type;
+		}
+		break;
+
+	case LIST_IGNORE:
+		switch (hash)
+		{
+		case 0x5cfee87:	/* flags */
+			return ((struct ignore *)data)->type;
+		}
+		break;
+
+	case LIST_CHANNELS:
+		switch (hash)
+		{
+		case 0xd1b:	/* id */
+			return ((struct session *)data)->server->id;
+		case 0x5cfee87:	/* flags */
+			tmp = ((struct session *)data)->alert_taskbar;   /* bit 10 */
+			tmp <<= 1;
+			tmp |= ((struct session *)data)->alert_tray;         /* 9 */
+			tmp <<= 1;
+			tmp |= ((struct session *)data)->alert_beep;         /* 8 */
+			tmp <<= 1;
+			/*tmp |= ((struct session *)data)->color_paste;*/    /* 7 */
+			tmp <<= 1;
+			tmp |= ((struct session *)data)->text_hidejoinpart;   /* 6 */
+			tmp <<= 1;
+			tmp |= ((struct session *)data)->server->have_idmsg; /* 5 */
+			tmp <<= 1;
+			tmp |= ((struct session *)data)->server->have_whox;  /* 4 */
+			tmp <<= 1;
+			tmp |= ((struct session *)data)->server->end_of_motd;/* 3 */
+			tmp <<= 1;
+			tmp |= ((struct session *)data)->server->is_away;    /* 2 */
+			tmp <<= 1;
+			tmp |= ((struct session *)data)->server->connecting; /* 1 */ 
+			tmp <<= 1;
+			tmp |= ((struct session *)data)->server->connected;  /* 0 */
+			return tmp;
+		case 0x1a192: /* lag */
+			return ((struct session *)data)->server->lag;
+		case 0x1916144c: /* maxmodes */
+			return ((struct session *)data)->server->modes_per_line;
+		case 0x66f1911: /* queue */
+			return ((struct session *)data)->server->sendq_len;
+		case 0x368f3a:	/* type */
+			return ((struct session *)data)->type;
+		case 0x6a68e08: /* users */
+			return ((struct session *)data)->total;
+		}
+		break;
+
+	case LIST_NOTIFY:
+		if (!xlist->notifyps)
+			return -1;
+		switch (hash)
+		{
+		case 0x5cfee87: /* flags */
+			return xlist->notifyps->ison;
+		}
+
+	case LIST_USERS:
+		switch (hash)
+		{
+		case 0x2de2ee:	/* away */
+			return ((struct User *)data)->away;
+		case 0x4705f29b: /* selected */
+			return ((struct User *)data)->selected;
+		}
+		break;
+
+	}
+
+	return -1;
+}
+
+void *
+xchat_plugingui_add (xchat_plugin *ph, const char *filename,
+							const char *name, const char *desc,
+							const char *version, char *reserved)
+{
+#ifdef USE_PLUGIN
+	ph = plugin_list_add (NULL, strdup (filename), strdup (name), strdup (desc),
+								 strdup (version), NULL, NULL, TRUE, TRUE);
+	fe_pluginlist_update ();
+#endif
+
+	return ph;
+}
+
+void
+xchat_plugingui_remove (xchat_plugin *ph, void *handle)
+{
+#ifdef USE_PLUGIN
+	plugin_free (handle, FALSE, FALSE);
+#endif
+}
+
+int
+xchat_emit_print (xchat_plugin *ph, const char *event_name, ...)
+{
+	va_list args;
+	/* currently only 4 because no events use more than 4.
+		This can be easily expanded without breaking the API. */
+	char *argv[4] = {NULL, NULL, NULL, NULL};
+	int i = 0;
+
+	va_start (args, event_name);
+	while (1)
+	{
+		argv[i] = va_arg (args, char *);
+		if (!argv[i])
+			break;
+		i++;
+		if (i >= 4)
+			break;
+	}
+
+	i = text_emit_by_name ((char *)event_name, ph->context, argv[0], argv[1],
+								  argv[2], argv[3]);
+	va_end (args);
+
+	return i;
+}
+
+char *
+xchat_gettext (xchat_plugin *ph, const char *msgid)
+{
+	/* so that plugins can use xchat's internal gettext strings. */
+	/* e.g. The EXEC plugin uses this on Windows. */
+	return _(msgid);
+}
+
+void
+xchat_send_modes (xchat_plugin *ph, const char **targets, int ntargets, int modes_per_line, char sign, char mode)
+{
+	char tbuf[514];	/* modes.c needs 512 + null */
+
+	send_channel_modes (ph->context, tbuf, (char **)targets, 0, ntargets, sign, mode, modes_per_line);
+}
+
+char *
+xchat_strip (xchat_plugin *ph, const char *str, int len, int flags)
+{
+	return strip_color ((char *)str, len, flags);
+}
+
+void
+xchat_free (xchat_plugin *ph, void *ptr)
+{
+	g_free (ptr);
+}
diff --git a/src/common/plugin.h b/src/common/plugin.h
new file mode 100644
index 00000000..b0c89d1b
--- /dev/null
+++ b/src/common/plugin.h
@@ -0,0 +1,132 @@
+#ifndef XCHAT_COMMONPLUGIN_H
+#define XCHAT_COMMONPLUGIN_H
+
+#ifdef PLUGIN_C
+struct _xchat_plugin
+{
+	/* Keep these insync with xchat-plugin.h */
+	/* !!don't change the order, to keep binary compat!! */
+	xchat_hook *(*xchat_hook_command) (xchat_plugin *ph,
+		    const char *name,
+		    int pri,
+		    int (*callback) (char *word[], char *word_eol[], void *user_data),
+		    const char *help_text,
+		    void *userdata);
+	xchat_hook *(*xchat_hook_server) (xchat_plugin *ph,
+		   const char *name,
+		   int pri,
+		   int (*callback) (char *word[], char *word_eol[], void *user_data),
+		   void *userdata);
+	xchat_hook *(*xchat_hook_print) (xchat_plugin *ph,
+		  const char *name,
+		  int pri,
+		  int (*callback) (char *word[], void *user_data),
+		  void *userdata);
+	xchat_hook *(*xchat_hook_timer) (xchat_plugin *ph,
+		  int timeout,
+		  int (*callback) (void *user_data),
+		  void *userdata);
+	xchat_hook *(*xchat_hook_fd) (xchat_plugin *ph,
+		   int fd,
+		   int flags,
+		   int (*callback) (int fd, int flags, void *user_data),
+		   void *userdata);
+	void *(*xchat_unhook) (xchat_plugin *ph,
+	      xchat_hook *hook);
+	void (*xchat_print) (xchat_plugin *ph,
+	     const char *text);
+	void (*xchat_printf) (xchat_plugin *ph,
+	      const char *format, ...);
+	void (*xchat_command) (xchat_plugin *ph,
+	       const char *command);
+	void (*xchat_commandf) (xchat_plugin *ph,
+		const char *format, ...);
+	int (*xchat_nickcmp) (xchat_plugin *ph,
+	       const char *s1,
+	       const char *s2);
+	int (*xchat_set_context) (xchat_plugin *ph,
+		   xchat_context *ctx);
+	xchat_context *(*xchat_find_context) (xchat_plugin *ph,
+		    const char *servname,
+		    const char *channel);
+	xchat_context *(*xchat_get_context) (xchat_plugin *ph);
+	const char *(*xchat_get_info) (xchat_plugin *ph,
+		const char *id);
+	int (*xchat_get_prefs) (xchat_plugin *ph,
+		 const char *name,
+		 const char **string,
+		 int *integer);
+	xchat_list * (*xchat_list_get) (xchat_plugin *ph,
+		const char *name);
+	void (*xchat_list_free) (xchat_plugin *ph,
+		 xchat_list *xlist);
+	const char * const * (*xchat_list_fields) (xchat_plugin *ph,
+		   const char *name);
+	int (*xchat_list_next) (xchat_plugin *ph,
+		 xchat_list *xlist);
+	const char * (*xchat_list_str) (xchat_plugin *ph,
+		xchat_list *xlist,
+		const char *name);
+	int (*xchat_list_int) (xchat_plugin *ph,
+		xchat_list *xlist,
+		const char *name);
+	void * (*xchat_plugingui_add) (xchat_plugin *ph,
+		     const char *filename,
+		     const char *name,
+		     const char *desc,
+		     const char *version,
+		     char *reserved);
+	void (*xchat_plugingui_remove) (xchat_plugin *ph,
+			void *handle);
+	int (*xchat_emit_print) (xchat_plugin *ph,
+			const char *event_name, ...);
+	void *(*xchat_read_fd) (xchat_plugin *ph);
+	time_t (*xchat_list_time) (xchat_plugin *ph,
+		xchat_list *xlist,
+		const char *name);
+	char *(*xchat_gettext) (xchat_plugin *ph,
+		const char *msgid);
+	void (*xchat_send_modes) (xchat_plugin *ph,
+		  const char **targets,
+		  int ntargets,
+		  int modes_per_line,
+		  char sign,
+		  char mode);
+	char *(*xchat_strip) (xchat_plugin *ph,
+	     const char *str,
+	     int len,
+	     int flags);
+	void (*xchat_free) (xchat_plugin *ph,
+	    void *ptr);
+	void *(*xchat_dummy4) (xchat_plugin *ph);
+	void *(*xchat_dummy3) (xchat_plugin *ph);
+	void *(*xchat_dummy2) (xchat_plugin *ph);
+	void *(*xchat_dummy1) (xchat_plugin *ph);
+	/* PRIVATE FIELDS! */
+	void *handle;		/* from dlopen */
+	char *filename;	/* loaded from */
+	char *name;
+	char *desc;
+	char *version;
+	session *context;
+	void *deinit_callback;	/* pointer to xchat_plugin_deinit */
+	unsigned int fake:1;		/* fake plugin. Added by xchat_plugingui_add() */
+	unsigned int free_strings:1;		/* free name,desc,version? */
+};
+#endif
+
+char *plugin_load (session *sess, char *filename, char *arg);
+void plugin_add (session *sess, char *filename, void *handle, void *init_func, void *deinit_func, char *arg, int fake);
+int plugin_kill (char *name, int by_filename);
+void plugin_kill_all (void);
+void plugin_auto_load (session *sess);
+int plugin_emit_command (session *sess, char *name, char *word[], char *word_eol[]);
+int plugin_emit_server (session *sess, char *name, char *word[], char *word_eol[]);
+int plugin_emit_print (session *sess, char *word[]);
+int plugin_emit_dummy_print (session *sess, char *name);
+int plugin_emit_keypress (session *sess, unsigned int state, unsigned int keyval, int len, char *string);
+GList* plugin_command_list(GList *tmp_list);
+int plugin_show_help (session *sess, char *cmd);
+void plugin_command_foreach (session *sess, void *userdata, void (*cb) (session *sess, void *userdata, char *name, char *usage));
+
+#endif
diff --git a/src/common/proto-irc.c b/src/common/proto-irc.c
new file mode 100644
index 00000000..f1df6ccd
--- /dev/null
+++ b/src/common/proto-irc.c
@@ -0,0 +1,1260 @@
+/* X-Chat
+ * Copyright (C) 2002 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
+ */
+
+/* IRC RFC1459(+commonly used extensions) protocol implementation */
+
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdarg.h>
+
+#include "xchat.h"
+#include "ctcp.h"
+#include "fe.h"
+#include "ignore.h"
+#include "inbound.h"
+#include "modes.h"
+#include "notify.h"
+#include "plugin.h"
+#include "server.h"
+#include "text.h"
+#include "outbound.h"
+#include "util.h"
+#include "xchatc.h"
+
+
+static void
+irc_login (server *serv, char *user, char *realname)
+{
+	if (serv->password[0])
+		tcp_sendf (serv, "PASS %s\r\n", serv->password);
+
+	tcp_sendf (serv,
+				  "NICK %s\r\n"
+				  "USER %s %s %s :%s\r\n",
+				  serv->nick, user, user, serv->servername, realname);
+}
+
+static void
+irc_nickserv (server *serv, char *cmd, char *arg1, char *arg2, char *arg3)
+{
+	/* are all ircd authors idiots? */
+	switch (serv->nickservtype)
+	{
+	case 0:
+		tcp_sendf (serv, "PRIVMSG NICKSERV :%s %s%s%s\r\n", cmd, arg1, arg2, arg3);
+		break;
+	case 1:
+		tcp_sendf (serv, "NICKSERV %s %s%s%s\r\n", cmd, arg1, arg2, arg3);
+		break;
+	case 2:
+		tcp_sendf (serv, "NS %s %s%s%s\r\n", cmd, arg1, arg2, arg3);
+		break;
+	case 3:
+		tcp_sendf (serv, "PRIVMSG NS :%s %s%s%s\r\n", cmd, arg1, arg2, arg3);
+		break;
+	case 4:
+		/* why couldn't QuakeNet implement one of the existing ones? */
+		tcp_sendf (serv, "AUTH %s%s%s\r\n", cmd, arg1, arg2, arg3);
+	}
+}
+
+static void
+irc_ns_identify (server *serv, char *pass)
+{
+	irc_nickserv (serv, "IDENTIFY", pass, "", "");
+}
+
+static void
+irc_ns_ghost (server *serv, char *usname, char *pass)
+{
+	if (serv->nickservtype != 4)
+		irc_nickserv (serv, "GHOST", usname, " ", pass);
+}
+
+static void
+irc_join (server *serv, char *channel, char *key)
+{
+	if (key[0])
+		tcp_sendf (serv, "JOIN %s %s\r\n", channel, key);
+	else
+		tcp_sendf (serv, "JOIN %s\r\n", channel);
+}
+
+static void
+irc_join_list_flush (server *serv, GString *c, GString *k)
+{
+	char *chanstr, *keystr;
+
+	chanstr = g_string_free (c, FALSE);
+	keystr = g_string_free (k, FALSE);
+	if (chanstr[0])
+	{
+		if (keystr[0])
+			tcp_sendf (serv, "JOIN %s %s\r\n", chanstr, keystr);
+		else
+			tcp_sendf (serv, "JOIN %s\r\n", chanstr);
+	}
+	g_free (chanstr);
+	g_free (keystr);
+}
+
+/* join a whole list of channels & keys, split to multiple lines
+ * to get around 512 limit */
+
+static void
+irc_join_list (server *serv, GSList *channels, GSList *keys)
+{
+	GSList *clist;
+	GSList *klist;
+	GString *c = g_string_new (NULL);
+	GString *k = g_string_new (NULL);
+	int len;
+	int add;
+	int i, j;
+
+	i = j = 0;
+	len = 9; /* "JOIN<space><space>\r\n" */
+	clist = channels;
+	klist = keys;
+
+	while (clist)
+	{
+		/* measure how many bytes this channel would add... */
+		if (1)
+		{
+			add = strlen (clist->data);
+			if (i != 0)
+				add++;	/* comma */
+		}
+
+		if (klist->data)
+		{
+			add += strlen (klist->data);
+		}
+		else
+		{
+			add++;	/* 'x' filler */
+		}
+
+		if (j != 0)
+			add++;	/* comma */
+
+		/* too big? dump buffer and start a fresh one */
+		if (len + add > 512)
+		{
+			irc_join_list_flush (serv, c, k);
+
+			c = g_string_new (NULL);
+			k = g_string_new (NULL);
+			i = j = 0;
+			len = 9;
+		}
+
+		/* now actually add it to our GStrings */
+		if (1)
+		{
+			add = strlen (clist->data);
+			if (i != 0)
+			{
+				add++;
+				g_string_append_c (c, ',');
+			}
+			g_string_append (c, clist->data);
+			i++;
+		}
+
+		if (klist->data)
+		{
+			add += strlen (klist->data);
+			if (j != 0)
+			{
+				add++;
+				g_string_append_c (k, ',');
+			}
+			g_string_append (k, klist->data);
+			j++;
+		}
+		else
+		{
+			add++;
+			if (j != 0)
+			{
+				add++;
+				g_string_append_c (k, ',');
+			}
+			g_string_append_c (k, 'x');
+			j++;
+		}
+
+		len += add;
+
+		klist = klist->next;
+		clist = clist->next;
+	}
+
+	irc_join_list_flush (serv, c, k);
+}
+
+static void
+irc_part (server *serv, char *channel, char *reason)
+{
+	if (reason[0])
+		tcp_sendf (serv, "PART %s :%s\r\n", channel, reason);
+	else
+		tcp_sendf (serv, "PART %s\r\n", channel);
+}
+
+static void
+irc_quit (server *serv, char *reason)
+{
+	if (reason[0])
+		tcp_sendf (serv, "QUIT :%s\r\n", reason);
+	else
+		tcp_send_len (serv, "QUIT\r\n", 6);
+}
+
+static void
+irc_set_back (server *serv)
+{
+	tcp_send_len (serv, "AWAY\r\n", 6);
+}
+
+static void
+irc_set_away (server *serv, char *reason)
+{
+	if (reason)
+	{
+		if (!reason[0])
+			reason = " ";
+	}
+	else
+	{
+		reason = " ";
+	}
+
+	tcp_sendf (serv, "AWAY :%s\r\n", reason);
+}
+
+static void
+irc_ctcp (server *serv, char *to, char *msg)
+{
+	tcp_sendf (serv, "PRIVMSG %s :\001%s\001\r\n", to, msg);
+}
+
+static void
+irc_nctcp (server *serv, char *to, char *msg)
+{
+	tcp_sendf (serv, "NOTICE %s :\001%s\001\r\n", to, msg);
+}
+
+static void
+irc_cycle (server *serv, char *channel, char *key)
+{
+	tcp_sendf (serv, "PART %s\r\nJOIN %s %s\r\n", channel, channel, key);
+}
+
+static void
+irc_kick (server *serv, char *channel, char *nick, char *reason)
+{
+	if (reason[0])
+		tcp_sendf (serv, "KICK %s %s :%s\r\n", channel, nick, reason);
+	else
+		tcp_sendf (serv, "KICK %s %s\r\n", channel, nick);
+}
+
+static void
+irc_invite (server *serv, char *channel, char *nick)
+{
+	tcp_sendf (serv, "INVITE %s %s\r\n", nick, channel);
+}
+
+static void
+irc_mode (server *serv, char *target, char *mode)
+{
+	tcp_sendf (serv, "MODE %s %s\r\n", target, mode);
+}
+
+/* find channel info when joined */
+
+static void
+irc_join_info (server *serv, char *channel)
+{
+	tcp_sendf (serv, "MODE %s\r\n", channel);
+}
+
+/* initiate userlist retreival */
+
+static void
+irc_user_list (server *serv, char *channel)
+{
+	tcp_sendf (serv, "WHO %s\r\n", channel);
+}
+
+/* userhost */
+
+static void
+irc_userhost (server *serv, char *nick)
+{
+	tcp_sendf (serv, "USERHOST %s\r\n", nick);
+}
+
+static void
+irc_away_status (server *serv, char *channel)
+{
+	if (serv->have_whox)
+		tcp_sendf (serv, "WHO %s %%ctnf,152\r\n", channel);
+	else
+		tcp_sendf (serv, "WHO %s\r\n", channel);
+}
+
+/*static void
+irc_get_ip (server *serv, char *nick)
+{
+	tcp_sendf (serv, "WHO %s\r\n", nick);
+}*/
+
+
+/*
+ *  Command: WHOIS
+ *     Parameters: [<server>] <nickmask>[,<nickmask>[,...]]
+ */
+static void
+irc_user_whois (server *serv, char *nicks)
+{
+	tcp_sendf (serv, "WHOIS %s\r\n", nicks);
+}
+
+static void
+irc_message (server *serv, char *channel, char *text)
+{
+	tcp_sendf (serv, "PRIVMSG %s :%s\r\n", channel, text);
+}
+
+static void
+irc_action (server *serv, char *channel, char *act)
+{
+	tcp_sendf (serv, "PRIVMSG %s :\001ACTION %s\001\r\n", channel, act);
+}
+
+static void
+irc_notice (server *serv, char *channel, char *text)
+{
+	tcp_sendf (serv, "NOTICE %s :%s\r\n", channel, text);
+}
+
+static void
+irc_topic (server *serv, char *channel, char *topic)
+{
+	if (!topic)
+		tcp_sendf (serv, "TOPIC %s :\r\n", channel);
+	else if (topic[0])
+		tcp_sendf (serv, "TOPIC %s :%s\r\n", channel, topic);
+	else
+		tcp_sendf (serv, "TOPIC %s\r\n", channel);
+}
+
+static void
+irc_list_channels (server *serv, char *arg, int min_users)
+{
+	if (arg[0])
+	{
+		tcp_sendf (serv, "LIST %s\r\n", arg);
+		return;
+	}
+
+	if (serv->use_listargs)
+		tcp_sendf (serv, "LIST >%d,<10000\r\n", min_users - 1);
+	else
+		tcp_send_len (serv, "LIST\r\n", 6);
+}
+
+static void
+irc_names (server *serv, char *channel)
+{
+	tcp_sendf (serv, "NAMES %s\r\n", channel);
+}
+
+static void
+irc_change_nick (server *serv, char *new_nick)
+{
+	tcp_sendf (serv, "NICK %s\r\n", new_nick);
+}
+
+static void
+irc_ping (server *serv, char *to, char *timestring)
+{
+	if (*to)
+		tcp_sendf (serv, "PRIVMSG %s :\001PING %s\001\r\n", to, timestring);
+	else
+		tcp_sendf (serv, "PING %s\r\n", timestring);
+}
+
+static int
+irc_raw (server *serv, char *raw)
+{
+	int len;
+	char tbuf[4096];
+	if (*raw)
+	{
+		len = strlen (raw);
+		if (len < sizeof (tbuf) - 3)
+		{
+			len = snprintf (tbuf, sizeof (tbuf), "%s\r\n", raw);
+			tcp_send_len (serv, tbuf, len);
+		} else
+		{
+			tcp_send_len (serv, raw, len);
+			tcp_send_len (serv, "\r\n", 2);
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+/* ============================================================== */
+/* ======================= IRC INPUT ============================ */
+/* ============================================================== */
+
+
+static void
+channel_date (session *sess, char *chan, char *timestr)
+{
+	time_t timestamp = (time_t) atol (timestr);
+	char *tim = ctime (&timestamp);
+	tim[24] = 0;	/* get rid of the \n */
+	EMIT_SIGNAL (XP_TE_CHANDATE, sess, chan, tim, NULL, NULL, 0);
+}
+
+static void
+process_numeric (session * sess, int n,
+					  char *word[], char *word_eol[], char *text)
+{
+	server *serv = sess->server;
+	/* show whois is the server tab */
+	session *whois_sess = serv->server_session;
+
+	/* unless this setting is on */
+	if (prefs.irc_whois_front)
+		whois_sess = serv->front_session;
+
+	switch (n)
+	{
+	case 1:
+		inbound_login_start (sess, word[3], word[1]);
+		/* if network is PTnet then you must get your IP address
+			from "001" server message */
+		if ((strncmp(word[7], "PTnet", 5) == 0) &&
+			(strncmp(word[8], "IRC", 3) == 0) &&
+			(strncmp(word[9], "Network", 7) == 0) &&
+			(strrchr(word[10], '@') != NULL))
+		{
+			serv->use_who = FALSE;
+			if (prefs.ip_from_server)
+				inbound_foundip (sess, strrchr(word[10], '@')+1);
+		}
+
+		/* use /NICKSERV */
+		if (strcasecmp (word[7], "DALnet") == 0 ||
+			 strcasecmp (word[7], "BRASnet") == 0)
+			serv->nickservtype = 1;
+
+		/* use /NS */
+		else if (strcasecmp (word[7], "FreeNode") == 0)
+			serv->nickservtype = 2;
+
+		goto def;
+
+	case 4:	/* check the ircd type */
+		serv->use_listargs = FALSE;
+		serv->modes_per_line = 3;		/* default to IRC RFC */
+		if (strncmp (word[5], "bahamut", 7) == 0)				/* DALNet */
+		{
+			serv->use_listargs = TRUE;		/* use the /list args */
+		} else if (strncmp (word[5], "u2.10.", 6) == 0)		/* Undernet */
+		{
+			serv->use_listargs = TRUE;		/* use the /list args */
+			serv->modes_per_line = 6;		/* allow 6 modes per line */
+		} else if (strncmp (word[5], "glx2", 4) == 0)
+		{
+			serv->use_listargs = TRUE;		/* use the /list args */
+		}
+		goto def;
+
+	case 5:
+		inbound_005 (serv, word);
+		goto def;
+
+	case 263:	/*Server load is temporarily too heavy */
+		if (fe_is_chanwindow (sess->server))
+		{
+			fe_chan_list_end (sess->server);
+			fe_message (word_eol[5] + 1, FE_MSG_ERROR);
+		}
+		goto def;
+
+	case 290:	/* CAPAB reply */
+		if (strstr (word_eol[1], "IDENTIFY-MSG"))
+		{
+			serv->have_idmsg = TRUE;
+			break;
+		}
+		goto def;
+
+	case 301:
+		inbound_away (serv, word[4],
+						(word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5]);
+		break;
+
+	case 302:
+		if (serv->skip_next_userhost)
+		{
+			char *eq = strchr (word[4], '=');
+			if (eq)
+			{
+				*eq = 0;
+				if (!serv->p_cmp (word[4] + 1, serv->nick))
+				{
+					char *at = strrchr (eq + 1, '@');
+					if (at)
+						inbound_foundip (sess, at + 1);
+				}
+			}
+
+			serv->skip_next_userhost = FALSE;
+			break;
+		}
+		else goto def;
+
+	case 303:
+		word[4]++;
+		notify_markonline (serv, word);
+		break;
+
+	case 305:
+		inbound_uback (serv);
+		goto def;
+
+	case 306:
+		inbound_uaway (serv);
+		goto def;
+
+	case 312:
+		if (!serv->skip_next_whois)
+			EMIT_SIGNAL (XP_TE_WHOIS3, whois_sess, word[4], word_eol[5], NULL, NULL, 0);
+		else
+			inbound_user_info (sess, NULL, NULL, NULL, word[5], word[4], NULL, 0xff);
+		break;
+
+	case 311:	/* WHOIS 1st line */
+		serv->inside_whois = 1;
+		inbound_user_info_start (sess, word[4]);
+		if (!serv->skip_next_whois)
+			EMIT_SIGNAL (XP_TE_WHOIS1, whois_sess, word[4], word[5],
+							 word[6], word_eol[8] + 1, 0);
+		else
+			inbound_user_info (sess, NULL, word[5], word[6], NULL, word[4],
+									 word_eol[8][0] == ':' ? word_eol[8] + 1 : word_eol[8], 0xff);
+		break;
+
+	case 314:	/* WHOWAS */
+		inbound_user_info_start (sess, word[4]);
+		EMIT_SIGNAL (XP_TE_WHOIS1, whois_sess, word[4], word[5],
+						 word[6], word_eol[8] + 1, 0);
+		break;
+
+	case 317:
+		if (!serv->skip_next_whois)
+		{
+			time_t timestamp = (time_t) atol (word[6]);
+			long idle = atol (word[5]);
+			char *tim;
+			char outbuf[64];
+
+			snprintf (outbuf, sizeof (outbuf),
+						"%02ld:%02ld:%02ld", idle / 3600, (idle / 60) % 60,
+						idle % 60);
+			if (timestamp == 0)
+				EMIT_SIGNAL (XP_TE_WHOIS4, whois_sess, word[4],
+								 outbuf, NULL, NULL, 0);
+			else
+			{
+				tim = ctime (&timestamp);
+				tim[19] = 0; 	/* get rid of the \n */
+				EMIT_SIGNAL (XP_TE_WHOIS4T, whois_sess, word[4],
+								 outbuf, tim, NULL, 0);
+			}
+		}
+		break;
+
+	case 318:	/* END OF WHOIS */
+		if (!serv->skip_next_whois)
+			EMIT_SIGNAL (XP_TE_WHOIS6, whois_sess, word[4], NULL,
+							 NULL, NULL, 0);
+		serv->skip_next_whois = 0;
+		serv->inside_whois = 0;
+		break;
+
+	case 313:
+	case 319:
+		if (!serv->skip_next_whois)
+			EMIT_SIGNAL (XP_TE_WHOIS2, whois_sess, word[4],
+							 word_eol[5] + 1, NULL, NULL, 0);
+		break;
+
+	case 307:	/* dalnet version */
+	case 320:	/* :is an identified user */
+		if (!serv->skip_next_whois)
+			EMIT_SIGNAL (XP_TE_WHOIS_ID, whois_sess, word[4],
+							 word_eol[5] + 1, NULL, NULL, 0);
+		break;
+
+	case 321:
+		if (!fe_is_chanwindow (sess->server))
+			EMIT_SIGNAL (XP_TE_CHANLISTHEAD, serv->server_session, NULL, NULL, NULL, NULL, 0);
+		break;
+
+	case 322:
+		if (fe_is_chanwindow (sess->server))
+		{
+			fe_add_chan_list (sess->server, word[4], word[5], word_eol[6] + 1);
+		} else
+		{
+			PrintTextf (serv->server_session, "%-16s %-7d %s\017\n",
+							word[4], atoi (word[5]), word_eol[6] + 1);
+		}
+		break;
+
+	case 323:
+		if (!fe_is_chanwindow (sess->server))
+			EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1], word[2], NULL, 0);
+		else
+			fe_chan_list_end (sess->server);
+		break;
+
+	case 324:
+		sess = find_channel (serv, word[4]);
+		if (!sess)
+			sess = serv->server_session;
+		if (sess->ignore_mode)
+			sess->ignore_mode = FALSE;
+		else
+			EMIT_SIGNAL (XP_TE_CHANMODES, sess, word[4], word_eol[5],
+							 NULL, NULL, 0);
+		fe_update_mode_buttons (sess, 't', '-');
+		fe_update_mode_buttons (sess, 'n', '-');
+		fe_update_mode_buttons (sess, 's', '-');
+		fe_update_mode_buttons (sess, 'i', '-');
+		fe_update_mode_buttons (sess, 'p', '-');
+		fe_update_mode_buttons (sess, 'm', '-');
+		fe_update_mode_buttons (sess, 'l', '-');
+		fe_update_mode_buttons (sess, 'k', '-');
+		handle_mode (serv, word, word_eol, "", TRUE);
+		break;
+
+	case 329:
+		sess = find_channel (serv, word[4]);
+		if (sess)
+		{
+			if (sess->ignore_date)
+				sess->ignore_date = FALSE;
+			else
+				channel_date (sess, word[4], word[5]);
+		}
+		break;
+
+	case 330:
+		if (!serv->skip_next_whois)
+			EMIT_SIGNAL (XP_TE_WHOIS_AUTH, whois_sess, word[4],
+							 word_eol[6] + 1, word[5], NULL, 0);
+		break;
+
+	case 332:
+		inbound_topic (serv, word[4],
+						(word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5]);
+		break;
+
+	case 333:
+		inbound_topictime (serv, word[4], word[5], atol (word[6]));
+		break;
+
+#if 0
+	case 338:  /* Undernet Real user@host, Real IP */
+		EMIT_SIGNAL (XP_TE_WHOIS_REALHOST, sess, word[4], word[5], word[6], 
+			(word_eol[7][0]==':') ? word_eol[7]+1 : word_eol[7], 0);
+		break;
+#endif
+
+	case 341:						  /* INVITE ACK */
+		EMIT_SIGNAL (XP_TE_UINVITE, sess, word[4], word[5], serv->servername,
+						 NULL, 0);
+		break;
+
+	case 352:						  /* WHO */
+		{
+			unsigned int away = 0;
+			session *who_sess = find_channel (serv, word[4]);
+
+			if (*word[9] == 'G')
+				away = 1;
+
+			inbound_user_info (sess, word[4], word[5], word[6], word[7],
+									 word[8], word_eol[11], away);
+
+			/* try to show only user initiated whos */
+			if (!who_sess || !who_sess->doing_who)
+				EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1],
+								 word[2], NULL, 0);
+		}
+		break;
+
+	case 354:	/* undernet WHOX: used as a reply for irc_away_status */
+		{
+			unsigned int away = 0;
+			session *who_sess;
+
+			/* irc_away_status sends out a "152" */
+			if (!strcmp (word[4], "152"))
+			{
+				who_sess = find_channel (serv, word[5]);
+
+				if (*word[7] == 'G')
+					away = 1;
+
+				/* :SanJose.CA.us.undernet.org 354 z1 152 #zed1 z1 H@ */
+				inbound_user_info (sess, word[5], 0, 0, 0, word[6], 0, away);
+
+				/* try to show only user initiated whos */
+				if (!who_sess || !who_sess->doing_who)
+					EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text,
+									 word[1], word[2], NULL, 0);
+			} else
+				goto def;
+		}
+		break;
+
+	case 315:						  /* END OF WHO */
+		{
+			session *who_sess;
+			who_sess = find_channel (serv, word[4]);
+			if (who_sess)
+			{
+				if (!who_sess->doing_who)
+					EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text,
+									 word[1], word[2], NULL, 0);
+				who_sess->doing_who = FALSE;
+			} else
+			{
+				if (!serv->doing_dns)
+					EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text,
+									 word[1], word[2], NULL, 0);
+				serv->doing_dns = FALSE;
+			}
+		}
+		break;
+
+	case 348:	/* +e-list entry */
+		if (!inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], TRUE))
+			goto def;
+		break;
+
+	case 349:	/* end of exemption list */
+		sess = find_channel (serv, word[4]);
+		if (!sess)
+		{
+			sess = serv->front_session;
+			goto def;
+		}
+		if (!fe_is_banwindow (sess))
+			goto def;
+		fe_ban_list_end (sess, TRUE);
+		break;
+
+	case 353:						  /* NAMES */
+		inbound_nameslist (serv, word[5],
+							(word_eol[6][0] == ':') ? word_eol[6] + 1 : word_eol[6]);
+		break;
+
+	case 366:
+		if (!inbound_nameslist_end (serv, word[4]))
+			goto def;
+		break;
+
+	case 367: /* banlist entry */
+		inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], FALSE);
+		break;
+
+	case 368:
+		sess = find_channel (serv, word[4]);
+		if (!sess)
+		{
+			sess = serv->front_session;
+			goto def;
+		}
+		if (!fe_is_banwindow (sess))
+			goto def;
+		fe_ban_list_end (sess, FALSE);
+		break;
+
+	case 369:	/* WHOWAS end */
+	case 406:	/* WHOWAS error */
+		EMIT_SIGNAL (XP_TE_SERVTEXT, whois_sess, text, word[1], word[2], NULL, 0);
+		serv->inside_whois = 0;
+		break;
+
+	case 372:	/* motd text */
+	case 375:	/* motd start */
+		if (!prefs.skipmotd || serv->motd_skipped)
+			EMIT_SIGNAL (XP_TE_MOTD, serv->server_session, text, NULL, NULL,
+							 NULL, 0);
+		break;
+
+	case 376:	/* end of motd */
+	case 422:	/* motd file is missing */
+		inbound_login_end (sess, text);
+		break;
+
+	case 433:	/* nickname in use */
+	case 432:	/* erroneous nickname */
+		if (serv->end_of_motd)
+			goto def;
+		inbound_next_nick (sess,  word[4]);
+		break;
+
+	case 437:
+		if (serv->end_of_motd || is_channel (serv, word[4]))
+			goto def;
+		inbound_next_nick (sess, word[4]);
+		break;
+
+	case 471:
+		EMIT_SIGNAL (XP_TE_USERLIMIT, sess, word[4], NULL, NULL, NULL, 0);
+		break;
+
+	case 473:
+		EMIT_SIGNAL (XP_TE_INVITE, sess, word[4], NULL, NULL, NULL, 0);
+		break;
+
+	case 474:
+		EMIT_SIGNAL (XP_TE_BANNED, sess, word[4], NULL, NULL, NULL, 0);
+		break;
+
+	case 475:
+		EMIT_SIGNAL (XP_TE_KEYWORD, sess, word[4], NULL, NULL, NULL, 0);
+		break;
+
+	case 601:
+		notify_set_offline (serv, word[4], FALSE);
+		break;
+
+	case 605:
+		notify_set_offline (serv, word[4], TRUE);
+		break;
+
+	case 600:
+	case 604:
+		notify_set_online (serv, word[4]);
+		break;
+
+	default:
+
+		if (serv->inside_whois && word[4][0])
+		{
+			/* some unknown WHOIS reply, ircd coders make them up weekly */
+			if (!serv->skip_next_whois)
+				EMIT_SIGNAL (XP_TE_WHOIS_SPECIAL, whois_sess, word[4],
+								(word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5],
+								 word[2], NULL, 0);
+			return;
+		}
+
+	def:
+		if (is_channel (serv, word[4]))
+		{
+			session *realsess = find_channel (serv, word[4]);
+			if (!realsess)
+				realsess = serv->server_session;
+			EMIT_SIGNAL (XP_TE_SERVTEXT, realsess, text, word[1], word[2], NULL, 0);
+		} else
+		{
+			EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1],
+							 word[2], NULL, 0);
+		}
+	}
+}
+
+/* handle named messages that starts with a ':' */
+
+static void
+process_named_msg (session *sess, char *type, char *word[], char *word_eol[])
+{
+	server *serv = sess->server;
+	char ip[128], nick[NICKLEN];
+	char *text, *ex;
+	int len = strlen (type);
+
+	/* fill in the "ip" and "nick" buffers */
+	ex = strchr (word[1], '!');
+	if (!ex)							  /* no '!', must be a server message */
+	{
+		safe_strcpy (ip, word[1], sizeof (ip));
+		safe_strcpy (nick, word[1], sizeof (nick));
+	} else
+	{
+		safe_strcpy (ip, ex + 1, sizeof (ip));
+		ex[0] = 0;
+		safe_strcpy (nick, word[1], sizeof (nick));
+		ex[0] = '!';
+	}
+
+	if (len == 4)
+	{
+		guint32 t;
+
+		t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]); 	
+		/* this should compile to a bunch of: CMP.L, JE ... nice & fast */
+		switch (t)
+		{
+		case WORDL('J','O','I','N'):
+			{
+				char *chan = word[3];
+
+				if (*chan == ':')
+					chan++;
+				if (!serv->p_cmp (nick, serv->nick))
+					inbound_ujoin (serv, chan, nick, ip);
+				else
+					inbound_join (serv, chan, nick, ip);
+			}
+			return;
+
+		case WORDL('K','I','C','K'):
+			{
+				char *kicked = word[4];
+				char *reason = word_eol[5];
+				if (*kicked)
+				{
+					if (*reason == ':')
+						reason++;
+					if (!strcmp (kicked, serv->nick))
+	 					inbound_ukick (serv, word[3], nick, reason);
+					else
+						inbound_kick (serv, word[3], kicked, nick, reason);
+				}
+			}
+			return;
+
+		case WORDL('K','I','L','L'):
+			EMIT_SIGNAL (XP_TE_KILL, sess, nick, word_eol[5], NULL, NULL, 0);
+			return;
+
+		case WORDL('M','O','D','E'):
+			handle_mode (serv, word, word_eol, nick, FALSE);	/* modes.c */
+			return;
+
+		case WORDL('N','I','C','K'):
+			inbound_newnick (serv, nick, (word_eol[3][0] == ':')
+									? word_eol[3] + 1 : word_eol[3], FALSE);
+			return;
+
+		case WORDL('P','A','R','T'):
+			{
+				char *chan = word[3];
+				char *reason = word_eol[4];
+
+				if (*chan == ':')
+					chan++;
+				if (*reason == ':')
+					reason++;
+				if (!strcmp (nick, serv->nick))
+					inbound_upart (serv, chan, ip, reason);
+				else
+					inbound_part (serv, chan, nick, ip, reason);
+			}
+			return;
+
+		case WORDL('P','O','N','G'):
+			inbound_ping_reply (serv->server_session,
+								 (word[4][0] == ':') ? word[4] + 1 : word[4], word[3]);
+			return;
+
+		case WORDL('Q','U','I','T'):
+			inbound_quit (serv, nick, ip,
+							  (word_eol[3][0] == ':') ? word_eol[3] + 1 : word_eol[3]);
+			return;
+		}
+
+		goto garbage;
+	}
+
+	else if (len >= 5)
+	{
+		guint32 t;
+
+		t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]); 	
+		/* this should compile to a bunch of: CMP.L, JE ... nice & fast */
+		switch (t)
+		{
+		case WORDL('I','N','V','I'):
+			if (ignore_check (word[1], IG_INVI))
+				return;
+			
+			if (word[4][0] == ':')
+				EMIT_SIGNAL (XP_TE_INVITED, sess, word[4] + 1, nick,
+								 serv->servername, NULL, 0);
+			else
+				EMIT_SIGNAL (XP_TE_INVITED, sess, word[4], nick,
+								 serv->servername, NULL, 0);
+				
+			return;
+
+		case WORDL('N','O','T','I'):
+			{
+				int id = FALSE;	/* identified */
+
+				text = word_eol[4];
+				if (*text == ':')
+					text++;
+
+				if (serv->have_idmsg)
+				{
+					if (*text == '+')
+					{
+						id = TRUE;
+						text++;
+					} else if (*text == '-')
+						text++;
+				}
+
+				if (!ignore_check (word[1], IG_NOTI))
+					inbound_notice (serv, word[3], nick, text, ip, id);
+			}
+			return;
+
+		case WORDL('P','R','I','V'):
+			{
+				char *to = word[3];
+				int len;
+				int id = FALSE;	/* identified */
+				if (*to)
+				{
+					text = word_eol[4];
+					if (*text == ':')
+						text++;
+					if (serv->have_idmsg)
+					{
+						if (*text == '+')
+						{
+							id = TRUE;
+							text++;
+						} else if (*text == '-')
+							text++;
+					}
+					len = strlen (text);
+					if (text[0] == 1 && text[len - 1] == 1)	/* ctcp */
+					{
+						text[len - 1] = 0;
+						text++;
+						if (strncasecmp (text, "ACTION", 6) != 0)
+							flood_check (nick, ip, serv, sess, 0);
+						if (strncasecmp (text, "DCC ", 4) == 0)
+							/* redo this with handle_quotes TRUE */
+							process_data_init (word[1], word_eol[1], word, word_eol, TRUE, FALSE);
+						ctcp_handle (sess, to, nick, ip, text, word, word_eol, id);
+					} else
+					{
+						if (is_channel (serv, to))
+						{
+							if (ignore_check (word[1], IG_CHAN))
+								return;
+							inbound_chanmsg (serv, NULL, to, nick, text, FALSE, id);
+						} else
+						{
+							if (ignore_check (word[1], IG_PRIV))
+								return;
+							inbound_privmsg (serv, nick, ip, text, id);
+						}
+					}
+				}
+			}
+			return;
+
+		case WORDL('T','O','P','I'):
+			inbound_topicnew (serv, nick, word[3],
+									(word_eol[4][0] == ':') ? word_eol[4] + 1 : word_eol[4]);
+			return;
+
+		case WORDL('W','A','L','L'):
+			text = word_eol[3];
+			if (*text == ':')
+				text++;
+			EMIT_SIGNAL (XP_TE_WALLOPS, sess, nick, text, NULL, NULL, 0);
+			return;
+		}
+	}
+
+garbage:
+	/* unknown message */
+	PrintTextf (sess, "GARBAGE: %s\n", word_eol[1]);
+}
+
+/* handle named messages that DON'T start with a ':' */
+
+static void
+process_named_servermsg (session *sess, char *buf, char *rawname, char *word_eol[])
+{
+	sess = sess->server->server_session;
+
+	if (!strncmp (buf, "PING ", 5))
+	{
+		tcp_sendf (sess->server, "PONG %s\r\n", buf + 5);
+		return;
+	}
+	if (!strncmp (buf, "ERROR", 5))
+	{
+		EMIT_SIGNAL (XP_TE_SERVERERROR, sess, buf + 7, NULL, NULL, NULL, 0);
+		return;
+	}
+	if (!strncmp (buf, "NOTICE ", 7))
+	{
+		buf = word_eol[3];
+		if (*buf == ':')
+			buf++;
+		EMIT_SIGNAL (XP_TE_SERVNOTICE, sess, buf, sess->server->servername, NULL, NULL, 0);
+		return;
+	}
+
+	EMIT_SIGNAL (XP_TE_SERVTEXT, sess, buf, sess->server->servername, rawname, NULL, 0);
+}
+
+/* irc_inline() - 1 single line received from serv */
+
+static void
+irc_inline (server *serv, char *buf, int len)
+{
+	session *sess, *tmp;
+	char *type, *text;
+	char *word[PDIWORDS+1];
+	char *word_eol[PDIWORDS+1];
+	char pdibuf_static[522]; /* 1 line can potentially be 512*6 in utf8 */
+	char *pdibuf = pdibuf_static;
+
+	/* need more than 522? fall back to malloc */
+	if (len >= sizeof (pdibuf_static))
+		pdibuf = malloc (len + 1);
+
+	sess = serv->front_session;
+
+	/* Python relies on this */
+	word[PDIWORDS] = NULL;
+	word_eol[PDIWORDS] = NULL;
+
+	if (buf[0] == ':')
+	{
+		/* split line into words and words_to_end_of_line */
+		process_data_init (pdibuf, buf, word, word_eol, FALSE, FALSE);
+
+		/* find a context for this message */
+		if (is_channel (serv, word[3]))
+		{
+			tmp = find_channel (serv, word[3]);
+			if (tmp)
+				sess = tmp;
+		}
+
+		/* for server messages, the 2nd word is the "message type" */
+		type = word[2];
+
+		word[0] = type;
+		word_eol[1] = buf;	/* keep the ":" for plugins */
+		if (plugin_emit_server (sess, type, word, word_eol))
+			goto xit;
+		word[1]++;
+		word_eol[1] = buf + 1;	/* but not for xchat internally */
+
+	} else
+	{
+		process_data_init (pdibuf, buf, word, word_eol, FALSE, FALSE);
+		word[0] = type = word[1];
+		if (plugin_emit_server (sess, type, word, word_eol))
+			goto xit;
+	}
+
+	if (buf[0] != ':')
+	{
+		process_named_servermsg (sess, buf, word[0], word_eol);
+		goto xit;
+	}
+
+	/* see if the second word is a numeric */
+	if (isdigit ((unsigned char) word[2][0]))
+	{
+		text = word_eol[4];
+		if (*text == ':')
+			text++;
+
+		process_numeric (sess, atoi (word[2]), word, word_eol, text);
+	} else
+	{
+		process_named_msg (sess, type, word, word_eol);
+	}
+
+xit:
+	if (pdibuf != pdibuf_static)
+		free (pdibuf);
+}
+
+void
+proto_fill_her_up (server *serv)
+{
+	serv->p_inline = irc_inline;
+	serv->p_invite = irc_invite;
+	serv->p_cycle = irc_cycle;
+	serv->p_ctcp = irc_ctcp;
+	serv->p_nctcp = irc_nctcp;
+	serv->p_quit = irc_quit;
+	serv->p_kick = irc_kick;
+	serv->p_part = irc_part;
+	serv->p_ns_identify = irc_ns_identify;
+	serv->p_ns_ghost = irc_ns_ghost;
+	serv->p_join = irc_join;
+	serv->p_join_list = irc_join_list;
+	serv->p_login = irc_login;
+	serv->p_join_info = irc_join_info;
+	serv->p_mode = irc_mode;
+	serv->p_user_list = irc_user_list;
+	serv->p_away_status = irc_away_status;
+	/*serv->p_get_ip = irc_get_ip;*/
+	serv->p_whois = irc_user_whois;
+	serv->p_get_ip = irc_user_list;
+	serv->p_get_ip_uh = irc_userhost;
+	serv->p_set_back = irc_set_back;
+	serv->p_set_away = irc_set_away;
+	serv->p_message = irc_message;
+	serv->p_action = irc_action;
+	serv->p_notice = irc_notice;
+	serv->p_topic = irc_topic;
+	serv->p_list_channels = irc_list_channels;
+	serv->p_change_nick = irc_change_nick;
+	serv->p_names = irc_names;
+	serv->p_ping = irc_ping;
+	serv->p_raw = irc_raw;
+	serv->p_cmp = rfc_casecmp;	/* can be changed by 005 in modes.c */
+}
diff --git a/src/common/proto-irc.h b/src/common/proto-irc.h
new file mode 100644
index 00000000..90803594
--- /dev/null
+++ b/src/common/proto-irc.h
@@ -0,0 +1,6 @@
+#ifndef XCHAT_PROTO_H
+#define XCHAT_PROTO_H
+
+void proto_fill_her_up (server *serv);
+
+#endif
diff --git a/src/common/server.c b/src/common/server.c
new file mode 100644
index 00000000..dd43adff
--- /dev/null
+++ b/src/common/server.c
@@ -0,0 +1,2047 @@
+/* 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
+ *
+ * MS Proxy (ISA server) support is (c) 2006 Pavel Fedin <sonic_amiga@rambler.ru>
+ * based on Dante source code
+ * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006
+ *      Inferno Nettverk A/S, Norway.  All rights reserved.
+ */
+
+/*#define DEBUG_MSPROXY*/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#define WANTSOCKET
+#define WANTARPA
+#include "inet.h"
+
+#ifndef WIN32
+#include <signal.h>
+#include <sys/wait.h>
+#else
+#include <winbase.h>
+#endif
+
+#include "xchat.h"
+#include "fe.h"
+#include "cfgfiles.h"
+#include "network.h"
+#include "notify.h"
+#include "xchatc.h"
+#include "inbound.h"
+#include "outbound.h"
+#include "text.h"
+#include "util.h"
+#include "url.h"
+#include "proto-irc.h"
+#include "servlist.h"
+#include "server.h"
+
+#ifdef USE_OPENSSL
+#include <openssl/ssl.h>		  /* SSL_() */
+#include <openssl/err.h>		  /* ERR_() */
+#include "ssl.h"
+#endif
+
+#ifdef USE_MSPROXY
+#include "msproxy.h"
+#endif
+
+#ifdef WIN32
+#include "identd.c"
+#endif
+
+#ifdef USE_LIBPROXY
+#include <proxy.h>
+#endif
+
+#ifdef USE_OPENSSL
+extern SSL_CTX *ctx;				  /* xchat.c */
+/* local variables */
+static struct session *g_sess = NULL;
+#endif
+
+static GSList *away_list = NULL;
+GSList *serv_list = NULL;
+
+static void auto_reconnect (server *serv, int send_quit, int err);
+static void server_disconnect (session * sess, int sendquit, int err);
+static int server_cleanup (server * serv);
+static void server_connect (server *serv, char *hostname, int port, int no_login);
+
+#ifdef USE_LIBPROXY
+extern pxProxyFactory *libproxy_factory;
+#endif
+
+/* actually send to the socket. This might do a character translation or
+   send via SSL. server/dcc both use this function. */
+
+int
+tcp_send_real (void *ssl, int sok, char *encoding, int using_irc, char *buf, int len)
+{
+	int ret;
+	char *locale;
+	gsize loc_len;
+
+	if (encoding == NULL)	/* system */
+	{
+		locale = NULL;
+		if (!prefs.utf8_locale)
+		{
+			const gchar *charset;
+
+			g_get_charset (&charset);
+			locale = g_convert_with_fallback (buf, len, charset, "UTF-8",
+														 "?", 0, &loc_len, 0);
+		}
+	} else
+	{
+		if (using_irc)	/* using "IRC" encoding (CP1252/UTF-8 hybrid) */
+			/* if all chars fit inside CP1252, use that. Otherwise this
+			   returns NULL and we send UTF-8. */
+			locale = g_convert (buf, len, "CP1252", "UTF-8", 0, &loc_len, 0);
+		else
+			locale = g_convert_with_fallback (buf, len, encoding, "UTF-8",
+														 "?", 0, &loc_len, 0);
+	}
+
+	if (locale)
+	{
+		len = loc_len;
+#ifdef USE_OPENSSL
+		if (!ssl)
+			ret = send (sok, locale, len, 0);
+		else
+			ret = _SSL_send (ssl, locale, len);
+#else
+		ret = send (sok, locale, len, 0);
+#endif
+		g_free (locale);
+	} else
+	{
+#ifdef USE_OPENSSL
+		if (!ssl)
+			ret = send (sok, buf, len, 0);
+		else
+			ret = _SSL_send (ssl, buf, len);
+#else
+		ret = send (sok, buf, len, 0);
+#endif
+	}
+
+	return ret;
+}
+
+static int
+server_send_real (server *serv, char *buf, int len)
+{
+	fe_add_rawlog (serv, buf, len, TRUE);
+
+	return tcp_send_real (serv->ssl, serv->sok, serv->encoding, serv->using_irc,
+								 buf, len);
+}
+
+/* new throttling system, uses the same method as the Undernet
+   ircu2.10 server; under test, a 200-line paste didn't flood
+   off the client */
+
+static int
+tcp_send_queue (server *serv)
+{
+	char *buf, *p;
+	int len, i, pri;
+	GSList *list;
+	time_t now = time (0);
+
+	/* did the server close since the timeout was added? */
+	if (!is_server (serv))
+		return 0;
+
+	/* try priority 2,1,0 */
+	pri = 2;
+	while (pri >= 0)
+	{
+		list = serv->outbound_queue;
+		while (list)
+		{
+			buf = (char *) list->data;
+			if (buf[0] == pri)
+			{
+				buf++;	/* skip the priority byte */
+				len = strlen (buf);
+
+				if (serv->next_send < now)
+					serv->next_send = now;
+				if (serv->next_send - now >= 10)
+				{
+					/* check for clock skew */
+					if (now >= serv->prev_now)
+						return 1;		  /* don't remove the timeout handler */
+					/* it is skewed, reset to something sane */
+					serv->next_send = now;
+				}
+
+				for (p = buf, i = len; i && *p != ' '; p++, i--);
+				serv->next_send += (2 + i / 120);
+				serv->sendq_len -= len;
+				serv->prev_now = now;
+				fe_set_throttle (serv);
+
+				server_send_real (serv, buf, len);
+
+				buf--;
+				serv->outbound_queue = g_slist_remove (serv->outbound_queue, buf);
+				free (buf);
+				list = serv->outbound_queue;
+			} else
+			{
+				list = list->next;
+			}
+		}
+		/* now try pri 0 */
+		pri--;
+	}
+	return 0;						  /* remove the timeout handler */
+}
+
+int
+tcp_send_len (server *serv, char *buf, int len)
+{
+	char *dbuf;
+	int noqueue = !serv->outbound_queue;
+
+	if (!prefs.throttle)
+		return server_send_real (serv, buf, len);
+
+	dbuf = malloc (len + 2);	/* first byte is the priority */
+	dbuf[0] = 2;	/* pri 2 for most things */
+	memcpy (dbuf + 1, buf, len);
+	dbuf[len + 1] = 0;
+
+	/* privmsg and notice get a lower priority */
+	if (strncasecmp (dbuf + 1, "PRIVMSG", 7) == 0 ||
+		 strncasecmp (dbuf + 1, "NOTICE", 6) == 0)
+	{
+		dbuf[0] = 1;
+	}
+	else
+	{
+		/* WHO/MODE get the lowest priority */
+		if (strncasecmp (dbuf + 1, "WHO ", 4) == 0 ||
+		/* but only MODE queries, not changes */
+			(strncasecmp (dbuf + 1, "MODE", 4) == 0 &&
+			 strchr (dbuf, '-') == NULL &&
+			 strchr (dbuf, '+') == NULL))
+			dbuf[0] = 0;
+	}
+
+	serv->outbound_queue = g_slist_append (serv->outbound_queue, dbuf);
+	serv->sendq_len += len; /* tcp_send_queue uses strlen */
+
+	if (tcp_send_queue (serv) && noqueue)
+		fe_timeout_add (500, tcp_send_queue, serv);
+
+	return 1;
+}
+
+/*int
+tcp_send (server *serv, char *buf)
+{
+	return tcp_send_len (serv, buf, strlen (buf));
+}*/
+
+void
+tcp_sendf (server *serv, char *fmt, ...)
+{
+	va_list args;
+	/* keep this buffer in BSS. Converting UTF-8 to ISO-8859-x might make the
+      string shorter, so allow alot more than 512 for now. */
+	static char send_buf[1540];	/* good code hey (no it's not overflowable) */
+	int len;
+
+	va_start (args, fmt);
+	len = vsnprintf (send_buf, sizeof (send_buf) - 1, fmt, args);
+	va_end (args);
+
+	send_buf[sizeof (send_buf) - 1] = '\0';
+	if (len < 0 || len > (sizeof (send_buf) - 1))
+		len = strlen (send_buf);
+
+	tcp_send_len (serv, send_buf, len);
+}
+
+static int
+close_socket_cb (gpointer sok)
+{
+	closesocket (GPOINTER_TO_INT (sok));
+	return 0;
+}
+
+static void
+close_socket (int sok)
+{
+	/* close the socket in 5 seconds so the QUIT message is not lost */
+	fe_timeout_add (5000, close_socket_cb, GINT_TO_POINTER (sok));
+}
+
+/* handle 1 line of text received from the server */
+
+static void
+server_inline (server *serv, char *line, int len)
+{
+	char *utf_line_allocated = NULL;
+
+	/* Checks whether we're set to use UTF-8 charset */
+	if (serv->using_irc ||				/* 1. using CP1252/UTF-8 Hybrid */
+		(serv->encoding == NULL && prefs.utf8_locale) || /* OR 2. using system default->UTF-8 */
+	    (serv->encoding != NULL &&				/* OR 3. explicitly set to UTF-8 */
+		 (strcasecmp (serv->encoding, "UTF8") == 0 ||
+		  strcasecmp (serv->encoding, "UTF-8") == 0)))
+	{
+		/* The user has the UTF-8 charset set, either via /charset
+		command or from his UTF-8 locale. Thus, we first try the
+		UTF-8 charset, and if we fail to convert, we assume
+		it to be ISO-8859-1 (see text_validate). */
+
+		utf_line_allocated = text_validate (&line, &len);
+
+	} else
+	{
+		/* Since the user has an explicit charset set, either
+		via /charset command or from his non-UTF8 locale,
+		we don't fallback to ISO-8859-1 and instead try to remove
+		errnoeous octets till the string is convertable in the
+		said charset. */
+
+		const char *encoding = NULL;
+
+		if (serv->encoding != NULL)
+			encoding = serv->encoding;
+		else
+			g_get_charset (&encoding);
+
+		if (encoding != NULL)
+		{
+			char *conv_line; /* holds a copy of the original string */
+			int conv_len; /* tells g_convert how much of line to convert */
+			gsize utf_len;
+			gsize read_len;
+			GError *err;
+			gboolean retry;
+
+			conv_line = g_malloc (len + 1);
+			memcpy (conv_line, line, len);
+			conv_line[len] = 0;
+			conv_len = len;
+
+			/* if CP1255, convert it with the NUL terminator.
+				Works around SF bug #1122089 */
+			if (serv->using_cp1255)
+				conv_len++;
+
+			do
+			{
+				err = NULL;
+				retry = FALSE;
+				utf_line_allocated = g_convert_with_fallback (conv_line, conv_len, "UTF-8", encoding, "?", &read_len, &utf_len, &err);
+				if (err != NULL)
+				{
+					if (err->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE && conv_len > (read_len + 1))
+					{
+						/* Make our best bet by removing the erroneous char.
+						   This will work for casual 8-bit strings with non-standard chars. */
+						memmove (conv_line + read_len, conv_line + read_len + 1, conv_len - read_len -1);
+						conv_len--;
+						retry = TRUE;
+					}
+					g_error_free (err);
+				}
+			} while (retry);
+
+			g_free (conv_line);
+
+			/* If any conversion has occured at all. Conversion might fail
+			due to errors other than invalid sequences, e.g. unknown charset. */
+			if (utf_line_allocated != NULL)
+			{
+				line = utf_line_allocated;
+				len = utf_len;
+				if (serv->using_cp1255 && len > 0)
+					len--;
+			}
+			else
+			{
+				/* If all fails, treat as UTF-8 with fallback to ISO-8859-1. */
+				utf_line_allocated = text_validate (&line, &len);
+			}
+		}
+	}
+
+	fe_add_rawlog (serv, line, len, FALSE);
+	url_check_line (line, len);
+
+	/* let proto-irc.c handle it */
+	serv->p_inline (serv, line, len);
+
+	if (utf_line_allocated != NULL) /* only if a special copy was allocated */
+		g_free (utf_line_allocated);
+}
+
+/* read data from socket */
+
+static gboolean
+server_read (GIOChannel *source, GIOCondition condition, server *serv)
+{
+	int sok = serv->sok;
+	int error, i, len;
+	char lbuf[2050];
+
+	while (1)
+	{
+#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)
+		{
+			error = 0;
+			if (len < 0)
+			{
+				if (would_block ())
+					return TRUE;
+				error = sock_error ();
+			}
+			if (!serv->end_of_motd)
+			{
+				server_disconnect (serv->server_session, FALSE, error);
+				if (!servlist_cycle (serv))
+				{
+					if (prefs.autoreconnect)
+						auto_reconnect (serv, FALSE, error);
+				}
+			} else
+			{
+				if (prefs.autoreconnect)
+					auto_reconnect (serv, FALSE, error);
+				else
+					server_disconnect (serv->server_session, FALSE, error);
+			}
+			return TRUE;
+		}
+
+		i = 0;
+
+		lbuf[len] = 0;
+
+		while (i < len)
+		{
+			switch (lbuf[i])
+			{
+			case '\r':
+				break;
+
+			case '\n':
+				serv->linebuf[serv->pos] = 0;
+				server_inline (serv, serv->linebuf, serv->pos);
+				serv->pos = 0;
+				break;
+
+			default:
+				serv->linebuf[serv->pos] = lbuf[i];
+				if (serv->pos >= (sizeof (serv->linebuf) - 1))
+					fprintf (stderr,
+								"*** XCHAT WARNING: Buffer overflow - shit server!\n");
+				else
+					serv->pos++;
+			}
+			i++;
+		}
+	}
+}
+
+static void
+server_connected (server * serv)
+{
+	prefs.wait_on_exit = TRUE;
+	serv->ping_recv = time (0);
+	serv->connected = TRUE;
+	set_nonblocking (serv->sok);
+	serv->iotag = fe_input_add (serv->sok, FIA_READ|FIA_EX, server_read, serv);
+	if (!serv->no_login)
+	{
+		EMIT_SIGNAL (XP_TE_CONNECTED, serv->server_session, NULL, NULL, NULL,
+						 NULL, 0);
+		if (serv->network)
+		{
+			serv->p_login (serv,
+								(!(((ircnet *)serv->network)->flags & FLAG_USE_GLOBAL) &&
+								 (((ircnet *)serv->network)->user)) ?
+								(((ircnet *)serv->network)->user) :
+								prefs.username,
+								(!(((ircnet *)serv->network)->flags & FLAG_USE_GLOBAL) &&
+								 (((ircnet *)serv->network)->real)) ?
+								(((ircnet *)serv->network)->real) :
+								prefs.realname);
+		} else
+		{
+			serv->p_login (serv, prefs.username, prefs.realname);
+		}
+	} else
+	{
+		EMIT_SIGNAL (XP_TE_SERVERCONNECTED, serv->server_session, NULL, NULL,
+						 NULL, NULL, 0);
+	}
+
+	server_set_name (serv, serv->servername);
+	fe_server_event (serv, FE_SE_CONNECT, 0);
+}
+
+#ifdef WIN32
+
+static gboolean
+server_close_pipe (int *pipefd)	/* see comments below */
+{
+	close (pipefd[0]);	/* close WRITE end first to cause an EOF on READ */
+	close (pipefd[1]);	/* in giowin32, and end that thread. */
+	free (pipefd);
+	return FALSE;
+}
+
+#endif
+
+static void
+server_stopconnecting (server * serv)
+{
+	if (serv->iotag)
+	{
+		fe_input_remove (serv->iotag);
+		serv->iotag = 0;
+	}
+
+	if (serv->joindelay_tag)
+	{
+		fe_timeout_remove (serv->joindelay_tag);
+		serv->joindelay_tag = 0;
+	}
+
+#ifndef WIN32
+	/* kill the child process trying to connect */
+	kill (serv->childpid, SIGKILL);
+	waitpid (serv->childpid, NULL, 0);
+
+	close (serv->childwrite);
+	close (serv->childread);
+#else
+	PostThreadMessage (serv->childpid, WM_QUIT, 0, 0);
+
+	{
+		/* if we close the pipe now, giowin32 will crash. */
+		int *pipefd = malloc (sizeof (int) * 2);
+		pipefd[0] = serv->childwrite;
+		pipefd[1] = serv->childread;
+		g_idle_add ((GSourceFunc)server_close_pipe, pipefd);
+	}
+#endif
+
+#ifdef USE_OPENSSL
+	if (serv->ssl_do_connect_tag)
+	{
+		fe_timeout_remove (serv->ssl_do_connect_tag);
+		serv->ssl_do_connect_tag = 0;
+	}
+#endif
+
+	fe_progressbar_end (serv);
+
+	serv->connecting = FALSE;
+	fe_server_event (serv, FE_SE_DISCONNECT, 0);
+}
+
+#ifdef USE_OPENSSL
+#define	SSLTMOUT	90				  /* seconds */
+static void
+ssl_cb_info (SSL * s, int where, int ret)
+{
+/*	char buf[128];*/
+
+
+	return;							  /* FIXME: make debug level adjustable in serverlist or settings */
+
+/*	snprintf (buf, sizeof (buf), "%s (%d)", SSL_state_string_long (s), where);
+	if (g_sess)
+		EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0);
+	else
+		fprintf (stderr, "%s\n", buf);*/
+}
+
+static int
+ssl_cb_verify (int ok, X509_STORE_CTX * ctx)
+{
+	char subject[256];
+	char issuer[256];
+	char buf[512];
+
+
+	X509_NAME_oneline (X509_get_subject_name (ctx->current_cert), subject,
+							 sizeof (subject));
+	X509_NAME_oneline (X509_get_issuer_name (ctx->current_cert), issuer,
+							 sizeof (issuer));
+
+	snprintf (buf, sizeof (buf), "* Subject: %s", subject);
+	EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0);
+	snprintf (buf, sizeof (buf), "* Issuer: %s", issuer);
+	EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0);
+
+	return (TRUE);					  /* always ok */
+}
+
+static int
+ssl_do_connect (server * serv)
+{
+	char buf[128];
+
+	g_sess = serv->server_session;
+	if (SSL_connect (serv->ssl) <= 0)
+	{
+		char err_buf[128];
+		int err;
+
+		g_sess = NULL;
+		if ((err = ERR_get_error ()) > 0)
+		{
+			ERR_error_string (err, err_buf);
+			snprintf (buf, sizeof (buf), "(%d) %s", err, err_buf);
+			EMIT_SIGNAL (XP_TE_CONNFAIL, serv->server_session, buf, NULL,
+							 NULL, NULL, 0);
+
+			if (ERR_GET_REASON (err) == SSL_R_WRONG_VERSION_NUMBER)
+				PrintText (serv->server_session, _("Are you sure this is a SSL capable server and port?\n"));
+
+			server_cleanup (serv);
+
+			if (prefs.autoreconnectonfail)
+				auto_reconnect (serv, FALSE, -1);
+
+			return (0);				  /* remove it (0) */
+		}
+	}
+	g_sess = NULL;
+
+	if (SSL_is_init_finished (serv->ssl))
+	{
+		struct cert_info cert_info;
+		struct chiper_info *chiper_info;
+		int verify_error;
+		int i;
+
+		if (!_SSL_get_cert_info (&cert_info, serv->ssl))
+		{
+			snprintf (buf, sizeof (buf), "* Certification info:");
+			EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
+							 NULL, 0);
+			snprintf (buf, sizeof (buf), "  Subject:");
+			EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
+							 NULL, 0);
+			for (i = 0; cert_info.subject_word[i]; i++)
+			{
+				snprintf (buf, sizeof (buf), "    %s", cert_info.subject_word[i]);
+				EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
+								 NULL, 0);
+			}
+			snprintf (buf, sizeof (buf), "  Issuer:");
+			EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
+							 NULL, 0);
+			for (i = 0; cert_info.issuer_word[i]; i++)
+			{
+				snprintf (buf, sizeof (buf), "    %s", cert_info.issuer_word[i]);
+				EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
+								 NULL, 0);
+			}
+			snprintf (buf, sizeof (buf), "  Public key algorithm: %s (%d bits)",
+						 cert_info.algorithm, cert_info.algorithm_bits);
+			EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
+							 NULL, 0);
+			/*if (cert_info.rsa_tmp_bits)
+			{
+				snprintf (buf, sizeof (buf),
+							 "  Public key algorithm uses ephemeral key with %d bits",
+							 cert_info.rsa_tmp_bits);
+				EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
+								 NULL, 0);
+			}*/
+			snprintf (buf, sizeof (buf), "  Sign algorithm %s",
+						 cert_info.sign_algorithm/*, cert_info.sign_algorithm_bits*/);
+			EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
+							 NULL, 0);
+			snprintf (buf, sizeof (buf), "  Valid since %s to %s",
+						 cert_info.notbefore, cert_info.notafter);
+			EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
+							 NULL, 0);
+		} else
+		{
+			snprintf (buf, sizeof (buf), " * No Certificate");
+			EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
+							 NULL, 0);
+		}
+
+		chiper_info = _SSL_get_cipher_info (serv->ssl);	/* static buffer */
+		snprintf (buf, sizeof (buf), "* Cipher info:");
+		EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL,
+						 0);
+		snprintf (buf, sizeof (buf), "  Version: %s, cipher %s (%u bits)",
+					 chiper_info->version, chiper_info->chiper,
+					 chiper_info->chiper_bits);
+		EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL,
+						 0);
+
+		verify_error = SSL_get_verify_result (serv->ssl);
+		switch (verify_error)
+		{
+		case X509_V_OK:
+			/* snprintf (buf, sizeof (buf), "* Verify OK (?)"); */
+			/* EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL, 0); */
+			break;
+		case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
+		case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
+		case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
+		case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
+		case X509_V_ERR_CERT_HAS_EXPIRED:
+			if (serv->accept_invalid_cert)
+			{
+				snprintf (buf, sizeof (buf), "* Verify E: %s.? (%d) -- Ignored",
+							 X509_verify_cert_error_string (verify_error),
+							 verify_error);
+				EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
+								 NULL, 0);
+				break;
+			}
+		default:
+			snprintf (buf, sizeof (buf), "%s.? (%d)",
+						 X509_verify_cert_error_string (verify_error),
+						 verify_error);
+			EMIT_SIGNAL (XP_TE_CONNFAIL, serv->server_session, buf, NULL, NULL,
+							 NULL, 0);
+
+			server_cleanup (serv);
+
+			return (0);
+		}
+
+		server_stopconnecting (serv);
+
+		/* activate gtk poll */
+		server_connected (serv);
+
+		return (0);					  /* remove it (0) */
+	} else
+	{
+		if (serv->ssl->session && serv->ssl->session->time + SSLTMOUT < time (NULL))
+		{
+			snprintf (buf, sizeof (buf), "SSL handshake timed out");
+			EMIT_SIGNAL (XP_TE_CONNFAIL, serv->server_session, buf, NULL,
+							 NULL, NULL, 0);
+			server_cleanup (serv); /* ->connecting = FALSE */
+
+			if (prefs.autoreconnectonfail)
+				auto_reconnect (serv, FALSE, -1);
+
+			return (0);				  /* remove it (0) */
+		}
+
+		return (1);					  /* call it more (1) */
+	}
+}
+#endif
+
+static int
+timeout_auto_reconnect (server *serv)
+{
+	if (is_server (serv))  /* make sure it hasnt been closed during the delay */
+	{
+		serv->recondelay_tag = 0;
+		if (!serv->connected && !serv->connecting && serv->server_session)
+		{
+			server_connect (serv, serv->hostname, serv->port, FALSE);
+		}
+	}
+	return 0;			  /* returning 0 should remove the timeout handler */
+}
+
+static void
+auto_reconnect (server *serv, int send_quit, int err)
+{
+	session *s;
+	GSList *list;
+	int del;
+
+	if (serv->server_session == NULL)
+		return;
+
+	list = sess_list;
+	while (list)				  /* make sure auto rejoin can work */
+	{
+		s = list->data;
+		if (s->type == SESS_CHANNEL && s->channel[0])
+		{
+			strcpy (s->waitchannel, s->channel);
+			strcpy (s->willjoinchannel, s->channel);
+		}
+		list = list->next;
+	}
+
+	if (serv->connected)
+		server_disconnect (serv->server_session, send_quit, err);
+
+	del = prefs.recon_delay * 1000;
+	if (del < 1000)
+		del = 500;				  /* so it doesn't block the gui */
+
+#ifndef WIN32
+	if (err == -1 || err == 0 || err == ECONNRESET || err == ETIMEDOUT)
+#else
+	if (err == -1 || err == 0 || err == WSAECONNRESET || err == WSAETIMEDOUT)
+#endif
+		serv->reconnect_away = serv->is_away;
+
+	/* is this server in a reconnect delay? remove it! */
+	if (serv->recondelay_tag)
+	{
+		fe_timeout_remove (serv->recondelay_tag);
+		serv->recondelay_tag = 0;
+	}
+
+	serv->recondelay_tag = fe_timeout_add (del, timeout_auto_reconnect, serv);
+	fe_server_event (serv, FE_SE_RECONDELAY, del);
+}
+
+static void
+server_flush_queue (server *serv)
+{
+	list_free (&serv->outbound_queue);
+	serv->sendq_len = 0;
+	fe_set_throttle (serv);
+}
+
+#ifdef WIN32
+
+static int
+waitline2 (GIOChannel *source, char *buf, int bufsize)
+{
+	int i = 0;
+	int len;
+
+	while (1)
+	{
+		if (g_io_channel_read (source, &buf[i], 1, &len) != G_IO_ERROR_NONE)
+			return -1;
+		if (buf[i] == '\n' || bufsize == i + 1)
+		{
+			buf[i] = 0;
+			return i;
+		}
+		i++;
+	}
+}
+
+#else
+
+#define waitline2(source,buf,size) waitline(serv->childread,buf,size,0)
+
+#endif
+
+/* connect() successed */
+
+static void
+server_connect_success (server *serv)
+{
+#ifdef USE_OPENSSL
+#define	SSLDOCONNTMOUT	300
+	if (serv->use_ssl)
+	{
+		char *err;
+
+		/* it'll be a memory leak, if connection isn't terminated by
+		   server_cleanup() */
+		serv->ssl = _SSL_socket (ctx, serv->sok);
+		if ((err = _SSL_set_verify (ctx, ssl_cb_verify, NULL)))
+		{
+			EMIT_SIGNAL (XP_TE_CONNFAIL, serv->server_session, err, NULL,
+							 NULL, NULL, 0);
+			server_cleanup (serv);	/* ->connecting = FALSE */
+			return;
+		}
+		/* FIXME: it'll be needed by new servers */
+		/* send(serv->sok, "STLS\r\n", 6, 0); sleep(1); */
+		set_nonblocking (serv->sok);
+		serv->ssl_do_connect_tag = fe_timeout_add (SSLDOCONNTMOUT,
+																 ssl_do_connect, serv);
+		return;
+	}
+
+	serv->ssl = NULL;
+#endif
+	server_stopconnecting (serv);	/* ->connecting = FALSE */
+	/* activate glib poll */
+	server_connected (serv);
+}
+
+/* receive info from the child-process about connection progress */
+
+static gboolean
+server_read_child (GIOChannel *source, GIOCondition condition, server *serv)
+{
+	session *sess = serv->server_session;
+	char tbuf[128];
+	char outbuf[512];
+	char host[100];
+	char ip[100];
+	char *p;
+
+	waitline2 (source, tbuf, sizeof tbuf);
+
+	switch (tbuf[0])
+	{
+	case '0':	/* print some text */
+		waitline2 (source, tbuf, sizeof tbuf);
+		PrintText (serv->server_session, tbuf);
+		break;
+	case '1':						  /* unknown host */
+		server_stopconnecting (serv);
+		closesocket (serv->sok4);
+		if (serv->proxy_sok4 != -1)
+			closesocket (serv->proxy_sok4);
+#ifdef USE_IPV6
+		if (serv->sok6 != -1)
+			closesocket (serv->sok6);
+		if (serv->proxy_sok6 != -1)
+			closesocket (serv->proxy_sok6);
+#endif
+		EMIT_SIGNAL (XP_TE_UKNHOST, sess, NULL, NULL, NULL, NULL, 0);
+		if (!servlist_cycle (serv))
+			if (prefs.autoreconnectonfail)
+				auto_reconnect (serv, FALSE, -1);
+		break;
+	case '2':						  /* connection failed */
+		waitline2 (source, tbuf, sizeof tbuf);
+		server_stopconnecting (serv);
+		closesocket (serv->sok4);
+		if (serv->proxy_sok4 != -1)
+			closesocket (serv->proxy_sok4);
+#ifdef USE_IPV6
+		if (serv->sok6 != -1)
+			closesocket (serv->sok6);
+		if (serv->proxy_sok6 != -1)
+			closesocket (serv->proxy_sok6);
+#endif
+		EMIT_SIGNAL (XP_TE_CONNFAIL, sess, errorstring (atoi (tbuf)), NULL,
+						 NULL, NULL, 0);
+		if (!servlist_cycle (serv))
+			if (prefs.autoreconnectonfail)
+				auto_reconnect (serv, FALSE, -1);
+		break;
+	case '3':						  /* gethostbyname finished */
+		waitline2 (source, host, sizeof host);
+		waitline2 (source, ip, sizeof ip);
+		waitline2 (source, outbuf, sizeof outbuf);
+		EMIT_SIGNAL (XP_TE_CONNECT, sess, host, ip, outbuf, NULL, 0);
+#ifdef WIN32
+		if (prefs.identd)
+		{
+			if (serv->network)
+				identd_start ((((ircnet *)serv->network)->user) ?
+									(((ircnet *)serv->network)->user) :
+									prefs.username);
+			else
+				identd_start (prefs.username);
+		}
+#else
+		snprintf (outbuf, sizeof (outbuf), "%s/auth/xchat_auth",
+					 g_get_home_dir ());
+		if (access (outbuf, X_OK) == 0)
+		{
+			snprintf (outbuf, sizeof (outbuf), "exec -d %s/auth/xchat_auth %s",
+						 g_get_home_dir (), prefs.username);
+			handle_command (serv->server_session, outbuf, FALSE);
+		}
+#endif
+		break;
+	case '4':						  /* success */
+		waitline2 (source, tbuf, sizeof (tbuf));
+#ifdef USE_MSPROXY
+		serv->sok = strtol (tbuf, &p, 10);
+		if (*p++ == ' ')
+		{
+			serv->proxy_sok = strtol (p, &p, 10);
+			serv->msp_state.clientid = strtol (++p, &p, 10);
+			serv->msp_state.serverid = strtol (++p, &p, 10);
+			serv->msp_state.seq_sent = atoi (++p);
+		} else
+			serv->proxy_sok = -1;
+#ifdef DEBUG_MSPROXY
+		printf ("Parent got main socket: %d, proxy socket: %d\n", serv->sok, serv->proxy_sok);
+		printf ("Client ID 0x%08x server ID 0x%08x seq_sent %d\n", serv->msp_state.clientid, serv->msp_state.serverid, serv->msp_state.seq_sent);
+#endif
+#else
+		serv->sok = atoi (tbuf);
+#endif
+#ifdef USE_IPV6
+		/* close the one we didn't end up using */
+		if (serv->sok == serv->sok4)
+			closesocket (serv->sok6);
+		else
+			closesocket (serv->sok4);
+		if (serv->proxy_sok != -1)
+		{
+			if (serv->proxy_sok == serv->proxy_sok4)
+				closesocket (serv->proxy_sok6);
+			else
+				closesocket (serv->proxy_sok4);
+		}
+#endif
+		server_connect_success (serv);
+		break;
+	case '5':						  /* prefs ip discovered */
+		waitline2 (source, tbuf, sizeof tbuf);
+		prefs.local_ip = inet_addr (tbuf);
+		break;
+	case '7':						  /* gethostbyname (prefs.hostname) failed */
+		sprintf (outbuf,
+					_("Cannot resolve hostname %s\nCheck your IP Settings!\n"),
+					prefs.hostname);
+		PrintText (sess, outbuf);
+		break;
+	case '8':
+		PrintText (sess, _("Proxy traversal failed.\n"));
+		server_disconnect (sess, FALSE, -1);
+		break;
+	case '9':
+		waitline2 (source, tbuf, sizeof tbuf);
+		EMIT_SIGNAL (XP_TE_SERVERLOOKUP, sess, tbuf, NULL, NULL, NULL, 0);
+		break;
+	}
+
+	return TRUE;
+}
+
+/* kill all sockets & iotags of a server. Stop a connection attempt, or
+   disconnect if already connected. */
+
+static int
+server_cleanup (server * serv)
+{
+	fe_set_lag (serv, 0.0);
+
+	if (serv->iotag)
+	{
+		fe_input_remove (serv->iotag);
+		serv->iotag = 0;
+	}
+
+	if (serv->joindelay_tag)
+	{
+		fe_timeout_remove (serv->joindelay_tag);
+		serv->joindelay_tag = 0;
+	}
+
+#ifdef USE_OPENSSL
+	if (serv->ssl)
+	{
+		_SSL_close (serv->ssl);
+		serv->ssl = NULL;
+	}
+#endif
+
+	if (serv->connecting)
+	{
+		server_stopconnecting (serv);
+		closesocket (serv->sok4);
+		if (serv->proxy_sok4 != -1)
+			closesocket (serv->proxy_sok4);
+		if (serv->sok6 != -1)
+			closesocket (serv->sok6);
+		if (serv->proxy_sok6 != -1)
+			closesocket (serv->proxy_sok6);
+		return 1;
+	}
+
+	if (serv->connected)
+	{
+		close_socket (serv->sok);
+		if (serv->proxy_sok)
+			close_socket (serv->proxy_sok);
+		serv->connected = FALSE;
+		serv->end_of_motd = FALSE;
+		return 2;
+	}
+
+	/* is this server in a reconnect delay? remove it! */
+	if (serv->recondelay_tag)
+	{
+		fe_timeout_remove (serv->recondelay_tag);
+		serv->recondelay_tag = 0;
+		return 3;
+	}
+
+	return 0;
+}
+
+static void
+server_disconnect (session * sess, int sendquit, int err)
+{
+	server *serv = sess->server;
+	GSList *list;
+	char tbuf[64];
+	gboolean shutup = FALSE;
+
+	/* send our QUIT reason */
+	if (sendquit && serv->connected)
+	{
+		server_sendquit (sess);
+	}
+
+	fe_server_event (serv, FE_SE_DISCONNECT, 0);
+
+	/* close all sockets & io tags */
+	switch (server_cleanup (serv))
+	{
+	case 0:							  /* it wasn't even connected! */
+		notc_msg (sess);
+		return;
+	case 1:							  /* it was in the process of connecting */
+		sprintf (tbuf, "%d", sess->server->childpid);
+		EMIT_SIGNAL (XP_TE_STOPCONNECT, sess, tbuf, NULL, NULL, NULL, 0);
+		return;
+	case 3:
+		shutup = TRUE;	/* won't print "disconnected" in channels */
+	}
+
+	server_flush_queue (serv);
+
+	list = sess_list;
+	while (list)
+	{
+		sess = (struct session *) list->data;
+		if (sess->server == serv)
+		{
+			if (!shutup || sess->type == SESS_SERVER)
+				/* print "Disconnected" to each window using this server */
+				EMIT_SIGNAL (XP_TE_DISCON, sess, errorstring (err), NULL, NULL, NULL, 0);
+
+			if (!sess->channel[0] || sess->type == SESS_CHANNEL)
+				clear_channel (sess);
+		}
+		list = list->next;
+	}
+
+	serv->pos = 0;
+	serv->motd_skipped = FALSE;
+	serv->no_login = FALSE;
+	serv->servername[0] = 0;
+	serv->lag_sent = 0;
+
+	notify_cleanup ();
+}
+
+/* send a "print text" command to the parent process - MUST END IN \n! */
+
+static void
+proxy_error (int fd, char *msg)
+{
+	write (fd, "0\n", 2);
+	write (fd, msg, strlen (msg));
+}
+
+struct sock_connect
+{
+	char version;
+	char type;
+	guint16 port;
+	guint32 address;
+	char username[10];
+};
+
+/* traverse_socks() returns:
+ *				0 success                *
+ *          1 socks traversal failed */
+
+static int
+traverse_socks (int print_fd, int sok, char *serverAddr, int port)
+{
+	struct sock_connect sc;
+	unsigned char buf[256];
+
+	sc.version = 4;
+	sc.type = 1;
+	sc.port = htons (port);
+	sc.address = inet_addr (serverAddr);
+	strncpy (sc.username, prefs.username, 9);
+
+	send (sok, (char *) &sc, 8 + strlen (sc.username) + 1, 0);
+	buf[1] = 0;
+	recv (sok, buf, 10, 0);
+	if (buf[1] == 90)
+		return 0;
+
+	snprintf (buf, sizeof (buf), "SOCKS\tServer reported error %d,%d.\n", buf[0], buf[1]);
+	proxy_error (print_fd, buf);
+	return 1;
+}
+
+struct sock5_connect1
+{
+	char version;
+	char nmethods;
+	char method;
+};
+
+static int
+traverse_socks5 (int print_fd, int sok, char *serverAddr, int port)
+{
+	struct sock5_connect1 sc1;
+	unsigned char *sc2;
+	unsigned int packetlen, addrlen;
+	unsigned char buf[260];
+	int auth = prefs.proxy_auth && prefs.proxy_user[0] && prefs.proxy_pass[0];
+
+	sc1.version = 5;
+	sc1.nmethods = 1;
+	if (auth)
+		sc1.method = 2;  /* Username/Password Authentication (UPA) */
+	else
+		sc1.method = 0;  /* NO Authentication */
+	send (sok, (char *) &sc1, 3, 0);
+	if (recv (sok, buf, 2, 0) != 2)
+		goto read_error;
+
+	if (buf[0] != 5)
+	{
+		proxy_error (print_fd, "SOCKS\tServer is not socks version 5.\n");
+		return 1;
+	}
+
+	/* did the server say no auth required? */
+	if (buf[1] == 0)
+		auth = 0;
+
+	if (auth)
+	{
+		int len_u=0, len_p=0;
+
+		/* authentication sub-negotiation (RFC1929) */
+		if (buf[1] != 2)  /* UPA not supported by server */
+		{
+			proxy_error (print_fd, "SOCKS\tServer doesn't support UPA authentication.\n");
+			return 1;
+		}
+
+		memset (buf, 0, sizeof(buf));
+
+		/* form the UPA request */
+		len_u = strlen (prefs.proxy_user);
+		len_p = strlen (prefs.proxy_pass);
+		buf[0] = 1;
+		buf[1] = len_u;
+		memcpy (buf + 2, prefs.proxy_user, len_u);
+		buf[2 + len_u] = len_p;
+		memcpy (buf + 3 + len_u, prefs.proxy_pass, len_p);
+
+		send (sok, buf, 3 + len_u + len_p, 0);
+		if ( recv (sok, buf, 2, 0) != 2 )
+			goto read_error;
+		if ( buf[1] != 0 )
+		{
+			proxy_error (print_fd, "SOCKS\tAuthentication failed. "
+							 "Is username and password correct?\n");
+			return 1; /* UPA failed! */
+		}
+	}
+	else
+	{
+		if (buf[1] != 0)
+		{
+			proxy_error (print_fd, "SOCKS\tAuthentication required but disabled in settings.\n");
+			return 1;
+		}
+	}
+
+	addrlen = strlen (serverAddr);
+	packetlen = 4 + 1 + addrlen + 2;
+	sc2 = malloc (packetlen);
+	sc2[0] = 5;						  /* version */
+	sc2[1] = 1;						  /* command */
+	sc2[2] = 0;						  /* reserved */
+	sc2[3] = 3;						  /* address type */
+	sc2[4] = (unsigned char) addrlen;	/* hostname length */
+	memcpy (sc2 + 5, serverAddr, addrlen);
+	*((unsigned short *) (sc2 + 5 + addrlen)) = htons (port);
+	send (sok, sc2, packetlen, 0);
+	free (sc2);
+
+	/* consume all of the reply */
+	if (recv (sok, buf, 4, 0) != 4)
+		goto read_error;
+	if (buf[0] != 5 || buf[1] != 0)
+	{
+		if (buf[1] == 2)
+			snprintf (buf, sizeof (buf), "SOCKS\tProxy refused to connect to host (not allowed).\n");
+		else
+			snprintf (buf, sizeof (buf), "SOCKS\tProxy failed to connect to host (error %d).\n", buf[1]);
+		proxy_error (print_fd, buf);
+		return 1;
+	}
+	if (buf[3] == 1)	/* IPV4 32bit address */
+	{
+		if (recv (sok, buf, 6, 0) != 6)
+			goto read_error;
+	} else if (buf[3] == 4)	/* IPV6 128bit address */
+	{
+		if (recv (sok, buf, 18, 0) != 18)
+			goto read_error;
+	} else if (buf[3] == 3)	/* string, 1st byte is size */
+	{
+		if (recv (sok, buf, 1, 0) != 1)	/* read the string size */
+			goto read_error;
+		packetlen = buf[0] + 2;	/* can't exceed 260 */
+		if (recv (sok, buf, packetlen, 0) != packetlen)
+			goto read_error;
+	}
+
+	return 0;	/* success */
+
+read_error:
+	proxy_error (print_fd, "SOCKS\tRead error from server.\n");
+	return 1;
+}
+
+static int
+traverse_wingate (int print_fd, int sok, char *serverAddr, int port)
+{
+	char buf[128];
+
+	snprintf (buf, sizeof (buf), "%s %d\r\n", serverAddr, port);
+	send (sok, buf, strlen (buf), 0);
+
+	return 0;
+}
+
+/* stuff for HTTP auth is here */
+
+static void
+three_to_four (char *from, char *to)
+{
+	static const char tab64[64]=
+	{
+		'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
+		'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
+		'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
+		'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
+	};
+
+	to[0] = tab64 [ (from[0] >> 2) & 63 ];
+	to[1] = tab64 [ ((from[0] << 4) | (from[1] >> 4)) & 63 ];
+	to[2] = tab64 [ ((from[1] << 2) | (from[2] >> 6)) & 63 ];
+	to[3] = tab64 [ from[2] & 63 ];
+};
+
+void
+base64_encode (char *to, char *from, unsigned int len)
+{
+	while (len >= 3)
+	{
+		three_to_four (from, to);
+		len -= 3;
+		from += 3;
+		to += 4;
+	}
+	if (len)
+	{
+		char three[3]={0,0,0};
+		int i=0;
+		for (i=0;i<len;i++)
+			three[i] = *from++;
+		three_to_four (three, to);
+		if (len == 1)
+			to[2] = to[3] = '=';
+		else if (len == 2)
+			to[3] = '=';
+		to += 4;
+	};
+	to[0] = 0;
+}
+
+static int
+http_read_line (int print_fd, int sok, char *buf, int len)
+{
+#ifdef WIN32
+	/* make sure waitline() uses recv() or it'll fail on win32 */
+	len = waitline (sok, buf, len, FALSE);
+#else
+	len = waitline (sok, buf, len, TRUE);
+#endif
+	if (len >= 1)
+	{
+		/* print the message out (send it to the parent process) */
+		write (print_fd, "0\n", 2);
+
+		if (buf[len-1] == '\r')
+		{
+			buf[len-1] = '\n';
+			write (print_fd, buf, len);
+		} else
+		{
+			write (print_fd, buf, len);
+			write (print_fd, "\n", 1);
+		}
+	}
+
+	return len;
+}
+
+static int
+traverse_http (int print_fd, int sok, char *serverAddr, int port)
+{
+	char buf[512];
+	char auth_data[256];
+	char auth_data2[252];
+	int n, n2;
+
+	n = snprintf (buf, sizeof (buf), "CONNECT %s:%d HTTP/1.0\r\n",
+					  serverAddr, port);
+	if (prefs.proxy_auth)
+	{
+		n2 = snprintf (auth_data2, sizeof (auth_data2), "%s:%s",
+							prefs.proxy_user, prefs.proxy_pass);
+		base64_encode (auth_data, auth_data2, n2);
+		n += snprintf (buf+n, sizeof (buf)-n, "Proxy-Authorization: Basic %s\r\n", auth_data);
+	}
+	n += snprintf (buf+n, sizeof (buf)-n, "\r\n");
+	send (sok, buf, n, 0);
+
+	n = http_read_line (print_fd, sok, buf, sizeof (buf));
+	/* "HTTP/1.0 200 OK" */
+	if (n < 12)
+		return 1;
+	if (memcmp (buf, "HTTP/", 5) || memcmp (buf + 9, "200", 3))
+		return 1;
+	while (1)
+	{
+		/* read until blank line */
+		n = http_read_line (print_fd, sok, buf, sizeof (buf));
+		if (n < 1 || (n == 1 && buf[0] == '\n'))
+			break;
+	}
+	return 0;
+}
+
+static int
+traverse_proxy (int proxy_type, int print_fd, int sok, char *ip, int port, struct msproxy_state_t *state, netstore *ns_proxy, int csok4, int csok6, int *csok, char bound)
+{
+	switch (proxy_type)
+	{
+	case 1:
+		return traverse_wingate (print_fd, sok, ip, port);
+	case 2:
+		return traverse_socks (print_fd, sok, ip, port);
+	case 3:
+		return traverse_socks5 (print_fd, sok, ip, port);
+	case 4:
+		return traverse_http (print_fd, sok, ip, port);
+#ifdef USE_MSPROXY
+	case 5:
+		return traverse_msproxy (sok, ip, port, state, ns_proxy, csok4, csok6, csok, bound);
+#endif
+	}
+
+	return 1;
+}
+
+/* this is the child process making the connection attempt */
+
+static int
+server_child (server * serv)
+{
+	netstore *ns_server;
+	netstore *ns_proxy = NULL;
+	netstore *ns_local;
+	int port = serv->port;
+	int error;
+	int sok, psok;
+	char *hostname = serv->hostname;
+	char *real_hostname = NULL;
+	char *ip;
+	char *proxy_ip = NULL;
+	char *local_ip;
+	int connect_port;
+	char buf[512];
+	char bound = 0;
+	int proxy_type = 0;
+	char *proxy_host = NULL;
+	int proxy_port;
+
+	ns_server = net_store_new ();
+
+	/* is a hostname set? - bind to it */
+	if (prefs.hostname[0])
+	{
+		ns_local = net_store_new ();
+		local_ip = net_resolve (ns_local, prefs.hostname, 0, &real_hostname);
+		if (local_ip != NULL)
+		{
+			snprintf (buf, sizeof (buf), "5\n%s\n", local_ip);
+			write (serv->childwrite, buf, strlen (buf));
+			net_bind (ns_local, serv->sok4, serv->sok6);
+			bound = 1;
+		} else
+		{
+			write (serv->childwrite, "7\n", 2);
+		}
+		net_store_destroy (ns_local);
+	}
+
+	if (!serv->dont_use_proxy) /* blocked in serverlist? */
+	{
+		if (FALSE)
+			;
+#ifdef USE_LIBPROXY
+		else if (prefs.proxy_type == 5)
+		{
+			char **proxy_list;
+			char *url, *proxy;
+
+			url = g_strdup_printf ("irc://%s:%d", hostname, port);
+			proxy_list = px_proxy_factory_get_proxies (libproxy_factory, url);
+
+			if (proxy_list) {
+				/* can use only one */
+				proxy = proxy_list[0];
+				if (!strncmp (proxy, "direct", 6))
+					proxy_type = 0;
+				else if (!strncmp (proxy, "http", 4))
+					proxy_type = 4;
+				else if (!strncmp (proxy, "socks5", 6))
+					proxy_type = 3;
+				else if (!strncmp (proxy, "socks", 5))
+					proxy_type = 2;
+			}
+
+			if (proxy_type) {
+				char *c;
+				c = strchr (proxy, ':') + 3;
+				proxy_host = strdup (c);
+				c = strchr (proxy_host, ':');
+				*c = '\0';
+				proxy_port = atoi (c + 1);
+			}
+
+			g_strfreev (proxy_list);
+			g_free (url);
+#endif
+		} else if (prefs.proxy_host[0] &&
+			   prefs.proxy_type > 0 &&
+			   prefs.proxy_use != 2) /* proxy is NOT dcc-only */
+		{
+			proxy_type = prefs.proxy_type;
+			proxy_host = strdup (prefs.proxy_host);
+			proxy_port = prefs.proxy_port;
+		}
+	}
+
+	serv->proxy_type = proxy_type;
+
+	/* first resolve where we want to connect to */
+	if (proxy_type > 0)
+	{
+		snprintf (buf, sizeof (buf), "9\n%s\n", proxy_host);
+		write (serv->childwrite, buf, strlen (buf));
+		ip = net_resolve (ns_server, proxy_host, proxy_port, &real_hostname);
+		free (proxy_host);
+		if (!ip)
+		{
+			write (serv->childwrite, "1\n", 2);
+			goto xit;
+		}
+		connect_port = proxy_port;
+
+		/* if using socks4 or MS Proxy, attempt to resolve ip for irc server */
+		if ((proxy_type == 2) || (proxy_type == 5))
+		{
+			ns_proxy = net_store_new ();
+			proxy_ip = net_resolve (ns_proxy, hostname, port, &real_hostname);
+			if (!proxy_ip)
+			{
+				write (serv->childwrite, "1\n", 2);
+				goto xit;
+			}
+		} else						  /* otherwise we can just use the hostname */
+			proxy_ip = strdup (hostname);
+	} else
+	{
+		ip = net_resolve (ns_server, hostname, port, &real_hostname);
+		if (!ip)
+		{
+			write (serv->childwrite, "1\n", 2);
+			goto xit;
+		}
+		connect_port = port;
+	}
+
+	snprintf (buf, sizeof (buf), "3\n%s\n%s\n%d\n",
+				 real_hostname, ip, connect_port);
+	write (serv->childwrite, buf, strlen (buf));
+
+	if (!serv->dont_use_proxy && (proxy_type == 5))
+		error = net_connect (ns_server, serv->proxy_sok4, serv->proxy_sok6, &psok);
+	else
+	{
+		error = net_connect (ns_server, serv->sok4, serv->sok6, &sok);
+		psok = sok;
+	}
+
+	if (error != 0)
+	{
+		snprintf (buf, sizeof (buf), "2\n%d\n", sock_error ());
+		write (serv->childwrite, buf, strlen (buf));
+	} else
+	{
+		/* connect succeeded */
+		if (proxy_ip)
+		{
+			switch (traverse_proxy (proxy_type, serv->childwrite, psok, proxy_ip, port, &serv->msp_state, ns_proxy, serv->sok4, serv->sok6, &sok, bound))
+			{
+			case 0:	/* success */
+#ifdef USE_MSPROXY
+				if (!serv->dont_use_proxy && (proxy_type == 5))
+					snprintf (buf, sizeof (buf), "4\n%d %d %d %d %d\n", sok, psok, serv->msp_state.clientid, serv->msp_state.serverid,
+						serv->msp_state.seq_sent);
+				else
+#endif
+					snprintf (buf, sizeof (buf), "4\n%d\n", sok);	/* success */
+				write (serv->childwrite, buf, strlen (buf));
+				break;
+			case 1:	/* socks traversal failed */
+				write (serv->childwrite, "8\n", 2);
+				break;
+			}
+		} else
+		{
+			snprintf (buf, sizeof (buf), "4\n%d\n", sok);	/* success */
+			write (serv->childwrite, buf, strlen (buf));
+		}
+	}
+
+xit:
+
+#if defined (USE_IPV6) || defined (WIN32)
+	/* this is probably not needed */
+	net_store_destroy (ns_server);
+	if (ns_proxy)
+		net_store_destroy (ns_proxy);
+#endif
+
+	/* no need to free ip/real_hostname, this process is exiting */
+#ifdef WIN32
+	/* under win32 we use a thread -> shared memory, must free! */
+	if (proxy_ip)
+		free (proxy_ip);
+	if (ip)
+		free (ip);
+	if (real_hostname)
+		free (real_hostname);
+#endif
+
+	return 0;
+}
+
+static void
+server_connect (server *serv, char *hostname, int port, int no_login)
+{
+	int pid, read_des[2];
+	session *sess = serv->server_session;
+
+#ifdef USE_OPENSSL
+	if (!ctx && serv->use_ssl)
+	{
+		if (!(ctx = _SSL_context_init (ssl_cb_info, FALSE)))
+		{
+			fprintf (stderr, "_SSL_context_init failed\n");
+			exit (1);
+		}
+	}
+#endif
+
+	if (!hostname[0])
+		return;
+
+	if (port < 0)
+	{
+		/* use default port for this server type */
+		port = 6667;
+#ifdef USE_OPENSSL
+		if (serv->use_ssl)
+			port = 9999;
+#endif
+	}
+	port &= 0xffff;	/* wrap around */
+
+	if (serv->connected || serv->connecting || serv->recondelay_tag)
+		server_disconnect (sess, TRUE, -1);
+
+	fe_progressbar_start (sess);
+
+	EMIT_SIGNAL (XP_TE_SERVERLOOKUP, sess, hostname, NULL, NULL, NULL, 0);
+
+	safe_strcpy (serv->servername, hostname, sizeof (serv->servername));
+	/* overlap illegal in strncpy */
+	if (hostname != serv->hostname)
+		safe_strcpy (serv->hostname, hostname, sizeof (serv->hostname));
+
+#ifdef USE_OPENSSL
+	if (serv->use_ssl)
+	{
+		char cert_file[256];
+
+		/* first try network specific cert/key */
+		snprintf (cert_file, sizeof (cert_file), "%s/%s.pem",
+					 get_xdir_fs (), server_get_network (serv, TRUE));
+		if (SSL_CTX_use_certificate_file (ctx, cert_file, SSL_FILETYPE_PEM) == 1)
+			SSL_CTX_use_PrivateKey_file (ctx, cert_file, SSL_FILETYPE_PEM);
+		else
+		{
+			/* if that doesn't exist, try ~/.xchat2/client.pem */
+			snprintf (cert_file, sizeof (cert_file), "%s/%s.pem",
+						 get_xdir_fs (), "client");
+			if (SSL_CTX_use_certificate_file (ctx, cert_file, SSL_FILETYPE_PEM) == 1)
+				SSL_CTX_use_PrivateKey_file (ctx, cert_file, SSL_FILETYPE_PEM);
+		}
+	}
+#endif
+
+	server_set_defaults (serv);
+	serv->connecting = TRUE;
+	serv->port = port;
+	serv->no_login = no_login;
+
+	fe_server_event (serv, FE_SE_CONNECTING, 0);
+	fe_set_away (serv);
+	server_flush_queue (serv);
+
+#ifdef WIN32
+	if (_pipe (read_des, 4096, _O_BINARY) < 0)
+#else
+	if (pipe (read_des) < 0)
+#endif
+		return;
+#ifdef __EMX__ /* os/2 */
+	setmode (read_des[0], O_BINARY);
+	setmode (read_des[1], O_BINARY);
+#endif
+	serv->childread = read_des[0];
+	serv->childwrite = read_des[1];
+
+	/* create both sockets now, drop one later */
+	net_sockets (&serv->sok4, &serv->sok6);
+#ifdef USE_MSPROXY
+	/* In case of MS Proxy we have a separate UDP control connection */
+	if (!serv->dont_use_proxy && (serv->proxy_type == 5))
+		udp_sockets (&serv->proxy_sok4, &serv->proxy_sok6);
+	else
+#endif
+	{
+		serv->proxy_sok4 = -1;
+		serv->proxy_sok6 = -1;
+	}
+
+#ifdef WIN32
+	CloseHandle (CreateThread (NULL, 0,
+										(LPTHREAD_START_ROUTINE)server_child,
+										serv, 0, (DWORD *)&pid));
+#else
+#ifdef LOOKUPD
+	rand();	/* CL: net_resolve calls rand() when LOOKUPD is set, so prepare a different seed for each child. This method giver a bigger variation in seed values than calling srand(time(0)) in the child itself. */
+#endif
+	switch (pid = fork ())
+	{
+	case -1:
+		return;
+
+	case 0:
+		/* this is the child */
+		setuid (getuid ());
+		server_child (serv);
+		_exit (0);
+	}
+#endif
+	serv->childpid = pid;
+	serv->iotag = fe_input_add (serv->childread, FIA_READ, server_read_child,
+										 serv);
+}
+
+void
+server_fill_her_up (server *serv)
+{
+	serv->connect = server_connect;
+	serv->disconnect = server_disconnect;
+	serv->cleanup = server_cleanup;
+	serv->flush_queue = server_flush_queue;
+	serv->auto_reconnect = auto_reconnect;
+
+	proto_fill_her_up (serv);
+}
+
+void
+server_set_encoding (server *serv, char *new_encoding)
+{
+	char *space;
+
+	if (serv->encoding)
+	{
+		free (serv->encoding);
+		/* can be left as NULL to indicate system encoding */
+		serv->encoding = NULL;
+		serv->using_cp1255 = FALSE;
+		serv->using_irc = FALSE;
+	}
+
+	if (new_encoding)
+	{
+		serv->encoding = strdup (new_encoding);
+		/* the serverlist GUI might have added a space 
+			and short description - remove it. */
+		space = strchr (serv->encoding, ' ');
+		if (space)
+			space[0] = 0;
+
+		/* server_inline() uses these flags */
+		if (!strcasecmp (serv->encoding, "CP1255") ||
+			 !strcasecmp (serv->encoding, "WINDOWS-1255"))
+			serv->using_cp1255 = TRUE;
+		else if (!strcasecmp (serv->encoding, "IRC"))
+			serv->using_irc = TRUE;
+	}
+}
+
+server *
+server_new (void)
+{
+	static int id = 0;
+	server *serv;
+
+	serv = malloc (sizeof (struct server));
+	memset (serv, 0, sizeof (struct server));
+
+	/* use server.c and proto-irc.c functions */
+	server_fill_her_up (serv);
+
+	serv->id = id++;
+	serv->sok = -1;
+	strcpy (serv->nick, prefs.nick1);
+	server_set_defaults (serv);
+
+	serv_list = g_slist_prepend (serv_list, serv);
+
+	fe_new_server (serv);
+
+	return serv;
+}
+
+int
+is_server (server *serv)
+{
+	return g_slist_find (serv_list, serv) ? 1 : 0;
+}
+
+void
+server_set_defaults (server *serv)
+{
+	if (serv->chantypes)
+		free (serv->chantypes);
+	if (serv->chanmodes)
+		free (serv->chanmodes);
+	if (serv->nick_prefixes)
+		free (serv->nick_prefixes);
+	if (serv->nick_modes)
+		free (serv->nick_modes);
+
+	serv->chantypes = strdup ("#&!+");
+	serv->chanmodes = strdup ("beI,k,l");
+	serv->nick_prefixes = strdup ("@%+");
+	serv->nick_modes = strdup ("ohv");
+
+	serv->nickcount = 1;
+	serv->nickservtype = 1;
+	serv->end_of_motd = FALSE;
+	serv->is_away = FALSE;
+	serv->supports_watch = FALSE;
+	serv->bad_prefix = FALSE;
+	serv->use_who = TRUE;
+	serv->have_namesx = FALSE;
+	serv->have_uhnames = FALSE;
+	serv->have_whox = FALSE;
+	serv->have_capab = FALSE;
+	serv->have_idmsg = FALSE;
+	serv->have_except = FALSE;
+}
+
+char *
+server_get_network (server *serv, gboolean fallback)
+{
+	if (serv->network)
+		return ((ircnet *)serv->network)->name;
+
+	if (fallback)
+		return serv->servername;
+
+	return NULL;
+}
+
+void
+server_set_name (server *serv, char *name)
+{
+	GSList *list = sess_list;
+	session *sess;
+
+	if (name[0] == 0)
+		name = serv->hostname;
+
+	/* strncpy parameters must NOT overlap */
+	if (name != serv->servername)
+	{
+		safe_strcpy (serv->servername, name, sizeof (serv->servername));
+	}
+
+	while (list)
+	{
+		sess = (session *) list->data;
+		if (sess->server == serv)
+			fe_set_title (sess);
+		list = list->next;
+	}
+
+	if (serv->server_session->type == SESS_SERVER)
+	{
+		if (serv->network)
+		{
+			safe_strcpy (serv->server_session->channel, ((ircnet *)serv->network)->name, CHANLEN);
+		} else
+		{
+			safe_strcpy (serv->server_session->channel, name, CHANLEN);
+		}
+		fe_set_channel (serv->server_session);
+	}
+}
+
+struct away_msg *
+server_away_find_message (server *serv, char *nick)
+{
+	struct away_msg *away;
+	GSList *list = away_list;
+	while (list)
+	{
+		away = (struct away_msg *) list->data;
+		if (away->server == serv && !serv->p_cmp (nick, away->nick))
+			return away;
+		list = list->next;
+	}
+	return NULL;
+}
+
+static void
+server_away_free_messages (server *serv)
+{
+	GSList *list, *next;
+	struct away_msg *away;
+
+	list = away_list;
+	while (list)
+	{
+		away = list->data;
+		next = list->next;
+		if (away->server == serv)
+		{
+			away_list = g_slist_remove (away_list, away);
+			if (away->message)
+				free (away->message);
+			free (away);
+			next = away_list;
+		}
+		list = next;
+	}
+}
+
+void
+server_away_save_message (server *serv, char *nick, char *msg)
+{
+	struct away_msg *away = server_away_find_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;
+			safe_strcpy (away->nick, nick, sizeof (away->nick));
+			away->message = strdup (msg);
+			away_list = g_slist_prepend (away_list, away);
+		}
+	}
+}
+
+void
+server_free (server *serv)
+{
+	serv->cleanup (serv);
+
+	serv_list = g_slist_remove (serv_list, serv);
+
+	dcc_notify_kill (serv);
+	serv->flush_queue (serv);
+	server_away_free_messages (serv);
+
+	free (serv->nick_modes);
+	free (serv->nick_prefixes);
+	free (serv->chanmodes);
+	free (serv->chantypes);
+	if (serv->bad_nick_prefixes)
+		free (serv->bad_nick_prefixes);
+	if (serv->last_away_reason)
+		free (serv->last_away_reason);
+	if (serv->encoding)
+		free (serv->encoding);
+	if (serv->autojoin)
+		free (serv->autojoin);
+
+	fe_server_callback (serv);
+
+	free (serv);
+
+	notify_cleanup ();
+}
diff --git a/src/common/server.h b/src/common/server.h
new file mode 100644
index 00000000..874d27b2
--- /dev/null
+++ b/src/common/server.h
@@ -0,0 +1,26 @@
+#ifndef XCHAT_SERVER_H
+#define XCHAT_SERVER_H
+
+extern GSList *serv_list;
+
+/* eventually need to keep the tcp_* functions isolated to server.c */
+int tcp_send_len (server *serv, char *buf, int len);
+int tcp_send (server *serv, char *buf);
+void tcp_sendf (server *serv, char *fmt, ...);
+int tcp_send_real (void *ssl, int sok, char *encoding, int using_irc, char *buf, int len);
+
+server *server_new (void);
+int is_server (server *serv);
+void server_fill_her_up (server *serv);
+void server_set_encoding (server *serv, char *new_encoding);
+void server_set_defaults (server *serv);
+char *server_get_network (server *serv, gboolean fallback);
+void server_set_name (server *serv, char *name);
+void server_free (server *serv);
+
+void server_away_save_message (server *serv, char *nick, char *msg);
+struct away_msg *server_away_find_message (server *serv, char *nick);
+
+void base64_encode (char *to, char *from, unsigned int len);
+
+#endif
diff --git a/src/common/servlist.c b/src/common/servlist.c
new file mode 100644
index 00000000..20a156a8
--- /dev/null
+++ b/src/common/servlist.c
@@ -0,0 +1,1311 @@
+/* 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "xchat.h"
+#include <glib/ghash.h>
+
+#include "cfgfiles.h"
+#include "fe.h"
+#include "server.h"
+#include "text.h"
+#include "util.h" /* token_foreach */
+#include "xchatc.h"
+
+#include "servlist.h"
+
+
+struct defaultserver
+{
+	char *network;
+	char *host;
+	char *channel;
+	char *charset;
+};
+
+static const struct defaultserver def[] =
+{
+	{"2600net",	0},
+	{0,			"irc.2600.net"},
+
+	{"7-indonesia",	0},
+	{0,			"irc.7-indonesia.org"},
+
+	{"AccessIRC",	0},
+	{0,			"irc.accessirc.net"},
+	{0,			"eu.accessirc.net"},
+
+	{"AfterNET",	0},
+	{0,			"irc.afternet.org"},
+	{0,			"us.afternet.org"},
+	{0,			"eu.afternet.org"},
+
+	{"Aitvaras",	0},
+#ifdef USE_IPV6
+#ifdef USE_OPENSSL
+	{0,			"irc6.ktu.lt/+7668"},
+#endif
+	{0,			"irc6.ktu.lt/7666"},
+#endif
+#ifdef USE_OPENSSL
+	{0,			"irc.data.lt/+6668"},
+	{0,			"irc-ssl.omnitel.net/+6668"},
+	{0,			"irc-ssl.le.lt/+9999"},
+#endif
+	{0,			"irc.data.lt"},
+	{0,			"irc.omnitel.net"},
+	{0,			"irc.ktu.lt"},
+	{0,			"irc.le.lt"},
+	{0,			"irc.takas.lt"},
+	{0,			"irc.5ci.net"},
+	{0,			"irc.kis.lt"},
+
+	{"AmigaNet",	0},
+	{0,			"irc.amiganet.org"},
+	{0,			"us.amiganet.org"},
+	{0,			"uk.amiganet.org"},
+/*	{0,			"no.amiganet.org"},
+	{0,			"au.amiganet.org"},*/
+
+	{"ARCNet",	0},
+	{0,			"se1.arcnet.vapor.com"},
+	{0,			"us1.arcnet.vapor.com"},
+	{0,			"us2.arcnet.vapor.com"},
+	{0,			"us3.arcnet.vapor.com"},
+	{0,			"ca1.arcnet.vapor.com"},
+	{0,			"de1.arcnet.vapor.com"},
+	{0,			"de3.arcnet.vapor.com"},
+	{0,			"ch1.arcnet.vapor.com"},
+	{0,			"be1.arcnet.vapor.com"},
+	{0,			"nl3.arcnet.vapor.com"},
+	{0,			"uk1.arcnet.vapor.com"},
+	{0,			"uk2.arcnet.vapor.com"},
+/*	{0,			"uk3.arcnet.vapor.com"},*/
+	{0,			"fr1.arcnet.vapor.com"},
+
+	{"AstroLink",	0},
+	{0,			"irc.astrolink.org"},
+
+	{"AustNet",	0},
+	{0,			"au.austnet.org"},
+	{0,			"us.austnet.org"},
+	{0,			"ca.austnet.org"},
+
+/*	{"AxeNet",	0},
+	{0,			"irc.axenet.org"},
+	{0,			"angel.axenet.org"},
+	{0,			"energy.axenet.org"},
+	{0,			"python.axenet.org"},*/
+
+	{"AzzurraNet",	0},
+	{0,			"irc.azzurra.org"},
+	{0,			"crypto.azzurra.org"},
+
+	{"Beirut", 0},
+	{0,			"irc.beirut.com"},
+
+	{"ChattingAway", 0},
+	{0,			"irc.chattingaway.com"},
+
+	{"ChatJunkies",	0, "#xchat"},
+	{0,			"irc.chatjunkies.org"},
+	{0,			"nl.chatjunkies.org"},
+
+	{"ChatNet",	0},
+	{0,			"US.ChatNet.Org"},
+	{0,			"EU.ChatNet.Org"},
+
+	{"ChatSociety", 0},
+	{0,			"us.chatsociety.net"},
+	{0,			"eu.chatsociety.net"},
+
+	{"ChatSpike", 0},
+	{0,			"irc.chatspike.net"},
+
+	{"CoolChat",	0},
+	{0,			"irc.coolchat.net"},
+/*	{0,			"unix.coolchat.net"},
+	{0,			"toronto.coolchat.net"},*/
+
+	{"Criten", 0},
+	{0,			"irc.criten.net"},
+	{0,			"irc.eu.criten.net"},
+
+	{"DALnet",	0},
+	{0,			"irc.dal.net"},
+	{0,			"irc.eu.dal.net"},
+
+	{"Dark-Tou-Net",	0},
+	{0,			"irc.d-t-net.de"},
+	{0,			"bw.d-t-net.de"},
+	{0,			"nc.d-t-net.de"},
+	{0,			"wakka.d-t-net.de"},
+
+	{"DarkMyst", 0},
+	{0,			"irc.darkmyst.org"},
+
+	{"DeepIRC", 0},
+	{0,			"irc.deepirc.net"},
+
+	{"DeltaAnime", 0},
+	{0,			"irc.deltaanime.net"},
+
+	{"EFnet",	0},
+	{0,			"irc.blackened.com"},
+	{0,			"irc.Prison.NET"},
+	{0,			"irc.Qeast.net"},
+	{0,			"irc.efnet.pl"},
+	{0,			"efnet.demon.co.uk"},
+/*	{0,			"irc.lagged.org"},*/
+	{0,			"irc.lightning.net"},
+	{0,			"irc.mindspring.com"},
+	{0,			"irc.easynews.com"},
+	{0,			"irc.servercentral.net"},
+
+	{"EnterTheGame",	0},
+	{0,			"IRC.EnterTheGame.Com"},
+
+	{"EUIrc",	0},
+	{0,			"irc.euirc.net"},
+	{0,			"irc.ham.de.euirc.net"},
+	{0,			"irc.ber.de.euirc.net"},
+	{0,			"irc.ffm.de.euirc.net"},
+	{0,			"irc.bre.de.euirc.net"},
+	{0,			"irc.hes.de.euirc.net"},
+	{0,			"irc.vie.at.euirc.net"},
+	{0,			"irc.inn.at.euirc.net"},
+	{0,			"irc.bas.ch.euirc.net"},
+
+	{"EuropNet", 0},
+	{0,			"irc.europnet.org"},
+
+	{"EU-IRC",	0},
+	{0,			"irc.eu-irc.net"},
+
+	{"FDFNet",	0},
+	{0,			"irc.fdfnet.net"},
+	{0,			"irc.eu.fdfnet.net"},
+
+	{"FEFNet",	0},
+	{0,			"irc.fef.net"},
+	{0,			"irc.ggn.net"},
+	{0,			"irc.vendetta.com"},
+
+	{"FreeNode",	0},
+	{0,				"irc.freenode.net"},
+
+/*	{"Freeworld",	0},
+	{0,			"kabel.freeworld.nu"},
+	{0,			"irc.freeworld.nu"},*/
+
+	{"GalaxyNet",	0},
+	{0,			"irc.galaxynet.org"},
+/*	{0,			"sprynet.us.galaxynet.org"},
+	{0,			"atlanta.ga.us.galaxynet.org"},*/
+
+	{"GamesNET",	0},
+	{0,				"irc.gamesnet.net"},
+/*	{0,				"irc.us.gamesnet.net"},
+	{0,				"east.us.gamesnet.net"},
+	{0,				"west.us.gamesnet.net"},*/
+	{0,				"irc.ca.gamesnet.net"},
+	{0,				"irc.eu.gamesnet.net"},
+
+	{"GeekShed",	0},
+	{0,			"irc.geekshed.net"},
+
+	{"German-Elite",	0},
+	{0,			"dominion.german-elite.net"},
+	{0,			"komatu.german-elite.net"},
+/*	{0,			"liberty.german-elite.net"},*/
+
+	{"GimpNet",		0},
+	{0,			"irc.gimp.org"},
+/*	{0,			"irc.au.gimp.org"},*/
+	{0,			"irc.us.gimp.org"},
+
+	{"HabberNet",	0},
+	{0,			"irc.habber.net"},
+
+	{"Hashmark",	0},
+	{0,			"irc.hashmark.net"},
+
+	{"IdleMonkeys", 0},
+	{0,			"irc.idlemonkeys.net"},
+
+/*	{"Infinity-IRC",	0},
+	{0,			"Atlanta.GA.US.Infinity-IRC.Org"},
+	{0,			"Babylon.NY.US.Infinity-IRC.Org"},
+	{0,			"Sunshine.Ca.US.Infinity-IRC.Org"},
+	{0,			"IRC.Infinity-IRC.Org"},*/
+
+	{"iZ-smart.net",	0},
+	{0,			"irc.iZ-smart.net/6666"},
+	{0,			"irc.iZ-smart.net/6667"},
+	{0,			"irc.iZ-smart.net/6668"},
+
+	{"IrcLink",	0},
+	{0,			"irc.irclink.net"},
+	{0,			"Alesund.no.eu.irclink.net"},
+	{0,			"Oslo.no.eu.irclink.net"},
+	{0,			"frogn.no.eu.irclink.net"},
+	{0,			"tonsberg.no.eu.irclink.net"},
+
+	{"IRCNet",		0},
+	{0,				"irc.ircnet.com"},
+	{0,				"irc.stealth.net/6668"},
+	{0,				"ircnet.demon.co.uk"},
+/*	{0,				"ircnet.hinet.hr"},*/
+	{0,				"irc.datacomm.ch"},
+/*	{0,				"ircnet.kaptech.fr"},
+	{0,				"ircnet.easynet.co.uk"},*/
+	{0,				"random.ircd.de"},
+	{0,				"ircnet.netvision.net.il"},
+/*	{0,				"irc.seed.net.tw"},*/
+	{0,				"irc.cs.hut.fi"},
+
+	{"Irctoo.net",	0},
+	{0,			"irc.irctoo.net"},
+
+	{"Krstarica", 0},
+	{0,			"irc.krstarica.com"},
+
+	{"Librenet",	0},
+	{0,			"irc.librenet.net"},
+	{0,			"ielf.fr.librenet.net"},
+
+	{"LinkNet",	0},
+	{0,			"irc.link-net.org"},
+	{0,			"irc.no.link-net.org"},
+/*	{0,			"irc.gamesden.net.au"},*/
+	{0,			"irc.bahnhof.se"},
+/*	{0,			"irc.kinexuseurope.co.uk"},
+	{0,			"irc.gamiix.com"},*/
+
+	{"MagicStar",	0},
+	{0,			"irc.magicstar.net"},
+
+	{"Majistic",	0},
+	{0,			"irc.majistic.net"},
+
+	{"MindForge",	0},
+	{0,			"irc.mindforge.org"},
+
+	{"MintIRC",	0},
+	{0,			"irc.mintirc.net"},
+
+	{"MIXXnet",		0},
+	{0,			"irc.mixxnet.net"},
+
+	{"NeverNET",	0},
+	{0,			"irc.nevernet.net"},
+	{0,			"imagine.nevernet.net"},
+	{0,			"dimension.nevernet.net"},
+	{0,			"universe.nevernet.net"},
+	{0,			"wayland.nevernet.net"},
+	{0,			"forte.nevernet.net"},
+
+	{"NixHelpNet",	0},
+	{0,			"irc.nixhelp.org"},
+	{0,			"us.nixhelp.org"},
+	{0,			"uk.nixhelp.org"},
+	{0,			"uk2.nixhelp.org"},
+	{0,			"uk3.nixhelp.org"},
+	{0,			"nl.nixhelp.org"},
+	{0,			"ca.ld.nixhelp.org"},
+	{0,			"us.co.nixhelp.org"},
+	{0,			"us.ca.nixhelp.org"},
+	{0,			"us.pa.nixhelp.org"},
+
+	{"NullusNet",	0},
+	{0,			"irc.nullus.net"},
+
+	{"Oceanius", 0},
+	{0,			"irc.oceanius.com"},
+
+	{"OFTC",	0},
+	{0,			"irc.oftc.net"},
+
+	{"OtherNet",	0},
+	{0,			"irc.othernet.org"},
+
+	{"OzNet",	0},
+	{0,			"irc.oz.org"},
+
+	{"PIRC.PL",	0},
+	{0,			"irc.pirc.pl"},
+
+	{"PTlink",	0},
+	{0,			"irc.PTlink.net"},
+	{0,			"aaia.PTlink.net"},
+
+	{"PTNet, ISP's",	0},
+	{0,			"irc.PTNet.org"},
+	{0,			"rccn.PTnet.org"},
+	{0,			"EUnet.PTnet.org"},
+	{0,			"madinfo.PTnet.org"},
+	{0,			"netc2.PTnet.org"},
+	{0,			"netc1.PTnet.org"},
+	{0,			"telepac1.ptnet.org"},
+	{0,			"esoterica.PTnet.org"},
+	{0,			"ip-hub.ptnet.org"},
+	{0,			"telepac1.ptnet.org"},
+	{0,			"nortenet.PTnet.org"},
+
+	{"PTNet, UNI",	0},
+	{0,			"irc.PTNet.org"},
+	{0,			"rccn.PTnet.org"},
+	{0,			"uevora.PTnet.org"},
+	{0,			"umoderna.PTnet.org"},
+	{0,			"ist.PTnet.org"},
+	{0,			"aaum.PTnet.org"},
+	{0,			"uc.PTnet.org"},
+	{0,			"ualg.ptnet.org"},
+	{0,			"madinfo.PTnet.org"},
+/*	{0,			"isep.PTnet.org"},*/
+	{0,			"ua.PTnet.org"},
+	{0,			"ipg.PTnet.org"},
+	{0,			"isec.PTnet.org"},
+	{0,			"utad.PTnet.org"},
+	{0,			"iscte.PTnet.org"},
+	{0,			"ubi.PTnet.org"},
+
+	{"QuakeNet",	0},
+	{0,			"irc.quakenet.org"},
+	{0,			"irc.se.quakenet.org"},
+	{0,			"irc.dk.quakenet.org"},
+	{0,			"irc.no.quakenet.org"},
+	{0,			"irc.fi.quakenet.org"},
+	{0,			"irc.be.quakenet.org"},
+	{0,			"irc.uk.quakenet.org"},
+	{0,			"irc.de.quakenet.org"},
+	{0,			"irc.it.quakenet.org"},
+
+	{"RebelChat",	0},
+	{0,			"irc.rebelchat.org"},
+
+/*	{"Recycled-IRC",  0},
+	{0,			"irc.recycled-irc.org"},
+	{0,			"vermin.recycled-irc.org"},
+	{0,			"waste.recycled-irc.org"},
+	{0,			"lumber.recycled-irc.org"},
+	{0,			"trash.recycled-irc.org"},
+	{0,			"unwashed.recycled-irc.org"},
+	{0,			"garbage.recycled-irc.org"},
+	{0,			"dustbin.recycled-irc.org"},*/
+
+	{"RizeNET", 0},
+	{0,			"irc.rizenet.org"},
+	{0,			"omega.rizenet.org"},
+	{0,			"evelance.rizenet.org"},
+	{0,			"lisa.rizenet.org"},
+	{0,			"scott.rizenet.org"},
+
+	{"Rizon", 0},
+	{0,			"irc.rizon.net"},
+
+	{"RusNet", 0, 0, "KOI8-R (Cyrillic)"},
+	{0,			"irc.tomsk.net"},
+	{0,			"irc.rinet.ru"},
+	{0,			"irc.run.net"},
+	{0,			"irc.ru"},
+	{0,			"irc.lucky.net"},
+
+	{"SceneNet",	0},
+	{0,			"irc.scene.org"},
+	{0,			"irc.eu.scene.org"},
+	{0,			"irc.us.scene.org"},
+
+	{"SeilEn.de",	0},
+	{0,			"irc.seilen.de"},
+
+	{"SlashNET",	0},
+	{0,			"irc.slashnet.org"},
+	{0,			"area51.slashnet.org"},
+	{0,			"moo.slashnet.org"},
+	{0,			"radon.slashnet.org"},
+
+	{"Sohbet.Net", 0},
+	{0,			"irc.sohbet.net"},
+
+	{"SolidIRC", 0},
+	{0,			"irc.solidirc.com"},
+
+	{"SorceryNet",	0},
+	{0,			"irc.sorcery.net/9000"},
+	{0,			"irc.us.sorcery.net/9000"},
+	{0,			"irc.eu.sorcery.net/9000"},
+
+	{"Spidernet",	0},
+	{0,			"us.spidernet.org"},
+	{0,			"eu.spidernet.org"},
+	{0,			"irc.spidernet.org"},
+
+	{"StarChat", 0},
+	{0,			"irc.starchat.net"},
+	{0,			"gainesville.starchat.net"},
+	{0,			"freebsd.starchat.net"},
+	{0,			"sunset.starchat.net"},
+	{0,			"revenge.starchat.net"},
+	{0,			"tahoma.starchat.net"},
+	{0,			"neo.starchat.net"},
+
+	{"TNI3",			0},
+	{0,			"irc.tni3.com"},
+
+	{"TURLINet",			0},
+	{0,			"irc.turli.net"},
+	{0,			"irc.servx.ru"},
+	{0,			"irc.gavnos.ru"},
+
+	{"UnderNet",	0},
+	{0,			"us.undernet.org"},
+	{0,			"eu.undernet.org"},
+
+	{"UniBG",		0},
+	{0,			"irc.lirex.com"},
+	{0,			"irc.naturella.com"},
+	{0,			"irc.spnet.net"},
+	{0,			"irc.techno-link.com"},
+	{0,			"irc.telecoms.bg"},
+	{0,			"irc.tu-varna.edu"},
+
+	{"Whiffle",	0},
+	{0,			"irc.whiffle.org"},
+
+	{"Worldnet",		0},
+	{0,			"irc.worldnet.net"},
+	{0,			"irc.fr.worldnet.net"},
+
+	{"Xentonix.net",	0},
+	{0,			"irc.xentonix.net"},
+
+	{"XWorld",	0},
+	{0,			"Buffalo.NY.US.XWorld.org"},
+	{0,			"Minneapolis.MN.US.Xworld.Org"},
+	{0,			"Rochester.NY.US.XWorld.org"},
+	{0,			"Bayern.DE.EU.XWorld.Org"},
+	{0,			"Chicago.IL.US.XWorld.Org"},
+
+	{0,0}
+};
+
+GSList *network_list = 0;
+
+
+void
+servlist_connect (session *sess, ircnet *net, gboolean join)
+{
+	ircserver *ircserv;
+	GSList *list;
+	char *port;
+	server *serv;
+
+	if (!sess)
+		sess = new_ircwindow (NULL, NULL, SESS_SERVER, TRUE);
+
+	serv = sess->server;
+
+	/* connect to the currently selected Server-row */
+	list = g_slist_nth (net->servlist, net->selected);
+	if (!list)
+		list = net->servlist;
+	if (!list)
+		return;
+	ircserv = list->data;
+
+	/* incase a protocol switch is added to the servlist gui */
+	server_fill_her_up (sess->server);
+
+	if (join)
+	{
+		sess->willjoinchannel[0] = 0;
+
+		if (net->autojoin)
+		{
+			if (serv->autojoin)
+				free (serv->autojoin);
+			serv->autojoin = strdup (net->autojoin);
+		}
+	}
+
+	serv->password[0] = 0;
+	if (net->pass)
+		safe_strcpy (serv->password, net->pass, sizeof (serv->password));
+
+	if (net->flags & FLAG_USE_GLOBAL)
+	{
+		strcpy (serv->nick, prefs.nick1);
+	} else
+	{
+		if (net->nick)
+			strcpy (serv->nick, net->nick);
+	}
+
+	serv->dont_use_proxy = (net->flags & FLAG_USE_PROXY) ? FALSE : TRUE;
+
+#ifdef USE_OPENSSL
+	serv->use_ssl = (net->flags & FLAG_USE_SSL) ? TRUE : FALSE;
+	serv->accept_invalid_cert =
+		(net->flags & FLAG_ALLOW_INVALID) ? TRUE : FALSE;
+#endif
+
+	serv->network = net;
+
+	port = strrchr (ircserv->hostname, '/');
+	if (port)
+	{
+		*port = 0;
+
+		/* support "+port" to indicate SSL (like mIRC does) */
+		if (port[1] == '+')
+		{
+#ifdef USE_OPENSSL
+			serv->use_ssl = TRUE;
+#endif
+			serv->connect (serv, ircserv->hostname, atoi (port + 2), FALSE);
+		} else
+		{
+			serv->connect (serv, ircserv->hostname, atoi (port + 1), FALSE);
+		}
+
+		*port = '/';
+	} else
+		serv->connect (serv, ircserv->hostname, -1, FALSE);
+
+	server_set_encoding (serv, net->encoding);
+}
+
+int
+servlist_connect_by_netname (session *sess, char *network, gboolean join)
+{
+	ircnet *net;
+	GSList *list = network_list;
+
+	while (list)
+	{
+		net = list->data;
+
+		if (strcasecmp (net->name, network) == 0)
+		{
+			servlist_connect (sess, net, join);
+			return 1;
+		}
+
+		list = list->next;
+	}
+
+	return 0;
+}
+
+int
+servlist_have_auto (void)
+{
+	GSList *list = network_list;
+	ircnet *net;
+
+	while (list)
+	{
+		net = list->data;
+
+		if (net->flags & FLAG_AUTO_CONNECT)
+			return 1;
+
+		list = list->next;
+	}
+
+	return 0;
+}
+
+int
+servlist_auto_connect (session *sess)
+{
+	GSList *list = network_list;
+	ircnet *net;
+	int ret = 0;
+
+	while (list)
+	{
+		net = list->data;
+
+		if (net->flags & FLAG_AUTO_CONNECT)
+		{
+			servlist_connect (sess, net, TRUE);
+			ret = 1;
+		}
+
+		list = list->next;
+	}
+
+	return ret;
+}
+
+static gint
+servlist_cycle_cb (server *serv)
+{
+	if (serv->network)
+	{
+		PrintTextf (serv->server_session,
+			_("Cycling to next server in %s...\n"), ((ircnet *)serv->network)->name);
+		servlist_connect (serv->server_session, serv->network, TRUE);
+	}
+
+	return 0;
+}
+
+int
+servlist_cycle (server *serv)
+{
+	ircnet *net;
+	int max, del;
+
+	net = serv->network;
+	if (net)
+	{
+		max = g_slist_length (net->servlist);
+		if (max > 0)
+		{
+			/* try the next server, if that option is on */
+			if (net->flags & FLAG_CYCLE)
+			{
+				net->selected++;
+				if (net->selected >= max)
+					net->selected = 0;
+			}
+
+			del = prefs.recon_delay * 1000;
+			if (del < 1000)
+				del = 500;				  /* so it doesn't block the gui */
+
+			if (del)
+				serv->recondelay_tag = fe_timeout_add (del, servlist_cycle_cb, serv);
+			else
+				servlist_connect (serv->server_session, net, TRUE);
+
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+ircserver *
+servlist_server_find (ircnet *net, char *name, int *pos)
+{
+	GSList *list = net->servlist;
+	ircserver *serv;
+	int i = 0;
+
+	while (list)
+	{
+		serv = list->data;
+		if (strcmp (serv->hostname, name) == 0)
+		{
+			if (pos)
+				*pos = i;
+			return serv;
+		}
+		i++;
+		list = list->next;
+	}
+
+	return NULL;
+}
+
+/* find a network (e.g. (ircnet *) to "FreeNode") from a hostname
+   (e.g. "irc.eu.freenode.net") */
+
+ircnet *
+servlist_net_find_from_server (char *server_name)
+{
+	GSList *list = network_list;
+	GSList *slist;
+	ircnet *net;
+	ircserver *serv;
+
+	while (list)
+	{
+		net = list->data;
+
+		slist = net->servlist;
+		while (slist)
+		{
+			serv = slist->data;
+			if (strcasecmp (serv->hostname, server_name) == 0)
+				return net;
+			slist = slist->next;
+		}
+
+		list = list->next;
+	}
+
+	return NULL;
+}
+
+ircnet *
+servlist_net_find (char *name, int *pos, int (*cmpfunc) (const char *, const char *))
+{
+	GSList *list = network_list;
+	ircnet *net;
+	int i = 0;
+
+	while (list)
+	{
+		net = list->data;
+		if (cmpfunc (net->name, name) == 0)
+		{
+			if (pos)
+				*pos = i;
+			return net;
+		}
+		i++;
+		list = list->next;
+	}
+
+	return NULL;
+}
+
+ircserver *
+servlist_server_add (ircnet *net, char *name)
+{
+	ircserver *serv;
+
+	serv = malloc (sizeof (ircserver));
+	memset (serv, 0, sizeof (ircserver));
+	serv->hostname = strdup (name);
+
+	net->servlist = g_slist_append (net->servlist, serv);
+
+	return serv;
+}
+
+void
+servlist_server_remove (ircnet *net, ircserver *serv)
+{
+	free (serv->hostname);
+	free (serv);
+	net->servlist = g_slist_remove (net->servlist, serv);
+}
+
+static void
+servlist_server_remove_all (ircnet *net)
+{
+	ircserver *serv;
+
+	while (net->servlist)
+	{
+		serv = net->servlist->data;
+		servlist_server_remove (net, serv);
+	}
+}
+
+static void
+free_and_clear (char *str)
+{
+	if (str)
+	{
+		char *orig = str;
+		while (*str)
+			*str++ = 0;
+		free (orig);
+	}
+}
+
+/* executed on exit: Clear any password strings */
+
+void
+servlist_cleanup (void)
+{
+	GSList *list;
+	ircnet *net;
+
+	for (list = network_list; list; list = list->next)
+	{
+		net = list->data;
+		free_and_clear (net->pass);
+		free_and_clear (net->nickserv);
+	}
+}
+
+void
+servlist_net_remove (ircnet *net)
+{
+	GSList *list;
+	server *serv;
+
+	servlist_server_remove_all (net);
+	network_list = g_slist_remove (network_list, net);
+
+	if (net->nick)
+		free (net->nick);
+	if (net->nick2)
+		free (net->nick2);
+	if (net->user)
+		free (net->user);
+	if (net->real)
+		free (net->real);
+	free_and_clear (net->pass);
+	if (net->autojoin)
+		free (net->autojoin);
+	if (net->command)
+		free (net->command);
+	free_and_clear (net->nickserv);
+	if (net->comment)
+		free (net->comment);
+	if (net->encoding)
+		free (net->encoding);
+	free (net->name);
+	free (net);
+
+	/* for safety */
+	list = serv_list;
+	while (list)
+	{
+		serv = list->data;
+		if (serv->network == net)
+			serv->network = NULL;
+		list = list->next;
+	}
+}
+
+ircnet *
+servlist_net_add (char *name, char *comment, int prepend)
+{
+	ircnet *net;
+
+	net = malloc (sizeof (ircnet));
+	memset (net, 0, sizeof (ircnet));
+	net->name = strdup (name);
+/*	net->comment = strdup (comment);*/
+	net->flags = FLAG_CYCLE | FLAG_USE_GLOBAL | FLAG_USE_PROXY;
+
+	if (prepend)
+		network_list = g_slist_prepend (network_list, net);
+	else
+		network_list = g_slist_append (network_list, net);
+
+	return net;
+}
+
+static void
+servlist_load_defaults (void)
+{
+	int i = 0, j = 0;
+	ircnet *net = NULL;
+
+	while (1)
+	{
+		if (def[i].network)
+		{
+			net = servlist_net_add (def[i].network, def[i].host, FALSE);
+			net->encoding = strdup ("IRC (Latin/Unicode Hybrid)");
+			if (def[i].channel)
+				net->autojoin = strdup (def[i].channel);
+			if (def[i].charset)
+			{
+				free (net->encoding);
+				net->encoding = strdup (def[i].charset);
+			}
+			if (g_str_hash (def[i].network) == 0x8e1b96f7)
+				prefs.slist_select = j;
+			j++;
+		} else
+		{
+			servlist_server_add (net, def[i].host);
+			if (!def[i+1].host && !def[i+1].network)
+				break;
+		}
+		i++;
+	}
+}
+
+static int
+servlist_load (void)
+{
+	FILE *fp;
+	char buf[2048];
+	int len;
+	char *tmp;
+	ircnet *net = NULL;
+
+	fp = xchat_fopen_file ("servlist_.conf", "r", 0);
+	if (!fp)
+		return FALSE;
+
+	while (fgets (buf, sizeof (buf) - 2, fp))
+	{
+		len = strlen (buf);
+		buf[len] = 0;
+		buf[len-1] = 0;	/* remove the trailing \n */
+		if (net)
+		{
+			switch (buf[0])
+			{
+			case 'I':
+				net->nick = strdup (buf + 2);
+				break;
+			case 'i':
+				net->nick2 = strdup (buf + 2);
+				break;
+			case 'U':
+				net->user = strdup (buf + 2);
+				break;
+			case 'R':
+				net->real = strdup (buf + 2);
+				break;
+			case 'P':
+				net->pass = strdup (buf + 2);
+				break;
+			case 'J':
+				net->autojoin = strdup (buf + 2);
+				break;
+			case 'C':
+				if (net->command)
+				{
+					/* concat extra commands with a \n separator */
+					tmp = net->command;
+					net->command = malloc (strlen (tmp) + strlen (buf + 2) + 2);
+					strcpy (net->command, tmp);
+					strcat (net->command, "\n");
+					strcat (net->command, buf + 2);
+					free (tmp);
+				} else
+					net->command = strdup (buf + 2);
+				break;
+			case 'F':
+				net->flags = atoi (buf + 2);
+				break;
+			case 'D':
+				net->selected = atoi (buf + 2);
+				break;
+			case 'E':
+				net->encoding = strdup (buf + 2);
+				break;
+			case 'S':	/* new server/hostname for this network */
+				servlist_server_add (net, buf + 2);
+				break;
+			case 'B':
+				net->nickserv = strdup (buf + 2);
+				break;
+			}
+		}
+		if (buf[0] == 'N')
+			net = servlist_net_add (buf + 2, /* comment */ NULL, FALSE);
+	}
+	fclose (fp);
+
+	return TRUE;
+}
+
+void
+servlist_init (void)
+{
+	if (!network_list)
+		if (!servlist_load ())
+			servlist_load_defaults ();
+}
+
+/* check if a charset is known by Iconv */
+int
+servlist_check_encoding (char *charset)
+{
+	GIConv gic;
+	char *c;
+
+	c = strchr (charset, ' ');
+	if (c)
+		c[0] = 0;
+
+	if (!strcasecmp (charset, "IRC")) /* special case */
+	{
+		if (c)
+			c[0] = ' ';
+		return TRUE;
+	}
+
+	gic = g_iconv_open (charset, "UTF-8");
+
+	if (c)
+		c[0] = ' ';
+
+	if (gic != (GIConv)-1)
+	{
+		g_iconv_close (gic);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int
+servlist_write_ccmd (char *str, void *fp)
+{
+	return fprintf (fp, "C=%s\n", (str[0] == '/') ? str + 1 : str);
+}
+
+
+int
+servlist_save (void)
+{
+	FILE *fp;
+	char buf[256];
+	ircnet *net;
+	ircserver *serv;
+	GSList *list;
+	GSList *hlist;
+#ifndef WIN32
+	int first = FALSE;
+
+	snprintf (buf, sizeof (buf), "%s/servlist_.conf", get_xdir_fs ());
+	if (access (buf, F_OK) != 0)
+		first = TRUE;
+#endif
+
+	fp = xchat_fopen_file ("servlist_.conf", "w", 0);
+	if (!fp)
+		return FALSE;
+
+#ifndef WIN32
+	if (first)
+		chmod (buf, 0600);
+#endif
+	fprintf (fp, "v="PACKAGE_VERSION"\n\n");
+
+	list = network_list;
+	while (list)
+	{
+		net = list->data;
+
+		fprintf (fp, "N=%s\n", net->name);
+		if (net->nick)
+			fprintf (fp, "I=%s\n", net->nick);
+		if (net->nick2)
+			fprintf (fp, "i=%s\n", net->nick2);
+		if (net->user)
+			fprintf (fp, "U=%s\n", net->user);
+		if (net->real)
+			fprintf (fp, "R=%s\n", net->real);
+		if (net->pass)
+			fprintf (fp, "P=%s\n", net->pass);
+		if (net->autojoin)
+			fprintf (fp, "J=%s\n", net->autojoin);
+		if (net->nickserv)
+			fprintf (fp, "B=%s\n", net->nickserv);
+		if (net->encoding && strcasecmp (net->encoding, "System") &&
+			 strcasecmp (net->encoding, "System default"))
+		{
+			fprintf (fp, "E=%s\n", net->encoding);
+			if (!servlist_check_encoding (net->encoding))
+			{
+				snprintf (buf, sizeof (buf), _("Warning: \"%s\" character set is unknown. No conversion will be applied for network %s."),
+							 net->encoding, net->name);
+				fe_message (buf, FE_MSG_WARN);
+			}
+		}
+
+		if (net->command)
+			token_foreach (net->command, '\n', servlist_write_ccmd, fp);
+
+		fprintf (fp, "F=%d\nD=%d\n", net->flags, net->selected);
+
+		hlist = net->servlist;
+		while (hlist)
+		{
+			serv = hlist->data;
+			fprintf (fp, "S=%s\n", serv->hostname);
+			hlist = hlist->next;
+		}
+
+		if (fprintf (fp, "\n") < 1)
+		{
+			fclose (fp);
+			return FALSE;
+		}
+
+		list = list->next;
+	}
+
+	fclose (fp);
+	return TRUE;
+}
+
+static void
+joinlist_free1 (GSList *list)
+{
+	GSList *head = list;
+
+	for (; list; list = list->next)
+		g_free (list->data);
+	g_slist_free (head);
+}
+
+void
+joinlist_free (GSList *channels, GSList *keys)
+{
+	joinlist_free1 (channels);
+	joinlist_free1 (keys);
+}
+
+gboolean
+joinlist_is_in_list (server *serv, char *channel)
+{
+	GSList *channels, *keys;
+	GSList *list;
+
+	if (!serv->network || !((ircnet *)serv->network)->autojoin)
+		return FALSE;
+
+	joinlist_split (((ircnet *)serv->network)->autojoin, &channels, &keys);
+
+	for (list = channels; list; list = list->next)
+	{
+		if (serv->p_cmp (list->data, channel) == 0)
+			return TRUE;
+	}
+
+	joinlist_free (channels, keys);
+
+	return FALSE;
+}
+
+gchar *
+joinlist_merge (GSList *channels, GSList *keys)
+{
+	GString *out = g_string_new (NULL);
+	GSList *list;
+	int i, j;
+
+	for (; channels; channels = channels->next)
+	{
+		g_string_append (out, channels->data);
+
+		if (channels->next)
+			g_string_append_c (out, ',');
+	}
+
+	/* count number of REAL keys */
+	for (i = 0, list = keys; list; list = list->next)
+		if (list->data)
+			i++;
+
+	if (i > 0)
+	{
+		g_string_append_c (out, ' ');
+
+		for (j = 0; keys; keys = keys->next)
+		{
+			if (keys->data)
+			{
+				g_string_append (out, keys->data);
+				j++;
+				if (j == i)
+					break;
+			}
+
+			if (keys->next)
+				g_string_append_c (out, ',');
+		}
+	}
+
+	return g_string_free (out, FALSE);
+}
+
+void
+joinlist_split (char *autojoin, GSList **channels, GSList **keys)
+{
+	char *parta, *partb;
+	char *chan, *key;
+	int len;
+
+	*channels = NULL;
+	*keys = NULL;
+
+	/* after the first space, the keys begin */
+	parta = autojoin;
+	partb = strchr (autojoin, ' ');
+	if (partb)
+		partb++;
+
+	while (1)
+	{
+		chan = parta;
+		key = partb;
+
+		if (1)
+		{
+			while (parta[0] != 0 && parta[0] != ',' && parta[0] != ' ')
+			{
+				parta++;
+			}
+		}
+
+		if (partb)
+		{
+			while (partb[0] != 0 && partb[0] != ',' && partb[0] != ' ')
+			{
+				partb++;
+			}
+		}
+
+		len = parta - chan;
+		if (len < 1)
+			break;
+		*channels = g_slist_append (*channels, g_strndup (chan, len));
+
+		len = partb - key;
+		*keys = g_slist_append (*keys, len ? g_strndup (key, len) : NULL);
+
+		if (parta[0] == ' ' || parta[0] == 0)
+			break;
+		parta++;
+
+		if (partb)
+		{
+			if (partb[0] == 0 || partb[0] == ' ')
+				partb = NULL;	/* no more keys, but maybe more channels? */
+			else
+				partb++;
+		}
+	}
+
+#if 0
+	GSList *lista, *listb;
+	int i;
+
+	printf("-----\n");
+	i = 0;
+	lista = *channels;
+	listb = *keys;
+	while (lista)
+	{
+		printf("%d. |%s| |%s|\n", i, lista->data, listb->data);
+		i++;
+		lista = lista->next;
+		listb = listb->next;
+	}
+	printf("-----\n\n");
+#endif
+}
+
+
diff --git a/src/common/servlist.h b/src/common/servlist.h
new file mode 100644
index 00000000..9b7734f4
--- /dev/null
+++ b/src/common/servlist.h
@@ -0,0 +1,62 @@
+#ifndef XCHAT_SERVLIST_H
+#define XCHAT_SERVLIST_H
+
+typedef struct ircserver
+{
+	char *hostname;
+} ircserver;
+
+typedef struct ircnet
+{
+	char *name;
+	char *nick;
+	char *nick2;
+	char *user;
+	char *real;
+	char *pass;
+	char *autojoin;
+	char *command;
+	char *nickserv;
+	char *comment;
+	char *encoding;
+	GSList *servlist;
+	int selected;
+	guint32 flags;
+} ircnet;
+
+extern GSList *network_list;
+
+#define FLAG_CYCLE				1
+#define FLAG_USE_GLOBAL			2
+#define FLAG_USE_SSL				4
+#define FLAG_AUTO_CONNECT		8
+#define FLAG_USE_PROXY			16
+#define FLAG_ALLOW_INVALID		32
+#define FLAG_FAVORITE			64
+#define FLAG_COUNT				7
+
+void servlist_init (void);
+int servlist_save (void);
+int servlist_cycle (server *serv);
+void servlist_connect (session *sess, ircnet *net, gboolean join);
+int servlist_connect_by_netname (session *sess, char *network, gboolean join);
+int servlist_auto_connect (session *sess);
+int servlist_have_auto (void);
+int servlist_check_encoding (char *charset);
+void servlist_cleanup (void);
+
+ircnet *servlist_net_add (char *name, char *comment, int prepend);
+void servlist_net_remove (ircnet *net);
+ircnet *servlist_net_find (char *name, int *pos, int (*cmpfunc) (const char *, const char *));
+ircnet *servlist_net_find_from_server (char *server_name);
+
+void servlist_server_remove (ircnet *net, ircserver *serv);
+ircserver *servlist_server_add (ircnet *net, char *name);
+ircserver *servlist_server_find (ircnet *net, char *name, int *pos);
+
+void joinlist_split (char *autojoin, GSList **channels, GSList **keys);
+gboolean joinlist_is_in_list (server *serv, char *channel);
+void joinlist_free (GSList *channels, GSList *keys);
+gchar *joinlist_merge (GSList *channels, GSList *keys);
+
+#endif
diff --git a/src/common/ssl.c b/src/common/ssl.c
new file mode 100644
index 00000000..a18ad47c
--- /dev/null
+++ b/src/common/ssl.c
@@ -0,0 +1,323 @@
+/*
+ * ssl.c v0.0.3
+ * Copyright (C) 2000  --  DaP <profeta@freemail.c3.hu>
+ *
+ * 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 <openssl/ssl.h>		  /* SSL_() */
+#include <openssl/err.h>		  /* ERR_() */
+#include <time.h>					  /* asctime() */
+#include <string.h>				  /* strncpy() */
+#include "ssl.h"					  /* struct cert_info */
+#include "inet.h"
+#include "../../config.h"		  /* HAVE_SNPRINTF */
+
+#ifndef HAVE_SNPRINTF
+#define snprintf g_snprintf
+#endif
+
+/* globals */
+static struct chiper_info chiper_info;		/* static buffer for _SSL_get_cipher_info() */
+static char err_buf[256];			/* generic error buffer */
+
+
+/* +++++ Internal functions +++++ */
+
+static void
+__SSL_fill_err_buf (char *funcname)
+{
+	int err;
+	char buf[256];
+
+
+	err = ERR_get_error ();
+	ERR_error_string (err, buf);
+	snprintf (err_buf, sizeof (err_buf), "%s: %s (%d)\n", funcname, buf, err);
+}
+
+
+static void
+__SSL_critical_error (char *funcname)
+{
+	__SSL_fill_err_buf (funcname);
+	fprintf (stderr, "%s\n", err_buf);
+
+	exit (1);
+}
+
+/* +++++ SSL functions +++++ */
+
+SSL_CTX *
+_SSL_context_init (void (*info_cb_func), int server)
+{
+	SSL_CTX *ctx;
+#ifdef WIN32
+	int i, r;
+#endif
+
+	SSLeay_add_ssl_algorithms ();
+	SSL_load_error_strings ();
+	ctx = SSL_CTX_new (server ? SSLv3_server_method() : SSLv3_client_method ());
+
+	SSL_CTX_set_session_cache_mode (ctx, SSL_SESS_CACHE_BOTH);
+	SSL_CTX_set_timeout (ctx, 300);
+
+	/* used in SSL_connect(), SSL_accept() */
+	SSL_CTX_set_info_callback (ctx, info_cb_func);
+
+#ifdef WIN32
+	/* under win32, OpenSSL needs to be seeded with some randomness */
+	for (i = 0; i < 128; i++)
+	{
+		r = rand ();
+		RAND_seed ((unsigned char *)&r, sizeof (r));
+	}
+#endif
+
+	return(ctx);
+}
+
+static void
+ASN1_TIME_snprintf (char *buf, int buf_len, ASN1_TIME * tm)
+{
+	char *expires = NULL;
+	BIO *inMem = BIO_new (BIO_s_mem ());
+
+	ASN1_TIME_print (inMem, tm);
+	BIO_get_mem_data (inMem, &expires);
+	buf[0] = 0;
+	if (expires != NULL)
+	{
+		memset (buf, 0, buf_len);
+		strncpy (buf, expires, 24);
+	}
+	BIO_free (inMem);
+}
+
+
+static void
+broke_oneline (char *oneline, char *parray[])
+{
+	char *pt, *ppt;
+	int i;
+
+
+	i = 0;
+	ppt = pt = oneline + 1;
+	while ((pt = strchr (pt, '/')))
+	{
+		*pt = 0;
+		parray[i++] = ppt;
+		ppt = ++pt;
+	}
+	parray[i++] = ppt;
+	parray[i] = NULL;
+}
+
+
+/*
+    FIXME: Master-Key, Extensions, CA bits
+	    (openssl x509 -text -in servcert.pem)
+*/
+int
+_SSL_get_cert_info (struct cert_info *cert_info, SSL * ssl)
+{
+	X509 *peer_cert;
+	EVP_PKEY *peer_pkey;
+	/* EVP_PKEY *ca_pkey; */
+	/* EVP_PKEY *tmp_pkey; */
+	char notBefore[64];
+	char notAfter[64];
+	int alg;
+	int sign_alg;
+
+
+	if (!(peer_cert = SSL_get_peer_certificate (ssl)))
+		return (1);				  /* FATAL? */
+
+	X509_NAME_oneline (X509_get_subject_name (peer_cert), cert_info->subject,
+							 sizeof (cert_info->subject));
+	X509_NAME_oneline (X509_get_issuer_name (peer_cert), cert_info->issuer,
+							 sizeof (cert_info->issuer));
+	broke_oneline (cert_info->subject, cert_info->subject_word);
+	broke_oneline (cert_info->issuer, cert_info->issuer_word);
+
+	alg = OBJ_obj2nid (peer_cert->cert_info->key->algor->algorithm);
+	sign_alg = OBJ_obj2nid (peer_cert->sig_alg->algorithm);
+	ASN1_TIME_snprintf (notBefore, sizeof (notBefore),
+							  X509_get_notBefore (peer_cert));
+	ASN1_TIME_snprintf (notAfter, sizeof (notAfter),
+							  X509_get_notAfter (peer_cert));
+
+	peer_pkey = X509_get_pubkey (peer_cert);
+
+	strncpy (cert_info->algorithm,
+				(alg == NID_undef) ? "Unknown" : OBJ_nid2ln (alg),
+				sizeof (cert_info->algorithm));
+	cert_info->algorithm_bits = EVP_PKEY_bits (peer_pkey);
+	strncpy (cert_info->sign_algorithm,
+				(sign_alg == NID_undef) ? "Unknown" : OBJ_nid2ln (sign_alg),
+				sizeof (cert_info->sign_algorithm));
+	/* EVP_PKEY_bits(ca_pkey)); */
+	cert_info->sign_algorithm_bits = 0;
+	strncpy (cert_info->notbefore, notBefore, sizeof (cert_info->notbefore));
+	strncpy (cert_info->notafter, notAfter, sizeof (cert_info->notafter));
+
+	EVP_PKEY_free (peer_pkey);
+
+	/* SSL_SESSION_print_fp(stdout, SSL_get_session(ssl)); */
+/*
+	if (ssl->session->sess_cert->peer_rsa_tmp) {
+		tmp_pkey = EVP_PKEY_new();
+		EVP_PKEY_assign_RSA(tmp_pkey, ssl->session->sess_cert->peer_rsa_tmp);
+		cert_info->rsa_tmp_bits = EVP_PKEY_bits (tmp_pkey);
+		EVP_PKEY_free(tmp_pkey);
+	} else
+		fprintf(stderr, "REMOTE SIDE DOESN'T PROVIDES ->peer_rsa_tmp\n");
+*/
+	cert_info->rsa_tmp_bits = 0;
+
+	X509_free (peer_cert);
+
+	return (0);
+}
+
+
+struct chiper_info *
+_SSL_get_cipher_info (SSL * ssl)
+{
+	SSL_CIPHER *c;
+
+
+	c = SSL_get_current_cipher (ssl);
+	strncpy (chiper_info.version, SSL_CIPHER_get_version (c),
+				sizeof (chiper_info.version));
+	strncpy (chiper_info.chiper, SSL_CIPHER_get_name (c),
+				sizeof (chiper_info.chiper));
+	SSL_CIPHER_get_bits (c, &chiper_info.chiper_bits);
+
+	return (&chiper_info);
+}
+
+
+int
+_SSL_send (SSL * ssl, char *buf, int len)
+{
+	int num;
+
+
+	num = SSL_write (ssl, buf, len);
+
+	switch (SSL_get_error (ssl, num))
+	{
+	case SSL_ERROR_SSL:			  /* setup errno! */
+		/* ??? */
+		__SSL_fill_err_buf ("SSL_write");
+		fprintf (stderr, "%s\n", err_buf);
+		break;
+	case SSL_ERROR_SYSCALL:
+		/* ??? */
+		perror ("SSL_write/write");
+		break;
+	case SSL_ERROR_ZERO_RETURN:
+		/* fprintf(stderr, "SSL closed on write\n"); */
+		break;
+	}
+
+	return (num);
+}
+
+
+int
+_SSL_recv (SSL * ssl, char *buf, int len)
+{
+	int num;
+
+
+	num = SSL_read (ssl, buf, len);
+
+	switch (SSL_get_error (ssl, num))
+	{
+	case SSL_ERROR_SSL:
+		/* ??? */
+		__SSL_fill_err_buf ("SSL_read");
+		fprintf (stderr, "%s\n", err_buf);
+		break;
+	case SSL_ERROR_SYSCALL:
+		/* ??? */
+		if (!would_block ())
+			perror ("SSL_read/read");
+		break;
+	case SSL_ERROR_ZERO_RETURN:
+		/* fprintf(stdeerr, "SSL closed on read\n"); */
+		break;
+	}
+
+	return (num);
+}
+
+
+SSL *
+_SSL_socket (SSL_CTX *ctx, int sd)
+{
+	SSL *ssl;
+
+
+	if (!(ssl = SSL_new (ctx)))
+		/* FATAL */
+		__SSL_critical_error ("SSL_new");
+
+	SSL_set_fd (ssl, sd);
+	if (ctx->method == SSLv3_client_method())
+		SSL_set_connect_state (ssl);
+	else
+	        SSL_set_accept_state(ssl);
+
+	return (ssl);
+}
+
+
+char *
+_SSL_set_verify (SSL_CTX *ctx, void *verify_callback, char *cacert)
+{
+	if (!SSL_CTX_set_default_verify_paths (ctx))
+	{
+		__SSL_fill_err_buf ("SSL_CTX_set_default_verify_paths");
+		return (err_buf);
+	}
+/*
+	if (cacert)
+	{
+		if (!SSL_CTX_load_verify_locations (ctx, cacert, NULL))
+		{
+			__SSL_fill_err_buf ("SSL_CTX_load_verify_locations");
+			return (err_buf);
+		}
+	}
+*/
+	SSL_CTX_set_verify (ctx, SSL_VERIFY_PEER, verify_callback);
+
+	return (NULL);
+}
+
+
+void
+_SSL_close (SSL * ssl)
+{
+	SSL_set_shutdown (ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
+	SSL_free (ssl);
+	ERR_remove_state (0);		  /* free state buffer */
+}
diff --git a/src/common/ssl.h b/src/common/ssl.h
new file mode 100644
index 00000000..26eb0f88
--- /dev/null
+++ b/src/common/ssl.h
@@ -0,0 +1,65 @@
+/*
+    ...
+*/
+
+struct cert_info {
+    char subject[256];
+    char *subject_word[12];
+    char issuer[256];
+    char *issuer_word[12];
+    char algorithm[32];
+    int algorithm_bits;
+    char sign_algorithm[32];
+    int sign_algorithm_bits;
+    char notbefore[32];
+    char notafter[32];
+
+    int rsa_tmp_bits;
+};
+
+struct chiper_info {
+    char version[16];
+    char chiper[24];
+    int chiper_bits;
+};
+
+SSL_CTX *_SSL_context_init (void (*info_cb_func), int server);
+#define _SSL_context_free(a)	SSL_CTX_free(a);
+
+SSL *_SSL_socket (SSL_CTX *ctx, int sd);
+char *_SSL_set_verify (SSL_CTX *ctx, void *(verify_callback), char *cacert);
+/*
+    int SSL_connect(SSL *);
+    int SSL_accept(SSL *);
+    int SSL_get_fd(SSL *);
+*/
+void _SSL_close (SSL * ssl);
+
+int _SSL_get_cert_info (struct cert_info *cert_info, SSL * ssl);
+struct chiper_info *_SSL_get_cipher_info (SSL * ssl);
+
+/*char *_SSL_add_keypair (SSL_CTX *ctx, char *privkey, char *cert);*/
+/*void _SSL_add_random_keypair(SSL_CTX *ctx, int bits);*/
+
+int _SSL_send (SSL * ssl, char *buf, int len);
+int _SSL_recv (SSL * ssl, char *buf, int len);
+
+/* misc */
+/*void broke_oneline (char *oneline, char *parray[]);*/
+
+/*char *_SSL_do_cipher_base64(char *buf, int buf_len, char *key, int operation);*/		/* must be freed */
+
+/*void *_SSL_get_sess_obj(SSL *ssl, int type);*/		/* NOT must be freed */
+#define	_SSL_get_sess_pkey(a)	_SSL_get_sess_obj(a, 0)
+#define	_SSL_get_sess_prkey(a)	_SSL_get_sess_obj(a, 1)
+#define	_SSL_get_sess_x509(a)	_SSL_get_sess_obj(a, 2)
+/*char *_SSL_get_obj_base64(void *s, int type);*/		/* must be freed */
+#define	_SSL_get_pkey_base64(a)		_SSL_get_obj_base64(a, 0)
+#define	_SSL_get_prkey_base64(a)	_SSL_get_obj_base64(a, 1)
+#define	_SSL_get_x509_base64(a)		_SSL_get_obj_base64(a, 2)
+/*char *_SSL_get_ctx_obj_base64(SSL_CTX *ctx, int type);*/	/* must be freed */
+#define	_SSL_get_ctx_pkey_base64(a)	_SSL_get_ctx_obj_base64(a, 0)
+#define	_SSL_get_ctx_prkey_base64(a)	_SSL_get_ctx_obj_base64(a, 1)
+#define	_SSL_get_ctx_x509_base64(a)	_SSL_get_ctx_obj_base64(a, 2)
+
+/*int _SSL_verify_x509(X509 *x509);*/
diff --git a/src/common/text.c b/src/common/text.c
new file mode 100644
index 00000000..a2198517
--- /dev/null
+++ b/src/common/text.c
@@ -0,0 +1,2309 @@
+/* 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <time.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include "xchat.h"
+#include <glib/ghash.h>
+#include "cfgfiles.h"
+#include "chanopt.h"
+#include "plugin.h"
+#include "fe.h"
+#include "server.h"
+#include "util.h"
+#include "outbound.h"
+#include "xchatc.h"
+#include "text.h"
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+struct pevt_stage1
+{
+	int len;
+	char *data;
+	struct pevt_stage1 *next;
+};
+
+
+static void mkdir_p (char *dir);
+static char *log_create_filename (char *channame);
+
+
+static char *
+scrollback_get_filename (session *sess, char *buf, int max)
+{
+	char *net, *chan;
+
+	net = server_get_network (sess->server, FALSE);
+	if (!net)
+		return NULL;
+
+	snprintf (buf, max, "%s/scrollback/%s/%s.txt", get_xdir_fs (), net, "");
+	mkdir_p (buf);
+
+	chan = log_create_filename (sess->channel);
+	snprintf (buf, max, "%s/scrollback/%s/%s.txt", get_xdir_fs (), net, chan);
+	free (chan);
+
+	return buf;
+}
+
+#if 0
+
+static void
+scrollback_unlock (session *sess)
+{
+	char buf[1024];
+
+	if (scrollback_get_filename (sess, buf, sizeof (buf) - 6) == NULL)
+		return;
+
+	strcat (buf, ".lock");
+	unlink (buf);
+}
+
+static gboolean
+scrollback_lock (session *sess)
+{
+	char buf[1024];
+	int fh;
+
+	if (scrollback_get_filename (sess, buf, sizeof (buf) - 6) == NULL)
+		return FALSE;
+
+	strcat (buf, ".lock");
+
+	if (access (buf, F_OK) == 0)
+		return FALSE;	/* can't get lock */
+
+	fh = open (buf, O_CREAT | O_TRUNC | O_APPEND | O_WRONLY, 0644);
+	if (fh == -1)
+		return FALSE;
+
+	return TRUE;
+}
+
+#endif
+
+void
+scrollback_close (session *sess)
+{
+	if (sess->scrollfd != -1)
+	{
+		close (sess->scrollfd);
+		sess->scrollfd = -1;
+	}
+}
+
+static char *
+file_to_buffer (char *file, int *len)
+{
+	int fh;
+	char *buf;
+	struct stat st;
+
+	fh = open (file, O_RDONLY | OFLAGS);
+	if (fh == -1)
+		return NULL;
+
+	fstat (fh, &st);
+
+	buf = malloc (st.st_size);
+	if (!buf)
+	{
+		close (fh);
+		return NULL;
+	}
+
+	if (read (fh, buf, st.st_size) != st.st_size)
+	{
+		free (buf);
+		close (fh);
+		return NULL;
+	}
+
+	*len = st.st_size;
+	close (fh);
+	return buf;
+}
+
+/* shrink the file to roughly prefs.max_lines */
+
+static void
+scrollback_shrink (session *sess)
+{
+	char file[1024];
+	char *buf;
+	int fh;
+	int lines;
+	int line;
+	int len;
+	char *p;
+
+	scrollback_close (sess);
+	sess->scrollwritten = 0;
+	lines = 0;
+
+	if (scrollback_get_filename (sess, file, sizeof (file)) == NULL)
+		return;
+
+	buf = file_to_buffer (file, &len);
+	if (!buf)
+		return;
+
+	/* count all lines */
+	p = buf;
+	while (p != buf + len)
+	{
+		if (*p == '\n')
+			lines++;
+		p++;
+	}
+
+	fh = open (file, O_CREAT | O_TRUNC | O_APPEND | O_WRONLY, 0644);
+	if (fh == -1)
+	{
+		free (buf);
+		return;
+	}
+
+	line = 0;
+	p = buf;
+	while (p != buf + len)
+	{
+		if (*p == '\n')
+		{
+			line++;
+			if (line >= lines - prefs.max_lines &&
+				 p + 1 != buf + len)
+			{
+				p++;
+				write (fh, p, len - (p - buf));
+				break;
+			}
+		}
+		p++;
+	}
+
+	close (fh);
+	free (buf);
+}
+
+static void
+scrollback_save (session *sess, char *text)
+{
+	char buf[512 * 4];
+	time_t stamp;
+	int len;
+
+	if (sess->type == SESS_SERVER)
+		return;
+
+	if (sess->text_scrollback == SET_DEFAULT)
+	{
+		if (!prefs.text_replay)
+			return;
+	}
+	else
+	{
+		if (sess->text_scrollback != SET_ON)
+			return;
+	}
+
+	if (sess->scrollfd == -1)
+	{
+		if (scrollback_get_filename (sess, buf, sizeof (buf)) == NULL)
+			return;
+
+		sess->scrollfd = open (buf, O_CREAT | O_APPEND | O_WRONLY, 0644);
+		if (sess->scrollfd == -1)
+			return;
+	}
+
+	stamp = time (0);
+	if (sizeof (stamp) == 4)	/* gcc will optimize one of these out */
+		write (sess->scrollfd, buf, snprintf (buf, sizeof (buf), "T %d ", (int)stamp));
+	else
+		write (sess->scrollfd, buf, snprintf (buf, sizeof (buf), "T %"G_GINT64_FORMAT" ", (gint64)stamp));
+
+	len = strlen (text);
+	write (sess->scrollfd, text, len);
+	if (len && text[len - 1] != '\n')
+		write (sess->scrollfd, "\n", 1);
+
+	sess->scrollwritten++;
+
+	if ((sess->scrollwritten * 2 > prefs.max_lines && prefs.max_lines > 0) ||
+       sess->scrollwritten > 32000)
+		scrollback_shrink (sess);
+}
+
+void
+scrollback_load (session *sess)
+{
+	int fh;
+	char buf[512 * 4];
+	char *text;
+	time_t stamp;
+	int lines;
+	char *map, *end_map;
+	struct stat statbuf;
+	const char *begin, *eol;
+
+	if (sess->text_scrollback == SET_DEFAULT)
+	{
+		if (!prefs.text_replay)
+			return;
+	}
+	else
+	{
+		if (sess->text_scrollback != SET_ON)
+			return;
+	}
+
+	if (scrollback_get_filename (sess, buf, sizeof (buf)) == NULL)
+		return;
+
+	fh = open (buf, O_RDONLY | OFLAGS);
+	if (fh == -1)
+		return;
+
+	if (fstat (fh, &statbuf) < 0)
+		return;
+
+	map = mmap (NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, fh, 0);
+	if (map == MAP_FAILED)
+		return;
+
+	end_map = map + statbuf.st_size;
+	
+	lines = 0;
+	begin = map;
+	while (begin < end_map)
+	{
+		int n_bytes;
+
+		eol = memchr (begin, '\n', end_map - begin);
+
+		if (!eol)
+			eol = end_map;
+
+		n_bytes = MIN (eol - begin, sizeof (buf) - 1);
+		
+		strncpy (buf, begin, n_bytes);
+
+		buf[n_bytes] = 0;
+		
+		if (buf[0] == 'T')
+		{
+			if (sizeof (time_t) == 4)
+				stamp = strtoul (buf + 2, NULL, 10);
+			else
+				stamp = strtoull (buf + 2, NULL, 10); /* just incase time_t is 64 bits */
+			text = strchr (buf + 3, ' ');
+			if (text)
+			{
+				text = strip_color (text + 1, -1, STRIP_COLOR);
+				fe_print_text (sess, text, stamp);
+				g_free (text);
+			}
+			lines++;
+		}
+
+		begin = eol + 1;
+	}
+
+	sess->scrollwritten = lines;
+
+	if (lines)
+	{
+		text = ctime (&stamp);
+		text[24] = 0;	/* get rid of the \n */
+		snprintf (buf, sizeof (buf), "\n*\t%s %s\n\n", _("Loaded log from"), text);
+		fe_print_text (sess, buf, 0);
+		/*EMIT_SIGNAL (XP_TE_GENMSG, sess, "*", buf, NULL, NULL, NULL, 0);*/
+	}
+
+	munmap (map, statbuf.st_size);
+	close (fh);
+}
+
+void
+log_close (session *sess)
+{
+	char obuf[512];
+	time_t currenttime;
+
+	if (sess->logfd != -1)
+	{
+		currenttime = time (NULL);
+		write (sess->logfd, obuf,
+			 snprintf (obuf, sizeof (obuf) - 1, _("**** ENDING LOGGING AT %s\n"),
+						  ctime (&currenttime)));
+		close (sess->logfd);
+		sess->logfd = -1;
+	}
+}
+
+static void
+mkdir_p (char *dir)	/* like "mkdir -p" from a shell, FS encoding */
+{
+	char *start = dir;
+
+	/* the whole thing already exists? */
+	if (access (dir, F_OK) == 0)
+		return;
+
+	while (*dir)
+	{
+#ifdef WIN32
+		if (dir != start && (*dir == '/' || *dir == '\\'))
+#else
+		if (dir != start && *dir == '/')
+#endif
+		{
+			*dir = 0;
+#ifdef WIN32
+			mkdir (start);
+#else
+			mkdir (start, S_IRUSR | S_IWUSR | S_IXUSR);
+#endif
+			*dir = '/';
+		}
+		dir++;
+	}
+}
+
+static char *
+log_create_filename (char *channame)
+{
+	char *tmp, *ret;
+	int mbl;
+
+	ret = tmp = strdup (channame);
+	while (*tmp)
+	{
+		mbl = g_utf8_skip[((unsigned char *)tmp)[0]];
+		if (mbl == 1)
+		{
+#ifndef WIN32
+			*tmp = rfc_tolower (*tmp);
+			if (*tmp == '/')
+#else
+			/* win32 can't handle filenames with \|/><:"*? characters */
+			if (*tmp == '\\' || *tmp == '|' || *tmp == '/' ||
+				 *tmp == '>'  || *tmp == '<' || *tmp == ':' ||
+				 *tmp == '\"' || *tmp == '*' || *tmp == '?')
+#endif
+				*tmp = '_';
+		}
+		tmp += mbl;
+	}
+
+	return ret;
+}
+
+/* like strcpy, but % turns into %% */
+
+static char *
+log_escape_strcpy (char *dest, char *src, char *end)
+{
+	while (*src)
+	{
+		*dest = *src;
+		if (dest + 1 == end)
+			break;
+		dest++;
+		src++;
+
+		if (*src == '%')
+		{
+			if (dest + 1 == end)
+				break;
+			dest[0] = '%';
+			dest++;
+		}
+	}
+
+	dest[0] = 0;
+	return dest - 1;
+}
+
+/* substitutes %c %n %s into buffer */
+
+static void
+log_insert_vars (char *buf, int bufsize, char *fmt, char *c, char *n, char *s)
+{
+	char *end = buf + bufsize;
+
+	while (1)
+	{
+		switch (fmt[0])
+		{
+		case 0:
+			buf[0] = 0;
+			return;
+
+		case '%':
+			fmt++;
+			switch (fmt[0])
+			{
+			case 'c':
+				buf = log_escape_strcpy (buf, c, end);
+				break;
+			case 'n':
+				buf = log_escape_strcpy (buf, n, end);
+				break;
+			case 's':
+				buf = log_escape_strcpy (buf, s, end);
+				break;
+			default:
+				buf[0] = '%';
+				buf++;
+				buf[0] = fmt[0];
+				break;
+			}
+			break;
+
+		default:
+			buf[0] = fmt[0];
+		}
+		fmt++;
+		buf++;
+		/* doesn't fit? */
+		if (buf == end)
+		{
+			buf[-1] = 0;
+			return;
+		}
+	}
+}
+
+static char *
+log_create_pathname (char *servname, char *channame, char *netname)
+{
+	char fname[384];
+	char fnametime[384];
+	char *fs;
+	struct tm *tm;
+	time_t now;
+
+	if (!netname)
+		netname = "NETWORK";
+
+	/* first, everything is in UTF-8 */
+	if (!rfc_casecmp (channame, servname))
+		channame = strdup ("server");
+	else
+		channame = log_create_filename (channame);
+	log_insert_vars (fname, sizeof (fname), prefs.logmask, channame, netname, servname);
+	free (channame);
+
+	/* insert time/date */
+	now = time (NULL);
+	tm = localtime (&now);
+	strftime (fnametime, sizeof (fnametime), fname, tm);
+
+	/* create final path/filename */
+#ifdef WIN32
+	if (fnametime[0] == '/' || (fnametime[0] >= 'A' && fnametime[1] == ':'))
+#else
+	if (fnametime[0] == '/')	/* is it fullpath already? */
+#endif
+		snprintf (fname, sizeof (fname), "%s", fnametime);
+	else
+		snprintf (fname, sizeof (fname), "%s/xchatlogs/%s", get_xdir_utf8 (), fnametime);
+
+	/* now we need it in FileSystem encoding */
+	fs = xchat_filename_from_utf8 (fname, -1, 0, 0, 0);
+
+	/* create all the subdirectories */
+	if (fs)
+		mkdir_p (fs);
+
+	return fs;
+}
+
+static int
+log_open_file (char *servname, char *channame, char *netname)
+{
+	char buf[512];
+	int fd;
+	char *file;
+	time_t currenttime;
+
+	file = log_create_pathname (servname, channame, netname);
+	if (!file)
+		return -1;
+
+#ifdef WIN32
+	fd = open (file, O_CREAT | O_APPEND | O_WRONLY, S_IREAD|S_IWRITE);
+#else
+	fd = open (file, O_CREAT | O_APPEND | O_WRONLY, 0644);
+#endif
+	g_free (file);
+
+	if (fd == -1)
+		return -1;
+	currenttime = time (NULL);
+	write (fd, buf,
+			 snprintf (buf, sizeof (buf), _("**** BEGIN LOGGING AT %s\n"),
+						  ctime (&currenttime)));
+
+	return fd;
+}
+
+static void
+log_open (session *sess)
+{
+	static gboolean log_error = FALSE;
+
+	log_close (sess);
+	sess->logfd = log_open_file (sess->server->servername, sess->channel,
+										  server_get_network (sess->server, FALSE));
+
+	if (!log_error && sess->logfd == -1)
+	{
+		char message[512];
+		snprintf (message, sizeof (message),
+					_("* Can't open log file(s) for writing. Check the\n" \
+					  "  permissions on %s/xchatlogs"), get_xdir_utf8 ());
+		fe_message (message, FE_MSG_WAIT | FE_MSG_ERROR);
+
+		log_error = TRUE;
+	}
+}
+
+void
+log_open_or_close (session *sess)
+{
+	if (sess->text_logging == SET_DEFAULT)
+	{
+		if (prefs.logging)
+			log_open (sess);
+		else
+			log_close (sess);
+	}
+	else
+	{
+		if (sess->text_logging)
+			log_open (sess);
+		else
+			log_close (sess);
+	}
+}
+
+int
+get_stamp_str (char *fmt, time_t tim, char **ret)
+{
+	char *loc = NULL;
+	char dest[128];
+	gsize len;
+
+	/* strftime wants the format string in LOCALE! */
+	if (!prefs.utf8_locale)
+	{
+		const gchar *charset;
+
+		g_get_charset (&charset);
+		loc = g_convert_with_fallback (fmt, -1, charset, "UTF-8", "?", 0, 0, 0);
+		if (loc)
+			fmt = loc;
+	}
+
+	len = strftime (dest, sizeof (dest), fmt, localtime (&tim));
+	if (len)
+	{
+		if (prefs.utf8_locale)
+			*ret = g_strdup (dest);
+		else
+			*ret = g_locale_to_utf8 (dest, len, 0, &len, 0);
+	}
+
+	if (loc)
+		g_free (loc);
+
+	return len;
+}
+
+static void
+log_write (session *sess, char *text)
+{
+	char *temp;
+	char *stamp;
+	char *file;
+	int len;
+
+	if (sess->text_logging == SET_DEFAULT)
+	{
+		if (!prefs.logging)
+			return;
+	}
+	else
+	{
+		if (sess->text_logging != SET_ON)
+			return;
+	}
+
+	if (sess->logfd == -1)
+		log_open (sess);
+
+	/* change to a different log file? */
+	file = log_create_pathname (sess->server->servername, sess->channel,
+										 server_get_network (sess->server, FALSE));
+	if (file)
+	{
+		if (access (file, F_OK) != 0)
+		{
+			close (sess->logfd);
+			sess->logfd = log_open_file (sess->server->servername, sess->channel,
+												  server_get_network (sess->server, FALSE));
+		}
+		g_free (file);
+	}
+
+	if (prefs.timestamp_logs)
+	{
+		len = get_stamp_str (prefs.timestamp_log_format, time (0), &stamp);
+		if (len)
+		{
+			write (sess->logfd, stamp, len);
+			g_free (stamp);
+		}
+	}
+	temp = strip_color (text, -1, STRIP_ALL);
+	len = strlen (temp);
+	write (sess->logfd, temp, len);
+	/* lots of scripts/plugins print without a \n at the end */
+	if (temp[len - 1] != '\n')
+		write (sess->logfd, "\n", 1);	/* emulate what xtext would display */
+	g_free (temp);
+}
+
+/* converts a CP1252/ISO-8859-1(5) hybrid to UTF-8                           */
+/* Features: 1. It never fails, all 00-FF chars are converted to valid UTF-8 */
+/*           2. Uses CP1252 in the range 80-9f because ISO doesn't have any- */
+/*              thing useful in this range and it helps us receive from mIRC */
+/*           3. The five undefined chars in CP1252 80-9f are replaced with   */
+/*              ISO-8859-15 control codes.                                   */
+/*           4. Handles 0xa4 as a Euro symbol ala ISO-8859-15.               */
+/*           5. Uses ISO-8859-1 (which matches CP1252) for everything else.  */
+/*           6. This routine measured 3x faster than g_convert :)            */
+
+static unsigned char *
+iso_8859_1_to_utf8 (unsigned char *text, int len, gsize *bytes_written)
+{
+	unsigned int idx;
+	unsigned char *res, *output;
+	static const unsigned short lowtable[] = /* 74 byte table for 80-a4 */
+	{
+	/* compressed utf-8 table: if the first byte's 0x20 bit is set, it
+	   indicates a 2-byte utf-8 sequence, otherwise prepend a 0xe2. */
+		0x82ac, /* 80 Euro. CP1252 from here on... */
+		0xe281, /* 81 NA */
+		0x809a, /* 82 */
+		0xe692, /* 83 */
+		0x809e, /* 84 */
+		0x80a6, /* 85 */
+		0x80a0, /* 86 */
+		0x80a1, /* 87 */
+		0xeb86, /* 88 */
+		0x80b0, /* 89 */
+		0xe5a0, /* 8a */
+		0x80b9, /* 8b */
+		0xe592, /* 8c */
+		0xe28d, /* 8d NA */
+		0xe5bd, /* 8e */
+		0xe28f, /* 8f NA */
+		0xe290, /* 90 NA */
+		0x8098, /* 91 */
+		0x8099, /* 92 */
+		0x809c, /* 93 */
+		0x809d, /* 94 */
+		0x80a2, /* 95 */
+		0x8093, /* 96 */
+		0x8094, /* 97 */
+		0xeb9c, /* 98 */
+		0x84a2, /* 99 */
+		0xe5a1, /* 9a */
+		0x80ba, /* 9b */
+		0xe593, /* 9c */
+		0xe29d, /* 9d NA */
+		0xe5be, /* 9e */
+		0xe5b8, /* 9f */
+		0xe2a0, /* a0 */
+		0xe2a1, /* a1 */
+		0xe2a2, /* a2 */
+		0xe2a3, /* a3 */
+		0x82ac  /* a4 ISO-8859-15 Euro. */
+	};
+
+	if (len == -1)
+		len = strlen (text);
+
+	/* worst case scenario: every byte turns into 3 bytes */
+	res = output = g_malloc ((len * 3) + 1);
+	if (!output)
+		return NULL;
+
+	while (len)
+	{
+		if (G_LIKELY (*text < 0x80))
+		{
+			*output = *text;	/* ascii maps directly */
+		}
+		else if (*text <= 0xa4)	/* 80-a4 use a lookup table */
+		{
+			idx = *text - 0x80;
+			if (lowtable[idx] & 0x2000)
+			{
+				*output++ = (lowtable[idx] >> 8) & 0xdf; /* 2 byte utf-8 */
+				*output = lowtable[idx] & 0xff;
+			}
+			else
+			{
+				*output++ = 0xe2;	/* 3 byte utf-8 */
+				*output++ = (lowtable[idx] >> 8) & 0xff;
+				*output = lowtable[idx] & 0xff;
+			}
+		}
+		else if (*text < 0xc0)
+		{
+			*output++ = 0xc2;
+			*output = *text;
+		}
+		else
+		{
+			*output++ = 0xc3;
+			*output = *text - 0x40;
+		}
+		output++;
+		text++;
+		len--;
+	}
+	*output = 0;	/* terminate */
+	*bytes_written = output - res;
+
+	return res;
+}
+
+char *
+text_validate (char **text, int *len)
+{
+	char *utf;
+	gsize utf_len;
+
+	/* valid utf8? */
+	if (g_utf8_validate (*text, *len, 0))
+		return NULL;
+
+#ifdef WIN32
+	if (GetACP () == 1252) /* our routine is better than iconv's 1252 */
+#else
+	if (prefs.utf8_locale)
+#endif
+		/* fallback to iso-8859-1 */
+		utf = iso_8859_1_to_utf8 (*text, *len, &utf_len);
+	else
+	{
+		/* fallback to locale */
+		utf = g_locale_to_utf8 (*text, *len, 0, &utf_len, NULL);
+		if (!utf)
+			utf = iso_8859_1_to_utf8 (*text, *len, &utf_len);
+	}
+
+	if (!utf) 
+	{
+		*text = g_strdup ("%INVALID%");
+		*len = 9;
+	} else
+	{
+		*text = utf;
+		*len = utf_len;
+	}
+
+	return utf;
+}
+
+void
+PrintText (session *sess, char *text)
+{
+	char *conv;
+
+	if (!sess)
+	{
+		if (!sess_list)
+			return;
+		sess = (session *) sess_list->data;
+	}
+
+	/* make sure it's valid utf8 */
+	if (text[0] == 0)
+	{
+		text = "\n";
+		conv = NULL;
+	} else
+	{
+		int len = -1;
+		conv = text_validate ((char **)&text, &len);
+	}
+
+	log_write (sess, text);
+	scrollback_save (sess, text);
+	fe_print_text (sess, text, 0);
+
+	if (conv)
+		g_free (conv);
+}
+
+void
+PrintTextf (session *sess, char *format, ...)
+{
+	va_list args;
+	char *buf;
+
+	va_start (args, format);
+	buf = g_strdup_vprintf (format, args);
+	va_end (args);
+
+	PrintText (sess, buf);
+	g_free (buf);
+}
+
+/* Print Events stuff here --AGL */
+
+/* Consider the following a NOTES file:
+
+   The main upshot of this is:
+   * Plugins and Perl scripts (when I get round to signaling perl.c) can intercept text events and do what they like
+   * The default text engine can be config'ed
+
+   By default it should appear *exactly* the same (I'm working hard not to change the default style) but if you go into Settings->Edit Event Texts you can change the text's. The format is thus:
+
+   The normal %Cx (color) and %B (bold) etc work
+
+   $x is replaced with the data in var x (e.g. $1 is often the nick)
+
+   $axxx is replace with a single byte of value xxx (in base 10)
+
+   AGL (990507)
+ */
+
+/* These lists are thus:
+   pntevts_text[] are the strings the user sees (WITH %x etc)
+   pntevts[] are the data strings with \000 etc
+ */
+
+/* To add a new event:
+
+   Think up a name (like "Join")
+   Make up a pevt_name_help struct
+	Add an entry to textevents.in
+	Type: make textevents
+ */
+
+/* Internals:
+
+   On startup ~/.xchat/printevents.conf is loaded if it doesn't exist the
+   defaults are loaded. Any missing events are filled from defaults.
+   Each event is parsed by pevt_build_string and a binary output is produced
+   which looks like:
+
+   (byte) value: 0 = {
+   (int) numbers of bytes
+   (char []) that number of byte to be memcpy'ed into the buffer
+   }
+   1 =
+   (byte) number of varable to insert
+   2 = end of buffer
+
+   Each XP_TE_* signal is hard coded to call text_emit which calls
+   display_event which decodes the data
+
+   This means that this system *should be faster* than snprintf because
+   it always 'knows' that format of the string (basically is preparses much
+   of the work)
+
+   --AGL
+ */
+
+char *pntevts_text[NUM_XP];
+char *pntevts[NUM_XP];
+
+#define pevt_generic_none_help NULL
+
+static char * const pevt_genmsg_help[] = {
+	N_("Left message"),
+	N_("Right message"),
+};
+
+static char * const pevt_join_help[] = {
+	N_("The nick of the joining person"),
+	N_("The channel being joined"),
+	N_("The host of the person"),
+};
+
+static char * const pevt_chanaction_help[] = {
+	N_("Nickname"),
+	N_("The action"),
+	N_("Mode char"),
+	N_("Identified text"),
+};
+
+static char * const pevt_chanmsg_help[] = {
+	N_("Nickname"),
+	N_("The text"),
+	N_("Mode char"),
+	N_("Identified text"),
+};
+
+static char * const pevt_privmsg_help[] = {
+	N_("Nickname"),
+	N_("The message"),
+	N_("Identified text")
+};
+
+static char * const pevt_changenick_help[] = {
+	N_("Old nickname"),
+	N_("New nickname"),
+};
+
+static char * const pevt_newtopic_help[] = {
+	N_("Nick of person who changed the topic"),
+	N_("Topic"),
+	N_("Channel"),
+};
+
+static char * const pevt_topic_help[] = {
+	N_("Channel"),
+	N_("Topic"),
+};
+
+static char * const pevt_kick_help[] = {
+	N_("The nickname of the kicker"),
+	N_("The person being kicked"),
+	N_("The channel"),
+	N_("The reason"),
+};
+
+static char * const pevt_part_help[] = {
+	N_("The nick of the person leaving"),
+	N_("The host of the person"),
+	N_("The channel"),
+};
+
+static char * const pevt_chandate_help[] = {
+	N_("The channel"),
+	N_("The time"),
+};
+
+static char * const pevt_topicdate_help[] = {
+	N_("The channel"),
+	N_("The creator"),
+	N_("The time"),
+};
+
+static char * const pevt_quit_help[] = {
+	N_("Nick"),
+	N_("Reason"),
+	N_("Host"),
+};
+
+static char * const pevt_pingrep_help[] = {
+	N_("Who it's from"),
+	N_("The time in x.x format (see below)"),
+};
+
+static char * const pevt_notice_help[] = {
+	N_("Who it's from"),
+	N_("The message"),
+};
+
+static char * const pevt_channotice_help[] = {
+	N_("Who it's from"),
+	N_("The Channel it's going to"),
+	N_("The message"),
+};
+
+static char * const pevt_uchangenick_help[] = {
+	N_("Old nickname"),
+	N_("New nickname"),
+};
+
+static char * const pevt_ukick_help[] = {
+	N_("The person being kicked"),
+	N_("The channel"),
+	N_("The nickname of the kicker"),
+	N_("The reason"),
+};
+
+static char * const pevt_partreason_help[] = {
+	N_("The nick of the person leaving"),
+	N_("The host of the person"),
+	N_("The channel"),
+	N_("The reason"),
+};
+
+static char * const pevt_ctcpsnd_help[] = {
+	N_("The sound"),
+	N_("The nick of the person"),
+	N_("The channel"),
+};
+
+static char * const pevt_ctcpgen_help[] = {
+	N_("The CTCP event"),
+	N_("The nick of the person"),
+};
+
+static char * const pevt_ctcpgenc_help[] = {
+	N_("The CTCP event"),
+	N_("The nick of the person"),
+	N_("The Channel it's going to"),
+};
+
+static char * const pevt_chansetkey_help[] = {
+	N_("The nick of the person who set the key"),
+	N_("The key"),
+};
+
+static char * const pevt_chansetlimit_help[] = {
+	N_("The nick of the person who set the limit"),
+	N_("The limit"),
+};
+
+static char * const pevt_chanop_help[] = {
+	N_("The nick of the person who did the op'ing"),
+	N_("The nick of the person who has been op'ed"),
+};
+
+static char * const pevt_chanhop_help[] = {
+	N_("The nick of the person who has been halfop'ed"),
+	N_("The nick of the person who did the halfop'ing"),
+};
+
+static char * const pevt_chanvoice_help[] = {
+	N_("The nick of the person who did the voice'ing"),
+	N_("The nick of the person who has been voice'ed"),
+};
+
+static char * const pevt_chanban_help[] = {
+	N_("The nick of the person who did the banning"),
+	N_("The ban mask"),
+};
+
+static char * const pevt_chanrmkey_help[] = {
+	N_("The nick who removed the key"),
+};
+
+static char * const pevt_chanrmlimit_help[] = {
+	N_("The nick who removed the limit"),
+};
+
+static char * const pevt_chandeop_help[] = {
+	N_("The nick of the person of did the deop'ing"),
+	N_("The nick of the person who has been deop'ed"),
+};
+static char * const pevt_chandehop_help[] = {
+	N_("The nick of the person of did the dehalfop'ing"),
+	N_("The nick of the person who has been dehalfop'ed"),
+};
+
+static char * const pevt_chandevoice_help[] = {
+	N_("The nick of the person of did the devoice'ing"),
+	N_("The nick of the person who has been devoice'ed"),
+};
+
+static char * const pevt_chanunban_help[] = {
+	N_("The nick of the person of did the unban'ing"),
+	N_("The ban mask"),
+};
+
+static char * const pevt_chanexempt_help[] = {
+	N_("The nick of the person who did the exempt"),
+	N_("The exempt mask"),
+};
+
+static char * const pevt_chanrmexempt_help[] = {
+	N_("The nick of the person removed the exempt"),
+	N_("The exempt mask"),
+};
+
+static char * const pevt_chaninvite_help[] = {
+	N_("The nick of the person who did the invite"),
+	N_("The invite mask"),
+};
+
+static char * const pevt_chanrminvite_help[] = {
+	N_("The nick of the person removed the invite"),
+	N_("The invite mask"),
+};
+
+static char * const pevt_chanmodegen_help[] = {
+	N_("The nick of the person setting the mode"),
+	N_("The mode's sign (+/-)"),
+	N_("The mode letter"),
+	N_("The channel it's being set on"),
+};
+
+static char * const pevt_whois1_help[] = {
+	N_("Nickname"),
+	N_("Username"),
+	N_("Host"),
+	N_("Full name"),
+};
+
+static char * const pevt_whois2_help[] = {
+	N_("Nickname"),
+	N_("Channel Membership/\"is an IRC operator\""),
+};
+
+static char * const pevt_whois3_help[] = {
+	N_("Nickname"),
+	N_("Server Information"),
+};
+
+static char * const pevt_whois4_help[] = {
+	N_("Nickname"),
+	N_("Idle time"),
+};
+
+static char * const pevt_whois4t_help[] = {
+	N_("Nickname"),
+	N_("Idle time"),
+	N_("Signon time"),
+};
+
+static char * const pevt_whois5_help[] = {
+	N_("Nickname"),
+	N_("Away reason"),
+};
+
+static char * const pevt_whois6_help[] = {
+	N_("Nickname"),
+};
+
+static char * const pevt_whoisid_help[] = {
+	N_("Nickname"),
+	N_("Message"),
+	"Numeric"
+};
+
+static char * const pevt_whoisauth_help[] = {
+	N_("Nickname"),
+	N_("Message"),
+	N_("Account"),
+};
+
+static char * const pevt_whoisrealhost_help[] = {
+	N_("Nickname"),
+	N_("Real user@host"),
+	N_("Real IP"),
+	N_("Message"),
+};
+
+static char * const pevt_generic_channel_help[] = {
+	N_("Channel Name"),
+};
+
+static char * const pevt_servertext_help[] = {
+	N_("Text"),
+	N_("Server Name"),
+	N_("Raw Numeric or Identifier")
+};
+
+static char * const pevt_sslmessage_help[] = {
+	N_("Text"),
+	N_("Server Name")
+};
+
+static char * const pevt_invited_help[] = {
+	N_("Channel Name"),
+	N_("Nick of person who invited you"),
+	N_("Server Name"),
+};
+
+static char * const pevt_usersonchan_help[] = {
+	N_("Channel Name"),
+	N_("Users"),
+};
+
+static char * const pevt_nickclash_help[] = {
+	N_("Nickname in use"),
+	N_("Nick being tried"),
+};
+
+static char * const pevt_connfail_help[] = {
+	N_("Error"),
+};
+
+static char * const pevt_connect_help[] = {
+	N_("Host"),
+	N_("IP"),
+	N_("Port"),
+};
+
+static char * const pevt_sconnect_help[] = {
+	"PID"
+};
+
+static char * const pevt_generic_nick_help[] = {
+	N_("Nickname"),
+	N_("Server Name"),
+	N_("Network")
+};
+
+static char * const pevt_chanmodes_help[] = {
+	N_("Channel Name"),
+	N_("Modes string"),
+};
+
+static char * const pevt_rawmodes_help[] = {
+	N_("Nickname"),
+	N_("Modes string"),
+};
+
+static char * const pevt_kill_help[] = {
+	N_("Nickname"),
+	N_("Reason"),
+};
+
+static char * const pevt_dccchaterr_help[] = {
+	N_("Nickname"),
+	N_("IP address"),
+	N_("Port"),
+	N_("Error"),
+};
+
+static char * const pevt_dccstall_help[] = {
+	N_("DCC Type"),
+	N_("Filename"),
+	N_("Nickname"),
+};
+
+static char * const pevt_generic_file_help[] = {
+	N_("Filename"),
+	N_("Error"),
+};
+
+static char * const pevt_dccrecverr_help[] = {
+	N_("Filename"),
+	N_("Destination filename"),
+	N_("Nickname"),
+	N_("Error"),
+};
+
+static char * const pevt_dccrecvcomp_help[] = {
+	N_("Filename"),
+	N_("Destination filename"),
+	N_("Nickname"),
+	N_("CPS"),
+};
+
+static char * const pevt_dccconfail_help[] = {
+	N_("DCC Type"),
+	N_("Nickname"),
+	N_("Error"),
+};
+
+static char * const pevt_dccchatcon_help[] = {
+	N_("Nickname"),
+	N_("IP address"),
+};
+
+static char * const pevt_dcccon_help[] = {
+	N_("Nickname"),
+	N_("IP address"),
+	N_("Filename"),
+};
+
+static char * const pevt_dccsendfail_help[] = {
+	N_("Filename"),
+	N_("Nickname"),
+	N_("Error"),
+};
+
+static char * const pevt_dccsendcomp_help[] = {
+	N_("Filename"),
+	N_("Nickname"),
+	N_("CPS"),
+};
+
+static char * const pevt_dccoffer_help[] = {
+	N_("Filename"),
+	N_("Nickname"),
+	N_("Pathname"),
+};
+
+static char * const pevt_dccfileabort_help[] = {
+	N_("Nickname"),
+	N_("Filename")
+};
+
+static char * const pevt_dccchatabort_help[] = {
+	N_("Nickname"),
+};
+
+static char * const pevt_dccresumeoffer_help[] = {
+	N_("Nickname"),
+	N_("Filename"),
+	N_("Position"),
+};
+
+static char * const pevt_dccsendoffer_help[] = {
+	N_("Nickname"),
+	N_("Filename"),
+	N_("Size"),
+	N_("IP address"),
+};
+
+static char * const pevt_dccgenericoffer_help[] = {
+	N_("DCC String"),
+	N_("Nickname"),
+};
+
+static char * const pevt_notifynumber_help[] = {
+	N_("Number of notify items"),
+};
+
+static char * const pevt_serverlookup_help[] = {
+	N_("Server Name"),
+};
+
+static char * const pevt_servererror_help[] = {
+	N_("Text"),
+};
+
+static char * const pevt_foundip_help[] = {
+	N_("IP"),
+};
+
+static char * const pevt_dccrename_help[] = {
+	N_("Old Filename"),
+	N_("New Filename"),
+};
+
+static char * const pevt_ctcpsend_help[] = {
+	N_("Receiver"),
+	N_("Message"),
+};
+
+static char * const pevt_ignoreaddremove_help[] = {
+	N_("Hostmask"),
+};
+
+static char * const pevt_resolvinguser_help[] = {
+	N_("Nickname"),
+	N_("Hostname"),
+};
+
+static char * const pevt_malformed_help[] = {
+	N_("Nickname"),
+	N_("The Packet"),
+};
+
+static char * const pevt_pingtimeout_help[] = {
+	N_("Seconds"),
+};
+
+static char * const pevt_uinvite_help[] = {
+	N_("Nick of person who have been invited"),
+	N_("Channel Name"),
+	N_("Server Name"),
+};
+
+static char * const pevt_banlist_help[] = {
+	N_("Channel"),
+	N_("Banmask"),
+	N_("Who set the ban"),
+	N_("Ban time"),
+};
+
+static char * const pevt_discon_help[] = {
+	N_("Error"),
+};
+
+#include "textevents.h"
+
+static void
+pevent_load_defaults ()
+{
+	int i;
+
+	for (i = 0; i < NUM_XP; i++)
+	{
+		if (pntevts_text[i])
+			free (pntevts_text[i]);
+
+		/* make-te.c sets this 128 flag (DON'T call gettext() flag) */
+		if (te[i].num_args & 128)
+			pntevts_text[i] = strdup (te[i].def);
+		else
+			pntevts_text[i] = strdup (_(te[i].def));
+	}
+}
+
+void
+pevent_make_pntevts ()
+{
+	int i, m;
+	char out[1024];
+
+	for (i = 0; i < NUM_XP; i++)
+	{
+		if (pntevts[i] != NULL)
+			free (pntevts[i]);
+		if (pevt_build_string (pntevts_text[i], &(pntevts[i]), &m) != 0)
+		{
+			snprintf (out, sizeof (out),
+						 _("Error parsing event %s.\nLoading default."), te[i].name);
+			fe_message (out, FE_MSG_WARN);
+			free (pntevts_text[i]);
+			/* make-te.c sets this 128 flag (DON'T call gettext() flag) */
+			if (te[i].num_args & 128)
+				pntevts_text[i] = strdup (te[i].def);
+			else
+				pntevts_text[i] = strdup (_(te[i].def));
+			if (pevt_build_string (pntevts_text[i], &(pntevts[i]), &m) != 0)
+			{
+				fprintf (stderr,
+							"XChat CRITICAL *** default event text failed to build!\n");
+				abort ();
+			}
+		}
+	}
+}
+
+/* Loading happens at 2 levels:
+   1) File is read into blocks
+   2) Pe block is parsed and loaded
+
+   --AGL */
+
+/* Better hope you pass good args.. --AGL */
+
+static void
+pevent_trigger_load (int *i_penum, char **i_text, char **i_snd)
+{
+	int penum = *i_penum, len;
+	char *text = *i_text, *snd = *i_snd;
+
+	if (penum != -1 && text != NULL)
+	{
+		len = strlen (text) + 1;
+		if (pntevts_text[penum])
+			free (pntevts_text[penum]);
+		pntevts_text[penum] = malloc (len);
+		memcpy (pntevts_text[penum], text, len);
+	}
+
+	if (text)
+		free (text);
+	if (snd)
+		free (snd);
+	*i_text = NULL;
+	*i_snd = NULL;
+	*i_penum = 0;
+}
+
+static int
+pevent_find (char *name, int *i_i)
+{
+	int i = *i_i, j;
+
+	j = i + 1;
+	while (1)
+	{
+		if (j == NUM_XP)
+			j = 0;
+		if (strcmp (te[j].name, name) == 0)
+		{
+			*i_i = j;
+			return j;
+		}
+		if (j == i)
+			return -1;
+		j++;
+	}
+}
+
+int
+pevent_load (char *filename)
+{
+	/* AGL, I've changed this file and pevent_save, could you please take a look at
+	 *      the changes and possibly modify them to suit you
+	 *      //David H
+	 */
+	char *buf, *ibuf;
+	int fd, i = 0, pnt = 0;
+	struct stat st;
+	char *text = NULL, *snd = NULL;
+	int penum = 0;
+	char *ofs;
+
+	if (filename == NULL)
+		fd = xchat_open_file ("pevents.conf", O_RDONLY, 0, 0);
+	else
+		fd = xchat_open_file (filename, O_RDONLY, 0, XOF_FULLPATH);
+
+	if (fd == -1)
+		return 1;
+	if (fstat (fd, &st) != 0)
+		return 1;
+	ibuf = malloc (st.st_size);
+	read (fd, ibuf, st.st_size);
+	close (fd);
+
+	while (buf_get_line (ibuf, &buf, &pnt, st.st_size))
+	{
+		if (buf[0] == '#')
+			continue;
+		if (strlen (buf) == 0)
+			continue;
+
+		ofs = strchr (buf, '=');
+		if (!ofs)
+			continue;
+		*ofs = 0;
+		ofs++;
+		/*if (*ofs == 0)
+			continue;*/
+
+		if (strcmp (buf, "event_name") == 0)
+		{
+			if (penum >= 0)
+				pevent_trigger_load (&penum, &text, &snd);
+			penum = pevent_find (ofs, &i);
+			continue;
+		} else if (strcmp (buf, "event_text") == 0)
+		{
+			if (text)
+				free (text);
+
+#if 0
+			/* This allows updating of old strings. We don't use new defaults
+				if the user has customized the strings (.e.g a text theme).
+				Hash of the old default is enough to identify and replace it.
+				This only works in English. */
+
+			switch (g_str_hash (ofs))
+			{
+			case 0x526743a4:
+		/* %C08,02 Hostmask                  PRIV NOTI CHAN CTCP INVI UNIG %O */
+				text = strdup (te[XP_TE_IGNOREHEADER].def);
+				break;
+
+			case 0xe91bc9c2:
+		/* %C08,02                                                         %O */
+				text = strdup (te[XP_TE_IGNOREFOOTER].def);
+				break;
+
+			case 0x1fbfdf22:
+		/* -%C10-%C11-%O$tDCC RECV: Cannot open $1 for writing - aborting. */
+				text = strdup (te[XP_TE_DCCFILEERR].def);
+				break;
+
+			default:
+				text = strdup (ofs);
+			}
+#else
+			text = strdup (ofs);
+#endif
+
+			continue;
+		}/* else if (strcmp (buf, "event_sound") == 0)
+		{
+			if (snd)
+				free (snd);
+			snd = strdup (ofs);
+			continue;
+		}*/
+
+		continue;
+	}
+
+	pevent_trigger_load (&penum, &text, &snd);
+	free (ibuf);
+	return 0;
+}
+
+static void
+pevent_check_all_loaded ()
+{
+	int i;
+
+	for (i = 0; i < NUM_XP; i++)
+	{
+		if (pntevts_text[i] == NULL)
+		{
+			/*printf ("%s\n", te[i].name);
+			snprintf(out, sizeof(out), "The data for event %s failed to load. Reverting to defaults.\nThis may be because a new version of XChat is loading an old config file.\n\nCheck all print event texts are correct", evtnames[i]);
+			   gtkutil_simpledialog(out); */
+			/* make-te.c sets this 128 flag (DON'T call gettext() flag) */
+			if (te[i].num_args & 128)
+				pntevts_text[i] = strdup (te[i].def);
+			else
+				pntevts_text[i] = strdup (_(te[i].def));
+		}
+	}
+}
+
+void
+load_text_events ()
+{
+	memset (&pntevts_text, 0, sizeof (char *) * (NUM_XP));
+	memset (&pntevts, 0, sizeof (char *) * (NUM_XP));
+
+	if (pevent_load (NULL))
+		pevent_load_defaults ();
+	pevent_check_all_loaded ();
+	pevent_make_pntevts ();
+}
+
+/*
+	CL: format_event now handles filtering of arguments:
+	1) if prefs.stripcolor is set, filter all style control codes from arguments
+	2) always strip \010 (ATTR_HIDDEN) from arguments: it is only for use in the format string itself
+*/
+#define ARG_FLAG(argn) (1 << (argn))
+
+void
+format_event (session *sess, int index, char **args, char *o, int sizeofo, unsigned int stripcolor_args)
+{
+	int len, oi, ii, numargs;
+	char *i, *ar, d, a, done_all = FALSE;
+
+	i = pntevts[index];
+	numargs = te[index].num_args & 0x7f;
+
+	oi = ii = len = d = a = 0;
+	o[0] = 0;
+
+	if (i == NULL)
+		return;
+
+	while (done_all == FALSE)
+	{
+		d = i[ii++];
+		switch (d)
+		{
+		case 0:
+			memcpy (&len, &(i[ii]), sizeof (int));
+			ii += sizeof (int);
+			if (oi + len > sizeofo)
+			{
+				printf ("Overflow in display_event (%s)\n", i);
+				o[0] = 0;
+				return;
+			}
+			memcpy (&(o[oi]), &(i[ii]), len);
+			oi += len;
+			ii += len;
+			break;
+		case 1:
+			a = i[ii++];
+			if (a > numargs)
+			{
+				fprintf (stderr,
+							"XChat DEBUG: display_event: arg > numargs (%d %d %s)\n",
+							a, numargs, i);
+				break;
+			}
+			ar = args[(int) a + 1];
+			if (ar == NULL)
+			{
+				printf ("arg[%d] is NULL in print event\n", a + 1);
+			} else
+			{
+				if (stripcolor_args & ARG_FLAG(a + 1)) len = strip_color2 (ar, -1, &o[oi], STRIP_ALL);
+				else len = strip_hidden_attribute (ar, &o[oi]);
+				oi += len;
+			}
+			break;
+		case 2:
+			o[oi++] = '\n';
+			o[oi++] = 0;
+			done_all = TRUE;
+			continue;
+		case 3:
+/*			if (sess->type == SESS_DIALOG)
+			{
+				if (prefs.dialog_indent_nicks)
+					o[oi++] = '\t';
+				else
+					o[oi++] = ' ';
+			} else
+			{*/
+				if (prefs.indent_nicks)
+					o[oi++] = '\t';
+				else
+					o[oi++] = ' ';
+			/*}*/
+			break;
+		}
+	}
+	o[oi] = 0;
+	if (*o == '\n')
+		o[0] = 0;
+}
+
+static void
+display_event (session *sess, int event, char **args, unsigned int stripcolor_args)
+{
+	char o[4096];
+	format_event (sess, event, args, o, sizeof (o), stripcolor_args);
+	if (o[0])
+		PrintText (sess, o);
+}
+
+int
+pevt_build_string (const char *input, char **output, int *max_arg)
+{
+	struct pevt_stage1 *s = NULL, *base = NULL, *last = NULL, *next;
+	int clen;
+	char o[4096], d, *obuf, *i;
+	int oi, ii, max = -1, len, x;
+
+	len = strlen (input);
+	i = malloc (len + 1);
+	memcpy (i, input, len + 1);
+	check_special_chars (i, TRUE);
+
+	len = strlen (i);
+
+	clen = oi = ii = 0;
+
+	for (;;)
+	{
+		if (ii == len)
+			break;
+		d = i[ii++];
+		if (d != '$')
+		{
+			o[oi++] = d;
+			continue;
+		}
+		if (i[ii] == '$')
+		{
+			o[oi++] = '$';
+			continue;
+		}
+		if (oi > 0)
+		{
+			s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1));
+			if (base == NULL)
+				base = s;
+			if (last != NULL)
+				last->next = s;
+			last = s;
+			s->next = NULL;
+			s->data = malloc (oi + sizeof (int) + 1);
+			s->len = oi + sizeof (int) + 1;
+			clen += oi + sizeof (int) + 1;
+			s->data[0] = 0;
+			memcpy (&(s->data[1]), &oi, sizeof (int));
+			memcpy (&(s->data[1 + sizeof (int)]), o, oi);
+			oi = 0;
+		}
+		if (ii == len)
+		{
+			fe_message ("String ends with a $", FE_MSG_WARN);
+			return 1;
+		}
+		d = i[ii++];
+		if (d == 'a')
+		{								  /* Hex value */
+			x = 0;
+			if (ii == len)
+				goto a_len_error;
+			d = i[ii++];
+			d -= '0';
+			x = d * 100;
+			if (ii == len)
+				goto a_len_error;
+			d = i[ii++];
+			d -= '0';
+			x += d * 10;
+			if (ii == len)
+				goto a_len_error;
+			d = i[ii++];
+			d -= '0';
+			x += d;
+			if (x > 255)
+				goto a_range_error;
+			o[oi++] = x;
+			continue;
+
+		 a_len_error:
+			fe_message ("String ends in $a", FE_MSG_WARN);
+			return 1;
+		 a_range_error:
+			fe_message ("$a value is greater than 255", FE_MSG_WARN);
+			return 1;
+		}
+		if (d == 't')
+		{
+			/* Tab - if tabnicks is set then write '\t' else ' ' */
+			s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1));
+			if (base == NULL)
+				base = s;
+			if (last != NULL)
+				last->next = s;
+			last = s;
+			s->next = NULL;
+			s->data = malloc (1);
+			s->len = 1;
+			clen += 1;
+			s->data[0] = 3;
+
+			continue;
+		}
+		if (d < '1' || d > '9')
+		{
+			snprintf (o, sizeof (o), "Error, invalid argument $%c\n", d);
+			fe_message (o, FE_MSG_WARN);
+			return 1;
+		}
+		d -= '0';
+		if (max < d)
+			max = d;
+		s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1));
+		if (base == NULL)
+			base = s;
+		if (last != NULL)
+			last->next = s;
+		last = s;
+		s->next = NULL;
+		s->data = malloc (2);
+		s->len = 2;
+		clen += 2;
+		s->data[0] = 1;
+		s->data[1] = d - 1;
+	}
+	if (oi > 0)
+	{
+		s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1));
+		if (base == NULL)
+			base = s;
+		if (last != NULL)
+			last->next = s;
+		last = s;
+		s->next = NULL;
+		s->data = malloc (oi + sizeof (int) + 1);
+		s->len = oi + sizeof (int) + 1;
+		clen += oi + sizeof (int) + 1;
+		s->data[0] = 0;
+		memcpy (&(s->data[1]), &oi, sizeof (int));
+		memcpy (&(s->data[1 + sizeof (int)]), o, oi);
+		oi = 0;
+	}
+	s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1));
+	if (base == NULL)
+		base = s;
+	if (last != NULL)
+		last->next = s;
+	last = s;
+	s->next = NULL;
+	s->data = malloc (1);
+	s->len = 1;
+	clen += 1;
+	s->data[0] = 2;
+
+	oi = 0;
+	s = base;
+	obuf = malloc (clen);
+	while (s)
+	{
+		next = s->next;
+		memcpy (&obuf[oi], s->data, s->len);
+		oi += s->len;
+		free (s->data);
+		free (s);
+		s = next;
+	}
+
+	free (i);
+
+	if (max_arg)
+		*max_arg = max;
+	if (output)
+		*output = obuf;
+
+	return 0;
+}
+
+
+/* black n white(0/1) are bad colors for nicks, and we'll use color 2 for us */
+/* also light/dark gray (14/15) */
+/* 5,7,8 are all shades of yellow which happen to look dman near the same */
+
+static char rcolors[] = { 19, 20, 22, 24, 25, 26, 27, 28, 29 };
+
+static int
+color_of (char *name)
+{
+	int i = 0, sum = 0;
+
+	while (name[i])
+		sum += name[i++];
+	sum %= sizeof (rcolors) / sizeof (char);
+	return rcolors[sum];
+}
+
+
+/* called by EMIT_SIGNAL macro */
+
+void
+text_emit (int index, session *sess, char *a, char *b, char *c, char *d)
+{
+	char *word[PDIWORDS];
+	int i;
+	unsigned int stripcolor_args = (prefs.stripcolor ? 0xFFFFFFFF : 0);
+	char tbuf[NICKLEN + 4];
+
+	if (prefs.colorednicks && (index == XP_TE_CHANACTION || index == XP_TE_CHANMSG))
+	{
+		snprintf (tbuf, sizeof (tbuf), "\003%d%s", color_of (a), a);
+		a = tbuf;
+		stripcolor_args &= ~ARG_FLAG(1);	/* don't strip color from this argument */
+	}
+
+	word[0] = te[index].name;
+	word[1] = (a ? a : "\000");
+	word[2] = (b ? b : "\000");
+	word[3] = (c ? c : "\000");
+	word[4] = (d ? d : "\000");
+	for (i = 5; i < PDIWORDS; i++)
+		word[i] = "\000";
+
+	if (plugin_emit_print (sess, word))
+		return;
+
+	/* If a plugin's callback executes "/close", 'sess' may be invalid */
+	if (!is_session (sess))
+		return;
+
+	switch (index)
+	{
+	case XP_TE_JOIN:
+	case XP_TE_PART:
+	case XP_TE_PARTREASON:
+	case XP_TE_QUIT:
+		/* implement ConfMode / Hide Join and Part Messages */
+		if (chanopt_is_set (prefs.confmode, sess->text_hidejoinpart))
+			return;
+		break;
+
+	/* ===Private message=== */
+	case XP_TE_PRIVMSG:
+	case XP_TE_DPRIVMSG:
+	case XP_TE_PRIVACTION:
+	case XP_TE_DPRIVACTION:
+		if (chanopt_is_set_a (prefs.input_beep_priv, sess->alert_beep))
+			sound_beep (sess);
+		if (chanopt_is_set_a (prefs.input_flash_priv, sess->alert_taskbar))
+			fe_flash_window (sess);
+		/* why is this one different? because of plugin-tray.c's hooks! ugly */
+		if (sess->alert_tray == SET_ON)
+			fe_tray_set_icon (FE_ICON_MESSAGE);
+		break;
+
+	/* ===Highlighted message=== */
+	case XP_TE_HCHANACTION:
+	case XP_TE_HCHANMSG:
+		if (chanopt_is_set_a (prefs.input_beep_hilight, sess->alert_beep))
+			sound_beep (sess);
+		if (chanopt_is_set_a (prefs.input_flash_hilight, sess->alert_taskbar))
+			fe_flash_window (sess);
+		if (sess->alert_tray == SET_ON)
+			fe_tray_set_icon (FE_ICON_MESSAGE);
+		break;
+
+	/* ===Channel message=== */
+	case XP_TE_CHANACTION:
+	case XP_TE_CHANMSG:
+		if (chanopt_is_set_a (prefs.input_beep_chans, sess->alert_beep))
+			sound_beep (sess);
+		if (chanopt_is_set_a (prefs.input_flash_chans, sess->alert_taskbar))
+			fe_flash_window (sess);
+		if (sess->alert_tray == SET_ON)
+			fe_tray_set_icon (FE_ICON_MESSAGE);
+		break;
+	}
+
+	sound_play_event (index);
+	display_event (sess, index, word, stripcolor_args);
+}
+
+char *
+text_find_format_string (char *name)
+{
+	int i = 0;
+
+	i = pevent_find (name, &i);
+	if (i >= 0)
+		return pntevts_text[i];
+
+	return NULL;
+}
+
+int
+text_emit_by_name (char *name, session *sess, char *a, char *b, char *c, char *d)
+{
+	int i = 0;
+
+	i = pevent_find (name, &i);
+	if (i >= 0)
+	{
+		text_emit (i, sess, a, b, c, d);
+		return 1;
+	}
+
+	return 0;
+}
+
+void
+pevent_save (char *fn)
+{
+	int fd, i;
+	char buf[1024];
+
+	if (!fn)
+		fd = xchat_open_file ("pevents.conf", O_CREAT | O_TRUNC | O_WRONLY,
+									 0x180, XOF_DOMODE);
+	else
+		fd = xchat_open_file (fn, O_CREAT | O_TRUNC | O_WRONLY, 0x180,
+									 XOF_FULLPATH | XOF_DOMODE);
+	if (fd == -1)
+	{
+		/*
+		   fe_message ("Error opening config file\n", FALSE); 
+		   If we get here when X-Chat is closing the fe-message causes a nice & hard crash
+		   so we have to use perror which doesn't rely on GTK
+		 */
+
+		perror ("Error opening config file\n");
+		return;
+	}
+
+	for (i = 0; i < NUM_XP; i++)
+	{
+		write (fd, buf, snprintf (buf, sizeof (buf),
+										  "event_name=%s\n", te[i].name));
+		write (fd, buf, snprintf (buf, sizeof (buf),
+										  "event_text=%s\n\n", pntevts_text[i]));
+	}
+
+	close (fd);
+}
+
+/* =========================== */
+/* ========== SOUND ========== */
+/* =========================== */
+
+char *sound_files[NUM_XP];
+
+void
+sound_beep (session *sess)
+{
+	if (sound_files[XP_TE_BEEP] && sound_files[XP_TE_BEEP][0])
+		/* user defined beep _file_ */
+		sound_play_event (XP_TE_BEEP);
+	else
+		/* system beep */
+		fe_beep ();
+}
+
+static char *
+sound_find_command (void)
+{
+	/* some sensible unix players. You're bound to have one of them */
+	static const char * const progs[] = {"aplay", "esdplay", "soxplay", "artsplay", NULL};
+	char *cmd;
+	int i = 0;
+
+	if (prefs.soundcmd[0])
+		return g_strdup (prefs.soundcmd);
+
+	while (progs[i])
+	{
+		cmd = g_find_program_in_path (progs[i]);
+		if (cmd)
+			return cmd;
+		i++;
+	}
+
+	return NULL;
+}
+
+void
+sound_play (const char *file, gboolean quiet)
+{
+	char buf[512];
+	char wavfile[512];
+	char *file_fs;
+	char *cmd;
+
+	/* the pevents GUI editor triggers this after removing a soundfile */
+	if (!file[0])
+		return;
+
+#ifdef WIN32
+	/* check for fullpath, windows style */
+	if (strlen (file) > 3 &&
+		 file[1] == ':' && (file[2] == '\\' || file[2] == '/') )
+	{
+		strncpy (wavfile, file, sizeof (wavfile));
+	} else
+#endif
+	if (file[0] != '/')
+	{
+		snprintf (wavfile, sizeof (wavfile), "%s/%s", prefs.sounddir, file);
+	} else
+	{
+		strncpy (wavfile, file, sizeof (wavfile));
+	}
+	wavfile[sizeof (wavfile) - 1] = 0;	/* ensure termination */
+
+	file_fs = xchat_filename_from_utf8 (wavfile, -1, 0, 0, 0);
+	if (!file_fs)
+		return;
+
+	if (access (file_fs, R_OK) == 0)
+	{
+		cmd = sound_find_command ();
+
+#ifdef WIN32
+		if (cmd == NULL || strcmp (cmd, "esdplay") == 0)
+		{
+			PlaySound (file_fs, NULL, SND_NODEFAULT|SND_FILENAME|SND_ASYNC);
+		} else
+#endif
+		{
+			if (cmd)
+			{
+				if (strchr (file_fs, ' '))
+					snprintf (buf, sizeof (buf), "%s \"%s\"", cmd, file_fs);
+				else
+					snprintf (buf, sizeof (buf), "%s %s", cmd, file_fs);
+				buf[sizeof (buf) - 1] = '\0';
+				xchat_exec (buf);
+			}
+		}
+
+		if (cmd)
+			g_free (cmd);
+
+	} else
+	{
+		if (!quiet)
+		{
+			snprintf (buf, sizeof (buf), _("Cannot read sound file:\n%s"), wavfile);
+			fe_message (buf, FE_MSG_ERROR);
+		}
+	}
+
+	g_free (file_fs);
+}
+
+void
+sound_play_event (int i)
+{
+	if (sound_files[i])
+		sound_play (sound_files[i], FALSE);
+}
+
+static void
+sound_load_event (char *evt, char *file)
+{
+	int i = 0;
+
+	if (file[0] && pevent_find (evt, &i) != -1)
+	{
+		if (sound_files[i])
+			free (sound_files[i]);
+		sound_files[i] = strdup (file);
+	}
+}
+
+void
+sound_load ()
+{
+	int fd;
+	char buf[512];
+	char evt[128];
+
+	memset (&sound_files, 0, sizeof (char *) * (NUM_XP));
+
+	fd = xchat_open_file ("sound.conf", O_RDONLY, 0, 0);
+	if (fd == -1)
+		return;
+
+	evt[0] = 0;
+	while (waitline (fd, buf, sizeof buf, FALSE) != -1)
+	{
+		if (strncmp (buf, "event=", 6) == 0)
+		{
+			safe_strcpy (evt, buf + 6, sizeof (evt));
+		}
+		else if (strncmp (buf, "sound=", 6) == 0)
+		{
+			if (evt[0] != 0)
+			{
+				sound_load_event (evt, buf + 6);
+				evt[0] = 0;
+			}
+		}
+	}
+
+	close (fd);
+}
+
+void
+sound_save ()
+{
+	int fd, i;
+	char buf[512];
+
+	fd = xchat_open_file ("sound.conf", O_CREAT | O_TRUNC | O_WRONLY, 0x180,
+								 XOF_DOMODE);
+	if (fd == -1)
+		return;
+
+	for (i = 0; i < NUM_XP; i++)
+	{
+		if (sound_files[i] && sound_files[i][0])
+		{
+			write (fd, buf, snprintf (buf, sizeof (buf),
+											  "event=%s\n", te[i].name));
+			write (fd, buf, snprintf (buf, sizeof (buf),
+											  "sound=%s\n\n", sound_files[i]));
+		}
+	}
+
+	close (fd);
+}
diff --git a/src/common/text.h b/src/common/text.h
new file mode 100644
index 00000000..150821ae
--- /dev/null
+++ b/src/common/text.h
@@ -0,0 +1,42 @@
+#include "textenums.h"
+
+#ifndef XCHAT_TEXT_H
+#define XCHAT_TEXT_H
+
+#define EMIT_SIGNAL(i, sess, a, b, c, d, e) text_emit(i, sess, a, b, c, d)
+
+struct text_event
+{
+	char *name;
+	char * const *help;
+	int num_args;
+	char *def;
+};
+
+void scrollback_close (session *sess);
+void scrollback_load (session *sess);
+
+int text_word_check (char *word, int len);
+void PrintText (session *sess, char *text);
+void PrintTextf (session *sess, char *format, ...);
+void log_close (session *sess);
+void log_open_or_close (session *sess);
+void load_text_events (void);
+void pevent_save (char *fn);
+int pevt_build_string (const char *input, char **output, int *max_arg);
+int pevent_load (char *filename);
+void pevent_make_pntevts (void);
+void text_emit (int index, session *sess, char *a, char *b, char *c, char *d);
+int text_emit_by_name (char *name, session *sess, char *a, char *b, char *c, char *d);
+char *text_validate (char **text, int *len);
+int get_stamp_str (char *fmt, time_t tim, char **ret);
+void format_event (session *sess, int index, char **args, char *o, int sizeofo, unsigned int stripcolor_args);
+char *text_find_format_string (char *name);
+ 
+void sound_play (const char *file, gboolean quiet);
+void sound_play_event (int i);
+void sound_beep (session *);
+void sound_load ();
+void sound_save ();
+
+#endif
diff --git a/src/common/textenums.h b/src/common/textenums.h
new file mode 100644
index 00000000..682a35ef
--- /dev/null
+++ b/src/common/textenums.h
@@ -0,0 +1,76 @@
+/* this file is auto generated, edit textevents.in instead! */
+
+enum
+{
+	XP_TE_ADDNOTIFY,		XP_TE_BANLIST,
+	XP_TE_BANNED,		XP_TE_BEEP,
+	XP_TE_CHANGENICK,		XP_TE_CHANACTION,
+	XP_TE_HCHANACTION,		XP_TE_CHANBAN,
+	XP_TE_CHANDATE,		XP_TE_CHANDEHOP,
+	XP_TE_CHANDEOP,		XP_TE_CHANDEVOICE,
+	XP_TE_CHANEXEMPT,		XP_TE_CHANHOP,
+	XP_TE_CHANINVITE,		XP_TE_CHANLISTHEAD,
+	XP_TE_CHANMSG,		XP_TE_CHANMODEGEN,
+	XP_TE_CHANMODES,		XP_TE_HCHANMSG,
+	XP_TE_CHANNOTICE,		XP_TE_CHANOP,
+	XP_TE_CHANRMEXEMPT,		XP_TE_CHANRMINVITE,
+	XP_TE_CHANRMKEY,		XP_TE_CHANRMLIMIT,
+	XP_TE_CHANSETKEY,		XP_TE_CHANSETLIMIT,
+	XP_TE_CHANUNBAN,		XP_TE_CHANVOICE,
+	XP_TE_CONNECTED,		XP_TE_CONNECT,
+	XP_TE_CONNFAIL,		XP_TE_CTCPGEN,
+	XP_TE_CTCPGENC,		XP_TE_CTCPSEND,
+	XP_TE_CTCPSND,		XP_TE_CTCPSNDC,
+	XP_TE_DCCCHATABORT,		XP_TE_DCCCONCHAT,
+	XP_TE_DCCCHATF,		XP_TE_DCCCHATOFFER,
+	XP_TE_DCCCHATOFFERING,		XP_TE_DCCCHATREOFFER,
+	XP_TE_DCCCONFAIL,		XP_TE_DCCGENERICOFFER,
+	XP_TE_DCCHEAD,		XP_TE_MALFORMED,
+	XP_TE_DCCOFFER,		XP_TE_DCCIVAL,
+	XP_TE_DCCRECVABORT,		XP_TE_DCCRECVCOMP,
+	XP_TE_DCCCONRECV,		XP_TE_DCCRECVERR,
+	XP_TE_DCCFILEERR,		XP_TE_DCCRENAME,
+	XP_TE_DCCRESUMEREQUEST,		XP_TE_DCCSENDABORT,
+	XP_TE_DCCSENDCOMP,		XP_TE_DCCCONSEND,
+	XP_TE_DCCSENDFAIL,		XP_TE_DCCSENDOFFER,
+	XP_TE_DCCSTALL,		XP_TE_DCCTOUT,
+	XP_TE_DELNOTIFY,		XP_TE_DISCON,
+	XP_TE_FOUNDIP,		XP_TE_GENMSG,
+	XP_TE_IGNOREADD,		XP_TE_IGNORECHANGE,
+	XP_TE_IGNOREFOOTER,		XP_TE_IGNOREHEADER,
+	XP_TE_IGNOREREMOVE,		XP_TE_IGNOREEMPTY,
+	XP_TE_INVITE,		XP_TE_INVITED,
+	XP_TE_JOIN,		XP_TE_KEYWORD,
+	XP_TE_KICK,		XP_TE_KILL,
+	XP_TE_MSGSEND,		XP_TE_MOTD,
+	XP_TE_MOTDSKIP,		XP_TE_NICKCLASH,
+	XP_TE_NICKFAIL,		XP_TE_NODCC,
+	XP_TE_NOCHILD,		XP_TE_NOTICE,
+	XP_TE_NOTICESEND,		XP_TE_NOTIFYEMPTY,
+	XP_TE_NOTIFYHEAD,		XP_TE_NOTIFYNUMBER,
+	XP_TE_NOTIFYOFFLINE,		XP_TE_NOTIFYONLINE,
+	XP_TE_OPENDIALOG,		XP_TE_PART,
+	XP_TE_PARTREASON,		XP_TE_PINGREP,
+	XP_TE_PINGTIMEOUT,		XP_TE_PRIVACTION,
+	XP_TE_DPRIVACTION,		XP_TE_PRIVMSG,
+	XP_TE_DPRIVMSG,		XP_TE_ALREADYPROCESS,
+	XP_TE_QUIT,		XP_TE_RAWMODES,
+	XP_TE_WALLOPS,		XP_TE_RESOLVINGUSER,
+	XP_TE_SERVERCONNECTED,		XP_TE_SERVERERROR,
+	XP_TE_SERVERLOOKUP,		XP_TE_SERVNOTICE,
+	XP_TE_SERVTEXT,		XP_TE_SSLMESSAGE,
+	XP_TE_STOPCONNECT,		XP_TE_TOPIC,
+	XP_TE_NEWTOPIC,		XP_TE_TOPICDATE,
+	XP_TE_UKNHOST,		XP_TE_USERLIMIT,
+	XP_TE_USERSONCHAN,		XP_TE_WHOIS_AUTH,
+	XP_TE_WHOIS5,		XP_TE_WHOIS2,
+	XP_TE_WHOIS6,		XP_TE_WHOIS_ID,
+	XP_TE_WHOIS4,		XP_TE_WHOIS4T,
+	XP_TE_WHOIS1,		XP_TE_WHOIS_REALHOST,
+	XP_TE_WHOIS3,		XP_TE_WHOIS_SPECIAL,
+	XP_TE_UJOIN,		XP_TE_UKICK,
+	XP_TE_UPART,		XP_TE_UPARTREASON,
+	XP_TE_UACTION,		XP_TE_UINVITE,
+	XP_TE_UCHANMSG,		XP_TE_UCHANGENICK,
+	NUM_XP
+};
diff --git a/src/common/textevents.h b/src/common/textevents.h
new file mode 100644
index 00000000..08a54da5
--- /dev/null
+++ b/src/common/textevents.h
@@ -0,0 +1,424 @@
+/* this file is auto generated, edit textevents.in instead! */
+
+const struct text_event te[] = {
+
+{"Add Notify", pevt_generic_nick_help, 1, 
+N_("%C22*%O$t$1 added to notify list.")},
+
+{"Ban List", pevt_banlist_help, 4, 
+N_("%C22*%O$t$1 Banlist:%C19 $4%C20 $2%C21 $3")},
+
+{"Banned", pevt_generic_channel_help, 1, 
+N_("%C22*%O$tCannot join%C26 %B$1 %O(You are banned).")},
+
+{"Beep", pevt_generic_none_help, 128, 
+""},
+
+{"Change Nick", pevt_changenick_help, 2, 
+N_("%C22*%O$t$1 is now known as $2")},
+
+{"Channel Action", pevt_chanaction_help, 132, 
+"%C18*$t$1%O $2"},
+
+{"Channel Action Hilight", pevt_chanaction_help, 132, 
+"%C21*%O$t%C21%B$1%O%C21 $2"},
+
+{"Channel Ban", pevt_chanban_help, 2, 
+N_("%C22*%O$t$1 sets ban on $2")},
+
+{"Channel Creation", pevt_chandate_help, 2, 
+N_("%C22*%O$tChannel $1 created on $2")},
+
+{"Channel DeHalfOp", pevt_chandehop_help, 2, 
+N_("%C22*%O$t%C26$1%O removes channel half-operator status from%C26 $2")},
+
+{"Channel DeOp", pevt_chandeop_help, 2, 
+N_("%C22*%O$t%C26$1%O removes channel operator status from%C26 $2")},
+
+{"Channel DeVoice", pevt_chandevoice_help, 2, 
+N_("%C22*%O$t%C26$1%O removes voice from%C26 $2")},
+
+{"Channel Exempt", pevt_chanexempt_help, 2, 
+N_("%C22*%O$t$1 sets exempt on $2")},
+
+{"Channel Half-Operator", pevt_chanhop_help, 2, 
+N_("%C22*%O$t%C26$1%O gives channel half-operator status to%C26 $2")},
+
+{"Channel INVITE", pevt_chaninvite_help, 2, 
+N_("%C22*%O$t$1 sets invite on $2")},
+
+{"Channel List", pevt_generic_none_help, 0, 
+N_("%UChannel          Users   Topic")},
+
+{"Channel Message", pevt_chanmsg_help, 132, 
+"%C18%H<%H$4$1%H>%H%O$t$2"},
+
+{"Channel Mode Generic", pevt_chanmodegen_help, 4, 
+N_("%C22*%O$t$1 sets mode $2$3 $4")},
+
+{"Channel Modes", pevt_chanmodes_help, 2, 
+N_("%C22*%O$t%C22Channel $1 modes: $2")},
+
+{"Channel Msg Hilight", pevt_chanmsg_help, 132, 
+"$4%C21%B%H<%H$1%H>%H%O%C21$t$2"},
+
+{"Channel Notice", pevt_channotice_help, 131, 
+"%C28-%C29$1/$2%C28-%O$t$3"},
+
+{"Channel Operator", pevt_chanop_help, 2, 
+N_("%C22*%O$t%C26$1%O gives channel operator status to%C26 $2")},
+
+{"Channel Remove Exempt", pevt_chanrmexempt_help, 2, 
+N_("%C22*%O$t$1 removes exempt on $2")},
+
+{"Channel Remove Invite", pevt_chanrminvite_help, 2, 
+N_("%C22*%O$t$1 removes invite on $2")},
+
+{"Channel Remove Keyword", pevt_chanrmkey_help, 1, 
+N_("%C22*%O$t$1 removes channel keyword")},
+
+{"Channel Remove Limit", pevt_chanrmlimit_help, 1, 
+N_("%C22*%O$t$1 removes user limit")},
+
+{"Channel Set Key", pevt_chansetkey_help, 2, 
+N_("%C22*%O$t$1 sets channel keyword to $2")},
+
+{"Channel Set Limit", pevt_chansetlimit_help, 2, 
+N_("%C22*%O$t$1 sets channel limit to $2")},
+
+{"Channel UnBan", pevt_chanunban_help, 2, 
+N_("%C22*%O$t$1 removes ban on $2")},
+
+{"Channel Voice", pevt_chanvoice_help, 2, 
+N_("%C22*%O$t%C26$1%O gives voice to%C26 $2")},
+
+{"Connected", pevt_generic_none_help, 0, 
+N_("%C22*%O$t%C22Connected. Now logging in...")},
+
+{"Connecting", pevt_connect_help, 3, 
+N_("%C22*%O$t%C22Connecting to $1 ($2) port $3%O...")},
+
+{"Connection Failed", pevt_connfail_help, 1, 
+N_("%C21*%O$t%C21Connection failed. Error: $1")},
+
+{"CTCP Generic", pevt_ctcpgen_help, 2, 
+N_("%C22*%O$tReceived a CTCP $1 from $2")},
+
+{"CTCP Generic to Channel", pevt_ctcpgenc_help, 3, 
+N_("%C22*%O$tReceived a CTCP $1 from $2 (to $3)")},
+
+{"CTCP Send", pevt_ctcpsend_help, 2, 
+N_("%C19>%O$1%C19<%O$tCTCP $2")},
+
+{"CTCP Sound", pevt_ctcpsnd_help, 2, 
+N_("%C22*%O$tReceived a CTCP Sound $1 from $2")},
+
+{"CTCP Sound to Channel", pevt_ctcpsnd_help, 3, 
+N_("%C22*%O$tReceived a CTCP Sound $1 from $2 (to $3)")},
+
+{"DCC CHAT Abort", pevt_dccchatabort_help, 1, 
+N_("%C22*%O$tDCC CHAT to %C26$1%O aborted.")},
+
+{"DCC CHAT Connect", pevt_dccchatcon_help, 2, 
+N_("%C22*%O$tDCC CHAT connection established to %C26$1 %C30[%O$2%C30]")},
+
+{"DCC CHAT Failed", pevt_dccchaterr_help, 4, 
+N_("%C22*%O$tDCC CHAT to %C26$1%O lost ($4).")},
+
+{"DCC CHAT Offer", pevt_generic_nick_help, 1, 
+N_("%C22*%O$tReceived a DCC CHAT offer from $1")},
+
+{"DCC CHAT Offering", pevt_generic_nick_help, 1, 
+N_("%C22*%O$tOffering DCC CHAT to $1")},
+
+{"DCC CHAT Reoffer", pevt_generic_nick_help, 1, 
+N_("%C22*%O$tAlready offering CHAT to $1")},
+
+{"DCC Conection Failed", pevt_dccconfail_help, 3, 
+N_("%C22*%O$tDCC $1 connect attempt to%C26 $2%O failed (err=$3).")},
+
+{"DCC Generic Offer", pevt_dccgenericoffer_help, 2, 
+N_("%C22*%O$tReceived '$1%O' from $2")},
+
+{"DCC Header", pevt_generic_none_help, 0, 
+N_("%C24,18 Type  To/From    Status  Size    Pos     File         ")},
+
+{"DCC Malformed", pevt_malformed_help, 2, 
+N_("%C22*%O$tReceived a malformed DCC request from %C26$1%O.%010%C22*%O$tContents of packet: $2")},
+
+{"DCC Offer", pevt_dccoffer_help, 3, 
+N_("%C22*%O$tOffering%C26 $1%O to%C26 $2")},
+
+{"DCC Offer Not Valid", pevt_generic_none_help, 0, 
+N_("%C22*%O$tNo such DCC offer.")},
+
+{"DCC RECV Abort", pevt_dccfileabort_help, 2, 
+N_("%C22*%O$tDCC RECV%C26 $2%O to%C26 $1%O aborted.")},
+
+{"DCC RECV Complete", pevt_dccrecvcomp_help, 4, 
+N_("%C22*%O$tDCC RECV%C26 $1%O from%C26 $3%O complete %C30[%C26$4%O cps%C30]%O.")},
+
+{"DCC RECV Connect", pevt_dcccon_help, 3, 
+N_("%C22*%O$tDCC RECV connection established to%C26 $1 %C30[%O$2%C30]")},
+
+{"DCC RECV Failed", pevt_dccrecverr_help, 4, 
+N_("%C22*%O$tDCC RECV%C26 $1%O from%C26 $3%O failed ($4).")},
+
+{"DCC RECV File Open Error", pevt_generic_file_help, 2, 
+N_("%C22*%O$tDCC RECV: Cannot open $1 for writing ($2).")},
+
+{"DCC Rename", pevt_dccrename_help, 2, 
+N_("%C22*%O$tThe file%C26 $1%C already exists, saving it as%C26 $2%O instead.")},
+
+{"DCC RESUME Request", pevt_dccresumeoffer_help, 3, 
+N_("%C22*%O$t%C26$1 %Ohas requested to resume%C26 $2 %Cfrom%C26 $3%C.")},
+
+{"DCC SEND Abort", pevt_dccfileabort_help, 2, 
+N_("%C22*%O$tDCC SEND%C26 $2%O to%C26 $1%O aborted.")},
+
+{"DCC SEND Complete", pevt_dccsendcomp_help, 3, 
+N_("%C22*%O$tDCC SEND%C26 $1%O to%C26 $2%O complete %C30[%C26$3%O cps%C30]%O.")},
+
+{"DCC SEND Connect", pevt_dcccon_help, 3, 
+N_("%C22*%O$tDCC SEND connection established to%C26 $1 %C30[%O$2%C30]")},
+
+{"DCC SEND Failed", pevt_dccsendfail_help, 3, 
+N_("%C22*%O$tDCC SEND%C26 $1%O to%C26 $2%O failed. $3")},
+
+{"DCC SEND Offer", pevt_dccsendoffer_help, 4, 
+N_("%C22*%O$t%C26$1 %Ohas offered%C26 $2 %O(%C26$3 %Obytes)")},
+
+{"DCC Stall", pevt_dccstall_help, 3, 
+N_("%C22*%O$tDCC $1%C26 $2 %Oto%C26 $3 %Cstalled - aborting.")},
+
+{"DCC Timeout", pevt_dccstall_help, 3, 
+N_("%C22*%O$tDCC $1%C26 $2 %Oto%C26 $3 %Otimed out - aborting.")},
+
+{"Delete Notify", pevt_generic_nick_help, 1, 
+N_("%C22*%O$t$1 deleted from notify list.")},
+
+{"Disconnected", pevt_discon_help, 1, 
+N_("%C22*%O$tDisconnected ($1).")},
+
+{"Found IP", pevt_foundip_help, 1, 
+N_("%C22*%O$tFound your IP: [$1]")},
+
+{"Generic Message", pevt_genmsg_help, 130, 
+"$1$t$2"},
+
+{"Ignore Add", pevt_ignoreaddremove_help, 1, 
+N_("%O%C26$1%O added to ignore list.")},
+
+{"Ignore Changed", pevt_ignoreaddremove_help, 1, 
+N_("Ignore on %C26$1%O changed.")},
+
+{"Ignore Footer", pevt_generic_none_help, 0, 
+N_("%C24,18                                                              ")},
+
+{"Ignore Header", pevt_generic_none_help, 0, 
+N_("%C24,18 Hostmask                  PRIV NOTI CHAN CTCP DCC  INVI UNIG ")},
+
+{"Ignore Remove", pevt_ignoreaddremove_help, 1, 
+N_("%O%C26$1%O removed from ignore list.")},
+
+{"Ignorelist Empty", pevt_generic_none_help, 0, 
+N_("  Ignore list is empty.")},
+
+{"Invite", pevt_generic_channel_help, 1, 
+N_("%C22*%O$tCannot join%C26 %B$1 %O(Channel is invite only).")},
+
+{"Invited", pevt_invited_help, 3, 
+N_("%C22*%O$tYou have been invited to%C26 $1%O by%C26 $2%C (%C26$3%C)")},
+
+{"Join", pevt_join_help, 3, 
+N_("%C19*%O$t%C19%B$1 %B($3) has joined $2")},
+
+{"Keyword", pevt_generic_channel_help, 1, 
+N_("%C22*%O$tCannot join%C26 %B$1 %O(Requires keyword).")},
+
+{"Kick", pevt_kick_help, 4, 
+N_("%C21*%O$t%C21$1 has kicked $2 from $3 ($4%O%C21)")},
+
+{"Killed", pevt_kill_help, 2, 
+N_("%C22*%O$tYou have been killed by $1 ($2%O%C22)")},
+
+{"Message Send", pevt_ctcpsend_help, 130, 
+"%C19>%O$1%C19<%O$t$2"},
+
+{"Motd", pevt_servertext_help, 129, 
+"%C16*%O$t$1%O"},
+
+{"MOTD Skipped", pevt_generic_none_help, 0, 
+N_("%C22*%O$t%C22MOTD Skipped.")},
+
+{"Nick Clash", pevt_nickclash_help, 2, 
+N_("%C22*%O$t$1 already in use. Retrying with $2...")},
+
+{"Nick Failed", pevt_generic_none_help, 0, 
+N_("%C22*%O$tNickname already in use. Use /NICK to try another.")},
+
+{"No DCC", pevt_generic_none_help, 0, 
+N_("%C22*%O$tNo such DCC.")},
+
+{"No Running Process", pevt_generic_none_help, 0, 
+N_("%C22*%O$tNo process is currently running")},
+
+{"Notice", pevt_notice_help, 130, 
+"%C28-%C29$1%C28-%O$t$2"},
+
+{"Notice Send", pevt_ctcpsend_help, 130, 
+"%C19>%O$1%C19<%O$t$2"},
+
+{"Notify Empty", pevt_generic_none_help, 0, 
+N_("$tNotify list is empty.")},
+
+{"Notify Header", pevt_generic_none_help, 0, 
+N_("%C24,18 %B  Notify List                           ")},
+
+{"Notify Number", pevt_notifynumber_help, 1, 
+N_("%C22*%O$t$1 users in notify list.")},
+
+{"Notify Offline", pevt_generic_nick_help, 3, 
+N_("%C22*%O$tNotify: $1 is offline ($3).")},
+
+{"Notify Online", pevt_generic_nick_help, 3, 
+N_("%C22*%O$tNotify: $1 is online ($3).")},
+
+{"Open Dialog", pevt_generic_none_help, 128, 
+""},
+
+{"Part", pevt_part_help, 3, 
+N_("%C23*%O$t%C23$1 (%O%C23$2) has left $3")},
+
+{"Part with Reason", pevt_partreason_help, 4, 
+N_("%C23*%O$t%C23$1 (%O%C23$2) has left $3 (%O%C23%B%B$4%O%C23)")},
+
+{"Ping Reply", pevt_pingrep_help, 2, 
+N_("%C22*%O$tPing reply from $1: $2 second(s)")},
+
+{"Ping Timeout", pevt_pingtimeout_help, 1, 
+N_("%C22*%O$tNo ping reply for $1 seconds, disconnecting.")},
+
+{"Private Action", pevt_privmsg_help, 131, 
+"%C18**$t$3$1%O $2 %C18**"},
+
+{"Private Action to Dialog", pevt_privmsg_help, 131, 
+"%C18*$t$3$1%O $2"},
+
+{"Private Message", pevt_privmsg_help, 131, 
+"%C28*%C29$3$1%C28*$t%O$2"},
+
+{"Private Message to Dialog", pevt_privmsg_help, 131, 
+"%C18%H<%H$3$1%H>%H%O$t$2"},
+
+{"Process Already Running", pevt_generic_none_help, 0, 
+N_("%C22*%O$tA process is already running")},
+
+{"Quit", pevt_quit_help, 3, 
+N_("%C23*%O$t%C23$1 has quit (%O%C23%B%B$2%O%C23)")},
+
+{"Raw Modes", pevt_rawmodes_help, 2, 
+N_("%C22*%O$t$1 sets modes%B %C30[%O$2%B%C30]")},
+
+{"Receive Wallops", pevt_privmsg_help, 2, 
+N_("%C28-%C29$1/Wallops%C28-%O$t$2")},
+
+{"Resolving User", pevt_resolvinguser_help, 2, 
+N_("%C22*%O$tLooking up IP number for%C26 $1%O...")},
+
+{"Server Connected", pevt_generic_none_help, 0, 
+N_("%C22*%O$t%C22Connected.")},
+
+{"Server Error", pevt_servererror_help, 129, 
+"%C22*%O$t$1"},
+
+{"Server Lookup", pevt_serverlookup_help, 1, 
+N_("%C22*%O$t%C22Looking up $1")},
+
+{"Server Notice", pevt_servertext_help, 130, 
+"%C22*%O$t$1"},
+
+{"Server Text", pevt_servertext_help, 131, 
+"%C22*%O$t$1"},
+
+{"SSL Message", pevt_sslmessage_help, 130, 
+"%C22*%O$t$1"},
+
+{"Stop Connection", pevt_sconnect_help, 1, 
+N_("%C22*%O$tStopped previous connection attempt (pid=$1)")},
+
+{"Topic", pevt_topic_help, 2, 
+N_("%C29*%O$t%C29Topic for $1%C %C29is: $2")},
+
+{"Topic Change", pevt_newtopic_help, 3, 
+N_("%C22*%O$t$1 has changed the topic to: $2")},
+
+{"Topic Creation", pevt_topicdate_help, 3, 
+N_("%C29*%O$t%C29Topic for $1%C %C29set by $2%C %C29at $3")},
+
+{"Unknown Host", pevt_generic_none_help, 0, 
+N_("%C22*%O$tUnknown host. Maybe you misspelled it?")},
+
+{"User Limit", pevt_generic_channel_help, 1, 
+N_("%C22*%O$tCannot join%C26 %B$1 %O(User limit reached).")},
+
+{"Users On Channel", pevt_usersonchan_help, 2, 
+N_("%C22*%O$t%C26Users on $1:%C $2")},
+
+{"WhoIs Authenticated", pevt_whoisauth_help, 3, 
+N_("%C22*%O$t%C28[%O$1%C28] %O$2%C27 $3")},
+
+{"WhoIs Away Line", pevt_whois5_help, 2, 
+N_("%C22*%O$t%C28[%O$1%C28] %Cis away %C30(%O$2%O%C30)")},
+
+{"WhoIs Channel/Oper Line", pevt_whois2_help, 2, 
+N_("%C22*%O$t%C28[%O$1%C28]%O $2")},
+
+{"WhoIs End", pevt_whois6_help, 1, 
+N_("%C22*%O$t%C28[%O$1%C28] %OEnd of WHOIS list.")},
+
+{"WhoIs Identified", pevt_whoisid_help, 2, 
+N_("%C22*%O$t%C28[%O$1%C28]%O $2")},
+
+{"WhoIs Idle Line", pevt_whois4_help, 2, 
+N_("%C22*%O$t%C28[%O$1%C28]%O idle%C26 $2")},
+
+{"WhoIs Idle Line with Signon", pevt_whois4t_help, 3, 
+N_("%C22*%O$t%C28[%O$1%C28]%O idle%C26 $2%O, signon:%C26 $3")},
+
+{"WhoIs Name Line", pevt_whois1_help, 4, 
+N_("%C22*%O$t%C28[%O$1%C28] %C30(%O$2@$3%C30)%O: $4")},
+
+{"WhoIs Real Host", pevt_whoisrealhost_help, 4, 
+N_("%C22*%O$t%C28[%O$1%C28] %Oreal user@host%C27 $2%O, real IP%C27 $3")},
+
+{"WhoIs Server Line", pevt_whois3_help, 2, 
+N_("%C22*%O$t%C28[%O$1%C28]%O $2")},
+
+{"WhoIs Special", pevt_whoisid_help, 3, 
+N_("%C22*%O$t%C28[%O$1%C28]%O $2")},
+
+{"You Join", pevt_join_help, 3, 
+N_("%C19*%O$t%C19Now talking on $2")},
+
+{"You Kicked", pevt_ukick_help, 4, 
+N_("%C23*$tYou have been kicked from $2 by $3 ($4%O%C23)")},
+
+{"You Part", pevt_part_help, 3, 
+N_("%C23*$tYou have left channel $3")},
+
+{"You Part with Reason", pevt_partreason_help, 4, 
+N_("%C23*$tYou have left channel $3 (%O%C23%B%B$4%O%C23)")},
+
+{"Your Action", pevt_chanaction_help, 131, 
+"%C18*$t$1%O $2"},
+
+{"Your Invitation", pevt_uinvite_help, 3, 
+N_("%C22*%O$tYou've invited%C26 $1%O to%C26 $2%O (%C26$3%O)")},
+
+{"Your Message", pevt_chanmsg_help, 132, 
+"%C31%H<%H$4$1%H>%H%O%C30$t$2"},
+
+{"Your Nick Changing", pevt_uchangenick_help, 2, 
+N_("%C22*%O$tYou are now known as $2")},
+};
diff --git a/src/common/textevents.in b/src/common/textevents.in
new file mode 100644
index 00000000..5b4ce18c
--- /dev/null
+++ b/src/common/textevents.in
@@ -0,0 +1,840 @@
+Add Notify
+XP_TE_ADDNOTIFY
+pevt_generic_nick_help
+%C22*%O$t$1 added to notify list.
+1
+
+Ban List
+XP_TE_BANLIST
+pevt_banlist_help
+%C22*%O$t$1 Banlist:%C19 $4%C20 $2%C21 $3
+4
+
+Banned
+XP_TE_BANNED
+pevt_generic_channel_help
+%C22*%O$tCannot join%C26 %B$1 %O(You are banned).
+1
+
+Beep
+XP_TE_BEEP
+pevt_generic_none_help
+
+n0
+
+Change Nick
+XP_TE_CHANGENICK
+pevt_changenick_help
+%C22*%O$t$1 is now known as $2
+2
+
+Channel Action
+XP_TE_CHANACTION
+pevt_chanaction_help
+%C18*$t$1%O $2
+n4
+
+Channel Action Hilight
+XP_TE_HCHANACTION
+pevt_chanaction_help
+%C21*%O$t%C21%B$1%O%C21 $2
+n4
+
+Channel Ban
+XP_TE_CHANBAN
+pevt_chanban_help
+%C22*%O$t$1 sets ban on $2
+2
+
+Channel Creation
+XP_TE_CHANDATE
+pevt_chandate_help
+%C22*%O$tChannel $1 created on $2
+2
+
+Channel DeHalfOp
+XP_TE_CHANDEHOP
+pevt_chandehop_help
+%C22*%O$t%C26$1%O removes channel half-operator status from%C26 $2
+2
+
+Channel DeOp
+XP_TE_CHANDEOP
+pevt_chandeop_help
+%C22*%O$t%C26$1%O removes channel operator status from%C26 $2
+2
+
+Channel DeVoice
+XP_TE_CHANDEVOICE
+pevt_chandevoice_help
+%C22*%O$t%C26$1%O removes voice from%C26 $2
+2
+
+Channel Exempt
+XP_TE_CHANEXEMPT
+pevt_chanexempt_help
+%C22*%O$t$1 sets exempt on $2
+2
+
+Channel Half-Operator
+XP_TE_CHANHOP
+pevt_chanhop_help
+%C22*%O$t%C26$1%O gives channel half-operator status to%C26 $2
+2
+
+Channel INVITE
+XP_TE_CHANINVITE
+pevt_chaninvite_help
+%C22*%O$t$1 sets invite on $2
+2
+
+Channel List
+XP_TE_CHANLISTHEAD
+pevt_generic_none_help
+%UChannel          Users   Topic
+0
+
+Channel Message
+XP_TE_CHANMSG
+pevt_chanmsg_help
+%C18%H<%H$4$1%H>%H%O$t$2
+n4
+
+Channel Mode Generic
+XP_TE_CHANMODEGEN
+pevt_chanmodegen_help
+%C22*%O$t$1 sets mode $2$3 $4
+4
+
+Channel Modes
+XP_TE_CHANMODES
+pevt_chanmodes_help
+%C22*%O$t%C22Channel $1 modes: $2
+2
+
+Channel Msg Hilight
+XP_TE_HCHANMSG
+pevt_chanmsg_help
+$4%C21%B%H<%H$1%H>%H%O%C21$t$2
+n4
+
+Channel Notice
+XP_TE_CHANNOTICE
+pevt_channotice_help
+%C28-%C29$1/$2%C28-%O$t$3
+n3
+
+Channel Operator
+XP_TE_CHANOP
+pevt_chanop_help
+%C22*%O$t%C26$1%O gives channel operator status to%C26 $2
+2
+
+Channel Remove Exempt
+XP_TE_CHANRMEXEMPT
+pevt_chanrmexempt_help
+%C22*%O$t$1 removes exempt on $2
+2
+
+Channel Remove Invite
+XP_TE_CHANRMINVITE
+pevt_chanrminvite_help
+%C22*%O$t$1 removes invite on $2
+2
+
+Channel Remove Keyword
+XP_TE_CHANRMKEY
+pevt_chanrmkey_help
+%C22*%O$t$1 removes channel keyword
+1
+
+Channel Remove Limit
+XP_TE_CHANRMLIMIT
+pevt_chanrmlimit_help
+%C22*%O$t$1 removes user limit
+1
+
+Channel Set Key
+XP_TE_CHANSETKEY
+pevt_chansetkey_help
+%C22*%O$t$1 sets channel keyword to $2
+2
+
+Channel Set Limit
+XP_TE_CHANSETLIMIT
+pevt_chansetlimit_help
+%C22*%O$t$1 sets channel limit to $2
+2
+
+Channel UnBan
+XP_TE_CHANUNBAN
+pevt_chanunban_help
+%C22*%O$t$1 removes ban on $2
+2
+
+Channel Voice
+XP_TE_CHANVOICE
+pevt_chanvoice_help
+%C22*%O$t%C26$1%O gives voice to%C26 $2
+2
+
+Connected
+XP_TE_CONNECTED
+pevt_generic_none_help
+%C22*%O$t%C22Connected. Now logging in...
+0
+
+Connecting
+XP_TE_CONNECT
+pevt_connect_help
+%C22*%O$t%C22Connecting to $1 ($2) port $3%O...
+3
+
+Connection Failed
+XP_TE_CONNFAIL
+pevt_connfail_help
+%C21*%O$t%C21Connection failed. Error: $1
+1
+
+CTCP Generic
+XP_TE_CTCPGEN
+pevt_ctcpgen_help
+%C22*%O$tReceived a CTCP $1 from $2
+2
+
+CTCP Generic to Channel
+XP_TE_CTCPGENC
+pevt_ctcpgenc_help
+%C22*%O$tReceived a CTCP $1 from $2 (to $3)
+3
+
+CTCP Send
+XP_TE_CTCPSEND
+pevt_ctcpsend_help
+%C19>%O$1%C19<%O$tCTCP $2
+2
+
+CTCP Sound
+XP_TE_CTCPSND
+pevt_ctcpsnd_help
+%C22*%O$tReceived a CTCP Sound $1 from $2
+2
+
+CTCP Sound to Channel
+XP_TE_CTCPSNDC
+pevt_ctcpsnd_help
+%C22*%O$tReceived a CTCP Sound $1 from $2 (to $3)
+3
+
+DCC CHAT Abort
+XP_TE_DCCCHATABORT
+pevt_dccchatabort_help
+%C22*%O$tDCC CHAT to %C26$1%O aborted.
+1
+
+DCC CHAT Connect
+XP_TE_DCCCONCHAT
+pevt_dccchatcon_help
+%C22*%O$tDCC CHAT connection established to %C26$1 %C30[%O$2%C30]
+2
+
+DCC CHAT Failed
+XP_TE_DCCCHATF
+pevt_dccchaterr_help
+%C22*%O$tDCC CHAT to %C26$1%O lost ($4).
+4
+
+DCC CHAT Offer
+XP_TE_DCCCHATOFFER
+pevt_generic_nick_help
+%C22*%O$tReceived a DCC CHAT offer from $1
+1
+
+DCC CHAT Offering
+XP_TE_DCCCHATOFFERING
+pevt_generic_nick_help
+%C22*%O$tOffering DCC CHAT to $1
+1
+
+DCC CHAT Reoffer
+XP_TE_DCCCHATREOFFER
+pevt_generic_nick_help
+%C22*%O$tAlready offering CHAT to $1
+1
+
+DCC Conection Failed
+XP_TE_DCCCONFAIL
+pevt_dccconfail_help
+%C22*%O$tDCC $1 connect attempt to%C26 $2%O failed (err=$3).
+3
+
+DCC Generic Offer
+XP_TE_DCCGENERICOFFER
+pevt_dccgenericoffer_help
+%C22*%O$tReceived '$1%O' from $2
+2
+
+DCC Header
+XP_TE_DCCHEAD
+pevt_generic_none_help
+%C24,18 Type  To/From    Status  Size    Pos     File         
+0
+
+DCC Malformed
+XP_TE_MALFORMED
+pevt_malformed_help
+%C22*%O$tReceived a malformed DCC request from %C26$1%O.%010%C22*%O$tContents of packet: $2
+2
+
+DCC Offer
+XP_TE_DCCOFFER
+pevt_dccoffer_help
+%C22*%O$tOffering%C26 $1%O to%C26 $2
+3
+
+DCC Offer Not Valid
+XP_TE_DCCIVAL
+pevt_generic_none_help
+%C22*%O$tNo such DCC offer.
+0
+
+DCC RECV Abort
+XP_TE_DCCRECVABORT
+pevt_dccfileabort_help
+%C22*%O$tDCC RECV%C26 $2%O to%C26 $1%O aborted.
+2
+
+DCC RECV Complete
+XP_TE_DCCRECVCOMP
+pevt_dccrecvcomp_help
+%C22*%O$tDCC RECV%C26 $1%O from%C26 $3%O complete %C30[%C26$4%O cps%C30]%O.
+4
+
+DCC RECV Connect
+XP_TE_DCCCONRECV
+pevt_dcccon_help
+%C22*%O$tDCC RECV connection established to%C26 $1 %C30[%O$2%C30]
+3
+
+DCC RECV Failed
+XP_TE_DCCRECVERR
+pevt_dccrecverr_help
+%C22*%O$tDCC RECV%C26 $1%O from%C26 $3%O failed ($4).
+4
+
+DCC RECV File Open Error
+XP_TE_DCCFILEERR
+pevt_generic_file_help
+%C22*%O$tDCC RECV: Cannot open $1 for writing ($2).
+2
+
+DCC Rename
+XP_TE_DCCRENAME
+pevt_dccrename_help
+%C22*%O$tThe file%C26 $1%C already exists, saving it as%C26 $2%O instead.
+2
+
+DCC RESUME Request
+XP_TE_DCCRESUMEREQUEST
+pevt_dccresumeoffer_help
+%C22*%O$t%C26$1 %Ohas requested to resume%C26 $2 %Cfrom%C26 $3%C.
+3
+
+DCC SEND Abort
+XP_TE_DCCSENDABORT
+pevt_dccfileabort_help
+%C22*%O$tDCC SEND%C26 $2%O to%C26 $1%O aborted.
+2
+
+DCC SEND Complete
+XP_TE_DCCSENDCOMP
+pevt_dccsendcomp_help
+%C22*%O$tDCC SEND%C26 $1%O to%C26 $2%O complete %C30[%C26$3%O cps%C30]%O.
+3
+
+DCC SEND Connect
+XP_TE_DCCCONSEND
+pevt_dcccon_help
+%C22*%O$tDCC SEND connection established to%C26 $1 %C30[%O$2%C30]
+3
+
+DCC SEND Failed
+XP_TE_DCCSENDFAIL
+pevt_dccsendfail_help
+%C22*%O$tDCC SEND%C26 $1%O to%C26 $2%O failed. $3
+3
+
+DCC SEND Offer
+XP_TE_DCCSENDOFFER
+pevt_dccsendoffer_help
+%C22*%O$t%C26$1 %Ohas offered%C26 $2 %O(%C26$3 %Obytes)
+4
+
+DCC Stall
+XP_TE_DCCSTALL
+pevt_dccstall_help
+%C22*%O$tDCC $1%C26 $2 %Oto%C26 $3 %Cstalled - aborting.
+3
+
+DCC Timeout
+XP_TE_DCCTOUT
+pevt_dccstall_help
+%C22*%O$tDCC $1%C26 $2 %Oto%C26 $3 %Otimed out - aborting.
+3
+
+Delete Notify
+XP_TE_DELNOTIFY
+pevt_generic_nick_help
+%C22*%O$t$1 deleted from notify list.
+1
+
+Disconnected
+XP_TE_DISCON
+pevt_discon_help
+%C22*%O$tDisconnected ($1).
+1
+
+Found IP
+XP_TE_FOUNDIP
+pevt_foundip_help
+%C22*%O$tFound your IP: [$1]
+1
+
+Generic Message
+XP_TE_GENMSG
+pevt_genmsg_help
+$1$t$2
+n2
+
+Ignore Add
+XP_TE_IGNOREADD
+pevt_ignoreaddremove_help
+%O%C26$1%O added to ignore list.
+1
+
+Ignore Changed
+XP_TE_IGNORECHANGE
+pevt_ignoreaddremove_help
+Ignore on %C26$1%O changed.
+1
+
+Ignore Footer
+XP_TE_IGNOREFOOTER
+pevt_generic_none_help
+%C24,18                                                              
+0
+
+Ignore Header
+XP_TE_IGNOREHEADER
+pevt_generic_none_help
+%C24,18 Hostmask                  PRIV NOTI CHAN CTCP DCC  INVI UNIG 
+0
+
+Ignore Remove
+XP_TE_IGNOREREMOVE
+pevt_ignoreaddremove_help
+%O%C26$1%O removed from ignore list.
+1
+
+Ignorelist Empty
+XP_TE_IGNOREEMPTY
+pevt_generic_none_help
+  Ignore list is empty.
+0
+
+Invite
+XP_TE_INVITE
+pevt_generic_channel_help
+%C22*%O$tCannot join%C26 %B$1 %O(Channel is invite only).
+1
+
+Invited
+XP_TE_INVITED
+pevt_invited_help
+%C22*%O$tYou have been invited to%C26 $1%O by%C26 $2%C (%C26$3%C)
+3
+
+Join
+XP_TE_JOIN
+pevt_join_help
+%C19*%O$t%C19%B$1 %B($3) has joined $2
+3
+
+Keyword
+XP_TE_KEYWORD
+pevt_generic_channel_help
+%C22*%O$tCannot join%C26 %B$1 %O(Requires keyword).
+1
+
+Kick
+XP_TE_KICK
+pevt_kick_help
+%C21*%O$t%C21$1 has kicked $2 from $3 ($4%O%C21)
+4
+
+Killed
+XP_TE_KILL
+pevt_kill_help
+%C22*%O$tYou have been killed by $1 ($2%O%C22)
+2
+
+Message Send
+XP_TE_MSGSEND
+pevt_ctcpsend_help
+%C19>%O$1%C19<%O$t$2
+n2
+
+Motd
+XP_TE_MOTD
+pevt_servertext_help
+%C16*%O$t$1%O
+n1
+
+MOTD Skipped
+XP_TE_MOTDSKIP
+pevt_generic_none_help
+%C22*%O$t%C22MOTD Skipped.
+0
+
+Nick Clash
+XP_TE_NICKCLASH
+pevt_nickclash_help
+%C22*%O$t$1 already in use. Retrying with $2...
+2
+
+Nick Failed
+XP_TE_NICKFAIL
+pevt_generic_none_help
+%C22*%O$tNickname already in use. Use /NICK to try another.
+0
+
+No DCC
+XP_TE_NODCC
+pevt_generic_none_help
+%C22*%O$tNo such DCC.
+0
+
+No Running Process
+XP_TE_NOCHILD
+pevt_generic_none_help
+%C22*%O$tNo process is currently running
+0
+
+Notice
+XP_TE_NOTICE
+pevt_notice_help
+%C28-%C29$1%C28-%O$t$2
+n2
+
+Notice Send
+XP_TE_NOTICESEND
+pevt_ctcpsend_help
+%C19>%O$1%C19<%O$t$2
+n2
+
+Notify Empty
+XP_TE_NOTIFYEMPTY
+pevt_generic_none_help
+$tNotify list is empty.
+0
+
+Notify Header
+XP_TE_NOTIFYHEAD
+pevt_generic_none_help
+%C24,18 %B  Notify List                           
+0
+
+Notify Number
+XP_TE_NOTIFYNUMBER
+pevt_notifynumber_help
+%C22*%O$t$1 users in notify list.
+1
+
+Notify Offline
+XP_TE_NOTIFYOFFLINE
+pevt_generic_nick_help
+%C22*%O$tNotify: $1 is offline ($3).
+3
+
+Notify Online
+XP_TE_NOTIFYONLINE
+pevt_generic_nick_help
+%C22*%O$tNotify: $1 is online ($3).
+3
+
+Open Dialog
+XP_TE_OPENDIALOG
+pevt_generic_none_help
+
+n0
+
+Part
+XP_TE_PART
+pevt_part_help
+%C23*%O$t%C23$1 (%O%C23$2) has left $3
+3
+
+Part with Reason
+XP_TE_PARTREASON
+pevt_partreason_help
+%C23*%O$t%C23$1 (%O%C23$2) has left $3 (%O%C23%B%B$4%O%C23)
+4
+
+Ping Reply
+XP_TE_PINGREP
+pevt_pingrep_help
+%C22*%O$tPing reply from $1: $2 second(s)
+2
+
+Ping Timeout
+XP_TE_PINGTIMEOUT
+pevt_pingtimeout_help
+%C22*%O$tNo ping reply for $1 seconds, disconnecting.
+1
+
+Private Action
+XP_TE_PRIVACTION
+pevt_privmsg_help
+%C18**$t$3$1%O $2 %C18**
+n3
+
+Private Action to Dialog
+XP_TE_DPRIVACTION
+pevt_privmsg_help
+%C18*$t$3$1%O $2
+n3
+
+Private Message
+XP_TE_PRIVMSG
+pevt_privmsg_help
+%C28*%C29$3$1%C28*$t%O$2
+n3
+
+Private Message to Dialog
+XP_TE_DPRIVMSG
+pevt_privmsg_help
+%C18%H<%H$3$1%H>%H%O$t$2
+n3
+
+Process Already Running
+XP_TE_ALREADYPROCESS
+pevt_generic_none_help
+%C22*%O$tA process is already running
+0
+
+Quit
+XP_TE_QUIT
+pevt_quit_help
+%C23*%O$t%C23$1 has quit (%O%C23%B%B$2%O%C23)
+3
+
+Raw Modes
+XP_TE_RAWMODES
+pevt_rawmodes_help
+%C22*%O$t$1 sets modes%B %C30[%O$2%B%C30]
+2
+
+Receive Wallops
+XP_TE_WALLOPS
+pevt_privmsg_help
+%C28-%C29$1/Wallops%C28-%O$t$2
+2
+
+Resolving User
+XP_TE_RESOLVINGUSER
+pevt_resolvinguser_help
+%C22*%O$tLooking up IP number for%C26 $1%O...
+2
+
+Server Connected
+XP_TE_SERVERCONNECTED
+pevt_generic_none_help
+%C22*%O$t%C22Connected.
+0
+
+Server Error
+XP_TE_SERVERERROR
+pevt_servererror_help
+%C22*%O$t$1
+n1
+
+Server Lookup
+XP_TE_SERVERLOOKUP
+pevt_serverlookup_help
+%C22*%O$t%C22Looking up $1
+1
+
+Server Notice
+XP_TE_SERVNOTICE
+pevt_servertext_help
+%C22*%O$t$1
+n2
+
+Server Text
+XP_TE_SERVTEXT
+pevt_servertext_help
+%C22*%O$t$1
+n3
+
+SSL Message
+XP_TE_SSLMESSAGE
+pevt_sslmessage_help
+%C22*%O$t$1
+n2
+
+Stop Connection
+XP_TE_STOPCONNECT
+pevt_sconnect_help
+%C22*%O$tStopped previous connection attempt (pid=$1)
+1
+
+Topic
+XP_TE_TOPIC
+pevt_topic_help
+%C29*%O$t%C29Topic for $1%C %C29is: $2
+2
+
+Topic Change
+XP_TE_NEWTOPIC
+pevt_newtopic_help
+%C22*%O$t$1 has changed the topic to: $2
+3
+
+Topic Creation
+XP_TE_TOPICDATE
+pevt_topicdate_help
+%C29*%O$t%C29Topic for $1%C %C29set by $2%C %C29at $3
+3
+
+Unknown Host
+XP_TE_UKNHOST
+pevt_generic_none_help
+%C22*%O$tUnknown host. Maybe you misspelled it?
+0
+
+User Limit
+XP_TE_USERLIMIT
+pevt_generic_channel_help
+%C22*%O$tCannot join%C26 %B$1 %O(User limit reached).
+1
+
+Users On Channel
+XP_TE_USERSONCHAN
+pevt_usersonchan_help
+%C22*%O$t%C26Users on $1:%C $2
+2
+
+WhoIs Authenticated
+XP_TE_WHOIS_AUTH
+pevt_whoisauth_help
+%C22*%O$t%C28[%O$1%C28] %O$2%C27 $3
+3
+
+WhoIs Away Line
+XP_TE_WHOIS5
+pevt_whois5_help
+%C22*%O$t%C28[%O$1%C28] %Cis away %C30(%O$2%O%C30)
+2
+
+WhoIs Channel/Oper Line
+XP_TE_WHOIS2
+pevt_whois2_help
+%C22*%O$t%C28[%O$1%C28]%O $2
+2
+
+WhoIs End
+XP_TE_WHOIS6
+pevt_whois6_help
+%C22*%O$t%C28[%O$1%C28] %OEnd of WHOIS list.
+1
+
+WhoIs Identified
+XP_TE_WHOIS_ID
+pevt_whoisid_help
+%C22*%O$t%C28[%O$1%C28]%O $2
+2
+
+WhoIs Idle Line
+XP_TE_WHOIS4
+pevt_whois4_help
+%C22*%O$t%C28[%O$1%C28]%O idle%C26 $2
+2
+
+WhoIs Idle Line with Signon
+XP_TE_WHOIS4T
+pevt_whois4t_help
+%C22*%O$t%C28[%O$1%C28]%O idle%C26 $2%O, signon:%C26 $3
+3
+
+WhoIs Name Line
+XP_TE_WHOIS1
+pevt_whois1_help
+%C22*%O$t%C28[%O$1%C28] %C30(%O$2@$3%C30)%O: $4
+4
+
+WhoIs Real Host
+XP_TE_WHOIS_REALHOST
+pevt_whoisrealhost_help
+%C22*%O$t%C28[%O$1%C28] %Oreal user@host%C27 $2%O, real IP%C27 $3
+4
+
+WhoIs Server Line
+XP_TE_WHOIS3
+pevt_whois3_help
+%C22*%O$t%C28[%O$1%C28]%O $2
+2
+
+WhoIs Special
+XP_TE_WHOIS_SPECIAL
+pevt_whoisid_help
+%C22*%O$t%C28[%O$1%C28]%O $2
+3
+
+You Join
+XP_TE_UJOIN
+pevt_join_help
+%C19*%O$t%C19Now talking on $2
+3
+
+You Kicked
+XP_TE_UKICK
+pevt_ukick_help
+%C23*$tYou have been kicked from $2 by $3 ($4%O%C23)
+4
+
+You Part
+XP_TE_UPART
+pevt_part_help
+%C23*$tYou have left channel $3
+3
+
+You Part with Reason
+XP_TE_UPARTREASON
+pevt_partreason_help
+%C23*$tYou have left channel $3 (%O%C23%B%B$4%O%C23)
+4
+
+Your Action
+XP_TE_UACTION
+pevt_chanaction_help
+%C18*$t$1%O $2
+n3
+
+Your Invitation
+XP_TE_UINVITE
+pevt_uinvite_help
+%C22*%O$tYou've invited%C26 $1%O to%C26 $2%O (%C26$3%O)
+3
+
+Your Message
+XP_TE_UCHANMSG
+pevt_chanmsg_help
+%C31%H<%H$4$1%H>%H%O%C30$t$2
+n4
+
+Your Nick Changing
+XP_TE_UCHANGENICK
+pevt_uchangenick_help
+%C22*%O$tYou are now known as $2
+2
+
diff --git a/src/common/tree.c b/src/common/tree.c
new file mode 100644
index 00000000..1627bd98
--- /dev/null
+++ b/src/common/tree.c
@@ -0,0 +1,215 @@
+/*
+This is used for quick userlist insertion and lookup. It's not really
+a tree, but it could be :)
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "tree.h"
+
+#define ARRAY_GROW 32
+
+struct _tree
+{
+	int elements;
+	int array_size;
+	void **array;
+	tree_cmp_func *cmp;
+	void *data;
+};
+
+tree *
+tree_new (tree_cmp_func *cmp, void *data)
+{
+	tree *t = calloc (1, sizeof (tree));
+	t->cmp = cmp;
+	t->data = data;
+	return t;
+}
+
+void
+tree_destroy (tree *t)
+{
+	if (t)
+	{
+		if (t->array)
+			free (t->array);
+		free (t);
+	}
+}
+
+static int
+tree_find_insertion_pos (tree *t, void *key, int *done)
+{
+	int c, u, l, idx;
+
+	if (t->elements < 1)
+	{
+		*done = 1;
+		t->array[0] = key;
+		t->elements++;
+		return 0;
+	}
+
+	if (t->elements < 2)
+	{
+		*done = 1;
+		c = t->cmp (key, t->array[0], t->data);
+		if (c == 0)
+			return -1;
+		t->elements++;
+		if (c > 0)
+		{
+			t->array[1] = key;
+			return 1;
+		}
+		t->array[1] = t->array[0];
+		t->array[0] = key;
+		return 0;
+	}
+
+	*done = 0;
+
+	c = t->cmp (key, t->array[0], t->data);
+	if (c < 0)
+		return 0;	/* prepend */
+
+	c = t->cmp (key, t->array[t->elements - 1], t->data);
+	if (c > 0)
+		return t->elements;	/* append */
+
+	l = 0;
+	u = t->elements - 1;
+	while (1)
+	{
+		idx = (l + u) / 2;
+		c = t->cmp (key, t->array[idx], t->data);
+
+		if (0 > c)
+			u = idx;
+		else if (0 < c && 0 > t->cmp (key, t->array[idx+1], t->data))
+			return idx + 1;
+		else if (c == 0)
+			return -1;
+		else
+			l = idx + 1;
+	}
+}
+
+static void
+tree_insert_at_pos (tree *t, void *key, int pos)
+{
+	int post_bytes;
+
+	/* append is easy */
+	if (pos != t->elements)
+	{
+		post_bytes = (t->elements - pos) * sizeof (void *);
+		memmove (&t->array[pos + 1], &t->array[pos], post_bytes);
+	}
+
+	t->array[pos] = key;
+	t->elements++;
+}
+
+static void *
+mybsearch (const void *key, void **array, size_t nmemb,
+			  int (*compar) (const void *, const void *, void *data), void *data, int *pos)
+{
+	int l, u, idx;
+	int comparison;
+
+	l = 0;
+	u = nmemb;
+	while (l < u)
+	{
+		idx = (l + u) / 2;
+		comparison = (*compar) (key, array[idx], data);
+		if (comparison < 0)
+			u = idx;
+		else if (comparison > 0)
+			l = idx + 1;
+		else
+		{
+			*pos = idx;
+			return array[idx];
+		}
+	}
+
+	return NULL;
+}
+
+void *
+tree_find (tree *t, void *key, tree_cmp_func *cmp, void *data, int *pos)
+{
+	if (!t || !t->array)
+		return NULL;
+
+	return mybsearch (key, &t->array[0], t->elements, cmp, data, pos);
+}
+
+static void
+tree_remove_at_pos (tree *t, int pos)
+{
+	int post_bytes;
+
+	t->elements--;
+	if (pos != t->elements)
+	{
+		post_bytes = (t->elements - pos) * sizeof (void *);
+		memmove (&t->array[pos], &t->array[pos + 1], post_bytes);
+	}
+}
+
+int
+tree_remove (tree *t, void *key, int *pos)
+{
+	void *data;
+
+	data = tree_find (t, key, t->cmp, t->data, pos);
+	if (!data)
+		return 0;
+
+	tree_remove_at_pos (t, *pos);
+	return 1;
+}
+
+void
+tree_foreach (tree *t, tree_traverse_func *func, void *data)
+{
+	int j;
+
+	if (!t || !t->array)
+		return;
+
+	for (j = 0; j < t->elements; j++)
+	{
+		if (!func (t->array[j], data))
+			break;
+	}
+}
+
+int
+tree_insert (tree *t, void *key)
+{
+	int pos, done;
+
+	if (!t)
+		return -1;
+
+	if (t->array_size < t->elements + 1)
+	{
+		int new_size = t->array_size + ARRAY_GROW;
+
+		t->array = realloc (t->array, sizeof (void *) * new_size);
+		t->array_size = new_size;
+	}
+
+	pos = tree_find_insertion_pos (t, key, &done);
+	if (!done && pos != -1)
+		tree_insert_at_pos (t, key, pos);
+
+	return pos;
+}
diff --git a/src/common/tree.h b/src/common/tree.h
new file mode 100644
index 00000000..b1b66aa9
--- /dev/null
+++ b/src/common/tree.h
@@ -0,0 +1,16 @@
+#ifndef XCHAT_TREE_H
+#define XCHAT_TREE_H
+
+typedef struct _tree tree;
+
+typedef int (tree_cmp_func) (const void *keya, const void *keyb, void *data);
+typedef int (tree_traverse_func) (const void *key, void *data);
+
+tree *tree_new (tree_cmp_func *cmp, void *data);
+void tree_destroy (tree *t);
+void *tree_find (tree *t, void *key, tree_cmp_func *cmp, void *data, int *pos);
+int tree_remove (tree *t, void *key, int *pos);
+void tree_foreach (tree *t, tree_traverse_func *func, void *data);
+int tree_insert (tree *t, void *key);
+
+#endif
diff --git a/src/common/url.c b/src/common/url.c
new file mode 100644
index 00000000..92aeab0a
--- /dev/null
+++ b/src/common/url.c
@@ -0,0 +1,280 @@
+/* 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 <ctype.h>
+#include "xchat.h"
+#include "cfgfiles.h"
+#include "fe.h"
+#include "tree.h"
+#include "url.h"
+#ifdef HAVE_STRINGS_H
+#include <strings.h>
+#endif
+
+void *url_tree = NULL;
+
+
+static int
+url_free (char *url, void *data)
+{
+	free (url);
+	return TRUE;
+}
+
+void
+url_clear (void)
+{
+	tree_foreach (url_tree, (tree_traverse_func *)url_free, NULL);
+	tree_destroy (url_tree);
+	url_tree = NULL;
+}
+
+static int
+url_save_cb (char *url, FILE *fd)
+{
+	fprintf (fd, "%s\n", url);
+	return TRUE;
+}
+
+void
+url_save (const char *fname, const char *mode, gboolean fullpath)
+{
+	FILE *fd;
+
+	if (fullpath)
+		fd = xchat_fopen_file (fname, mode, XOF_FULLPATH);
+	else
+		fd = xchat_fopen_file (fname, mode, 0);
+	if (fd == NULL)
+		return;
+
+	tree_foreach (url_tree, (tree_traverse_func *)url_save_cb, fd);
+	fclose (fd);
+}
+
+void
+url_autosave (void)
+{
+	url_save ("url.save", "a", FALSE);
+}
+
+static int
+url_find (char *urltext)
+{
+	int pos;
+
+	if (tree_find (url_tree, urltext, (tree_cmp_func *)strcasecmp, NULL, &pos))
+		return 1;
+	return 0;
+}
+
+static void
+url_add (char *urltext, int len)
+{
+	char *data = malloc (len + 1);
+	if (!data)
+		return;
+	memcpy (data, urltext, len);
+	data[len] = 0;
+
+	if (data[len - 1] == '.')	/* chop trailing dot */
+	{
+		len--;
+		data[len] = 0;
+	}
+	if (data[len - 1] == ')')	/* chop trailing ) */
+		data[len - 1] = 0;
+
+	if (url_find (data))
+	{
+		free (data);
+		return;
+	}
+
+	if (!url_tree)
+		url_tree = tree_new ((tree_cmp_func *)strcasecmp, NULL);
+
+	tree_insert (url_tree, data);
+	fe_url_add (data);
+}
+
+/* check if a word is clickable. This is called on mouse motion events, so
+   keep it FAST! This new version was found to be almost 3x faster than
+   2.4.4 release. */
+
+int
+url_check_word (char *word, int len)
+{
+#define D(x) (x), ((sizeof (x)) - 1)
+	static const struct {
+		const char *s;
+		int len;
+	}
+	prefix[] = {
+		{ D("irc.") },
+		{ D("ftp.") },
+		{ D("www.") },
+		{ D("irc://") },
+		{ D("ftp://") },
+		{ D("http://") },
+		{ D("https://") },
+		{ D("file://") },
+		{ D("rtsp://") },
+		{ D("ut2004://") },
+	},
+	suffix[] = {
+		{ D(".org") },
+		{ D(".net") },
+		{ D(".com") },
+		{ D(".edu") },
+		{ D(".html") },
+		{ D(".info") },
+		{ D(".name") },
+	};
+#undef D
+	const char *at, *dot;
+	int i, dots;
+
+	if (len > 1 && word[1] == '#' && strchr("@+^%*#", word[0]))
+		return WORD_CHANNEL;
+
+	if ((word[0] == '#' || word[0] == '&') && word[1] != '#' && word[1] != 0)
+		return WORD_CHANNEL;
+
+	for (i = 0; i < G_N_ELEMENTS(prefix); i++)
+	{
+		int l;
+
+		l = prefix[i].len;
+		if (len > l)
+		{
+			int j;
+
+			/* This is pretty much strncasecmp(). */
+			for (j = 0; j < l; j++)
+			{
+				unsigned char c = word[j];
+				if (tolower(c) != prefix[i].s[j])
+					break;
+			}
+			if (j == l)
+				return WORD_URL;
+		}
+	}
+
+	at = strchr (word, '@');	  /* check for email addy */
+	dot = strrchr (word, '.');
+	if (at && dot)
+	{
+		if (at < dot)
+		{
+			if (strchr (word, '*'))
+				return WORD_HOST;
+			else
+				return WORD_EMAIL;
+		}
+	}
+ 
+	/* check if it's an IP number */
+	dots = 0;
+	for (i = 0; i < len; i++)
+	{
+		if (word[i] == '.' && i > 0)
+			dots++;	/* allow 127.0.0.1:80 */
+		else if (!isdigit ((unsigned char) word[i]) && word[i] != ':')
+		{
+			dots = 0;
+			break;
+		}
+	}
+	if (dots == 3)
+		return WORD_HOST;
+
+	if (len > 5)
+	{
+		for (i = 0; i < G_N_ELEMENTS(suffix); i++)
+		{
+			int l;
+
+			l = suffix[i].len;
+			if (len > l)
+			{
+				const unsigned char *p = &word[len - l];
+				int j;
+
+				/* This is pretty much strncasecmp(). */
+				for (j = 0; j < l; j++)
+				{
+					if (tolower(p[j]) != suffix[i].s[j])
+						break;
+				}
+				if (j == l)
+					return WORD_HOST;
+			}
+		}
+
+		if (word[len - 3] == '.' &&
+			 isalpha ((unsigned char) word[len - 2]) &&
+				isalpha ((unsigned char) word[len - 1]))
+			return WORD_HOST;
+	}
+
+	return 0;
+}
+
+void
+url_check_line (char *buf, int len)
+{
+	char *po = buf;
+	char *start;
+	int wlen;
+
+	if (buf[0] == ':' && buf[1] != 0)
+		po++;
+
+	start = po;
+
+	/* check each "word" (space separated) */
+	while (1)
+	{
+		switch (po[0])
+		{
+		case 0:
+		case ' ':
+			wlen = po - start;
+			if (wlen > 2)
+			{
+				if (url_check_word (start, wlen) == WORD_URL)
+				{
+					url_add (start, wlen);
+				}
+			}
+			if (po[0] == 0)
+				return;
+			po++;
+			start = po;
+			break;
+
+		default:
+			po++;
+		}
+	}
+}
diff --git a/src/common/url.h b/src/common/url.h
new file mode 100644
index 00000000..487a2007
--- /dev/null
+++ b/src/common/url.h
@@ -0,0 +1,19 @@
+#ifndef XCHAT_URL_H
+#define XCHAT_URL_H
+
+extern void *url_tree;
+
+#define WORD_URL     1
+#define WORD_NICK    2
+#define WORD_CHANNEL 3
+#define WORD_HOST    4
+#define WORD_EMAIL   5
+#define WORD_DIALOG  -1
+
+void url_clear (void);
+void url_save (const char *fname, const char *mode, gboolean fullpath);
+void url_autosave (void);
+int url_check_word (char *word, int len);
+void url_check_line (char *buf, int len);
+
+#endif
diff --git a/src/common/userlist.c b/src/common/userlist.c
new file mode 100644
index 00000000..18ac4bed
--- /dev/null
+++ b/src/common/userlist.c
@@ -0,0 +1,454 @@
+/* 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 "xchat.h"
+#include "modes.h"
+#include "fe.h"
+#include "notify.h"
+#include "tree.h"
+#include "xchatc.h"
+#include "util.h"
+
+
+static int
+nick_cmp_az_ops (server *serv, struct User *user1, struct User *user2)
+{
+	unsigned int access1 = user1->access;
+	unsigned int access2 = user2->access;
+	int pos;
+
+	if (access1 != access2)
+	{
+		for (pos = 0; pos < USERACCESS_SIZE; pos++)
+		{
+			if ((access1&(1<<pos)) && (access2&(1<<pos)))
+				break;
+			if ((access1&(1<<pos)) && !(access2&(1<<pos)))
+				return -1;
+			if (!(access1&(1<<pos)) && (access2&(1<<pos)))
+				return 1;
+		}
+	}
+
+	return serv->p_cmp (user1->nick, user2->nick);
+}
+
+static int
+nick_cmp_alpha (struct User *user1, struct User *user2, server *serv)
+{
+	return serv->p_cmp (user1->nick, user2->nick);
+}
+
+static int
+nick_cmp (struct User *user1, struct User *user2, server *serv)
+{
+	switch (prefs.userlist_sort)
+	{
+	case 0:
+		return nick_cmp_az_ops (serv, user1, user2);
+	case 1:
+		return serv->p_cmp (user1->nick, user2->nick);
+	case 2:
+		return -1 * nick_cmp_az_ops (serv, user1, user2);
+	case 3:
+		return -1 * serv->p_cmp (user1->nick, user2->nick);
+	default:
+		return -1;
+	}
+}
+
+/*
+ insert name in appropriate place in linked list. Returns row number or:
+  -1: duplicate
+*/
+
+static int
+userlist_insertname (session *sess, struct User *newuser)
+{
+	if (!sess->usertree)
+	{
+		sess->usertree = tree_new ((tree_cmp_func *)nick_cmp, sess->server);
+		sess->usertree_alpha = tree_new ((tree_cmp_func *)nick_cmp_alpha, sess->server);
+	}
+
+	tree_insert (sess->usertree_alpha, newuser);
+	return tree_insert (sess->usertree, newuser);
+}
+
+void
+userlist_set_away (struct session *sess, char *nick, unsigned int away)
+{
+	struct User *user;
+
+	user = userlist_find (sess, nick);
+	if (user)
+	{
+		if (user->away != away)
+		{
+			user->away = away;
+			/* rehash GUI */
+			fe_userlist_rehash (sess, user);
+			if (away)
+				fe_userlist_update (sess, user);
+		}
+	}
+}
+
+int
+userlist_add_hostname (struct session *sess, char *nick, char *hostname,
+							  char *realname, char *servername, unsigned int away)
+{
+	struct User *user;
+
+	user = userlist_find (sess, nick);
+	if (user)
+	{
+		if (!user->hostname && hostname)
+			user->hostname = strdup (hostname);
+		if (!user->realname && realname)
+			user->realname = strdup (realname);
+		if (!user->servername && servername)
+			user->servername = strdup (servername);
+
+		if (away != 0xff)
+		{
+			if (prefs.showhostname_in_userlist || user->away != away)
+			{
+				user->away = away;
+				fe_userlist_rehash (sess, user);
+			}
+			user->away = away;
+		}
+
+		fe_userlist_update (sess, user);
+
+		return 1;
+	}
+	return 0;
+}
+
+static int
+free_user (struct User *user, gpointer data)
+{
+	if (user->realname)
+		free (user->realname);
+	if (user->hostname)
+		free (user->hostname);
+	if (user->servername)
+		free (user->servername);
+	free (user);
+
+	return TRUE;
+}
+
+void
+userlist_free (session *sess)
+{
+	tree_foreach (sess->usertree, (tree_traverse_func *)free_user, NULL);
+	tree_destroy (sess->usertree);
+	tree_destroy (sess->usertree_alpha);
+
+	sess->usertree = NULL;
+	sess->usertree_alpha = NULL;
+	sess->me = NULL;
+
+	sess->ops = 0;
+	sess->hops = 0;
+	sess->voices = 0;
+	sess->total = 0;
+}
+
+void
+userlist_clear (session *sess)
+{
+	fe_userlist_clear (sess);
+	userlist_free (sess);
+	fe_userlist_numbers (sess);
+}
+
+static int
+find_cmp (const char *name, struct User *user, server *serv)
+{
+	return serv->p_cmp ((char *)name, user->nick);
+}
+
+struct User *
+userlist_find (struct session *sess, char *name)
+{
+	int pos;
+
+	if (sess->usertree_alpha)
+		return tree_find (sess->usertree_alpha, name,
+								(tree_cmp_func *)find_cmp, sess->server, &pos);
+
+	return NULL;
+}
+
+struct User *
+userlist_find_global (struct server *serv, char *name)
+{
+	struct User *user;
+	session *sess;
+	GSList *list = sess_list;
+	while (list)
+	{
+		sess = (session *) list->data;
+		if (sess->server == serv)
+		{
+			user = userlist_find (sess, name);
+			if (user)
+				return user;
+		}
+		list = list->next;
+	}
+	return 0;
+}
+
+static void
+update_counts (session *sess, struct User *user, char prefix,
+					int level, int offset)
+{
+	switch (prefix)
+	{
+	case '@':
+		user->op = level;
+		sess->ops += offset;
+		break;
+	case '%':
+		user->hop = level;
+		sess->hops += offset;
+		break;
+	case '+':
+		user->voice = level;
+		sess->voices += offset;
+		break;
+	}
+}
+
+void
+userlist_update_mode (session *sess, char *name, char mode, char sign)
+{
+	int access;
+	int offset = 0;
+	int level;
+	int pos;
+	char prefix;
+	struct User *user;
+
+	user = userlist_find (sess, name);
+	if (!user)
+		return;
+
+	/* remove from binary trees, before we loose track of it */
+	tree_remove (sess->usertree, user, &pos);
+	tree_remove (sess->usertree_alpha, user, &pos);
+
+	/* which bit number is affected? */
+	access = mode_access (sess->server, mode, &prefix);
+
+	if (sign == '+')
+	{
+		level = TRUE;
+		if (!(user->access & (1 << access)))
+		{
+			offset = 1;
+			user->access |= (1 << access);
+		}
+	} else
+	{
+		level = FALSE;
+		if (user->access & (1 << access))
+		{
+			offset = -1;
+			user->access &= ~(1 << access);
+		}
+	}
+
+	/* now what is this users highest prefix? e.g. @ for ops */
+	user->prefix[0] = get_nick_prefix (sess->server, user->access);
+
+	/* update the various counts using the CHANGED prefix only */
+	update_counts (sess, user, prefix, level, offset);
+
+	/* insert it back into its new place */
+	tree_insert (sess->usertree_alpha, user);
+	pos = tree_insert (sess->usertree, user);
+
+	/* let GTK move it too */
+	fe_userlist_move (sess, user, pos);
+	fe_userlist_numbers (sess);
+}
+
+int
+userlist_change (struct session *sess, char *oldname, char *newname)
+{
+	struct User *user = userlist_find (sess, oldname);
+	int pos;
+
+	if (user)
+	{
+		tree_remove (sess->usertree, user, &pos);
+		tree_remove (sess->usertree_alpha, user, &pos);
+
+		safe_strcpy (user->nick, newname, NICKLEN);
+
+		tree_insert (sess->usertree_alpha, user);
+
+		fe_userlist_move (sess, user, tree_insert (sess->usertree, user));
+		fe_userlist_numbers (sess);
+
+		return 1;
+	}
+
+	return 0;
+}
+
+int
+userlist_remove (struct session *sess, char *name)
+{
+	struct User *user;
+	int pos;
+
+	user = userlist_find (sess, name);
+	if (!user)
+		return FALSE;
+
+	if (user->voice)
+		sess->voices--;
+	if (user->op)
+		sess->ops--;
+	if (user->hop)
+		sess->hops--;
+	sess->total--;
+	fe_userlist_numbers (sess);
+	fe_userlist_remove (sess, user);
+
+	if (user == sess->me)
+		sess->me = NULL;
+
+	tree_remove (sess->usertree, user, &pos);
+	tree_remove (sess->usertree_alpha, user, &pos);
+	free_user (user, NULL);
+
+	return TRUE;
+}
+
+void
+userlist_add (struct session *sess, char *name, char *hostname)
+{
+	struct User *user;
+	int row, prefix_chars;
+	unsigned int acc;
+
+	acc = nick_access (sess->server, name, &prefix_chars);
+
+	notify_set_online (sess->server, name + prefix_chars);
+
+	user = malloc (sizeof (struct User));
+	memset (user, 0, sizeof (struct User));
+
+	user->access = acc;
+
+	/* assume first char is the highest level nick prefix */
+	if (prefix_chars)
+		user->prefix[0] = name[0];
+
+	/* add it to our linked list */
+	if (hostname)
+		user->hostname = strdup (hostname);
+	safe_strcpy (user->nick, name + prefix_chars, NICKLEN);
+	/* is it me? */
+	if (!sess->server->p_cmp (user->nick, sess->server->nick))
+		user->me = TRUE;
+	row = userlist_insertname (sess, user);
+
+	/* duplicate? some broken servers trigger this */
+	if (row == -1)
+	{
+		if (user->hostname)
+			free (user->hostname);
+		free (user);
+		return;
+	}
+
+	sess->total++;
+
+	/* most ircds don't support multiple modechars infront of the nickname
+      for /NAMES - though they should. */
+	while (prefix_chars)
+	{
+		update_counts (sess, user, name[0], TRUE, 1);
+		name++;
+		prefix_chars--;
+	}
+
+	if (user->me)
+		sess->me = user;
+
+	fe_userlist_insert (sess, user, row, FALSE);
+	fe_userlist_numbers (sess);
+}
+
+static int
+rehash_cb (struct User *user, session *sess)
+{
+	fe_userlist_rehash (sess, user);
+	return TRUE;
+}
+
+void
+userlist_rehash (session *sess)
+{
+	tree_foreach (sess->usertree_alpha, (tree_traverse_func *)rehash_cb, sess);
+}
+
+static int
+flat_cb (struct User *user, GSList **list)
+{
+	*list = g_slist_prepend (*list, user);
+	return TRUE;
+}
+
+GSList *
+userlist_flat_list (session *sess)
+{
+	GSList *list = NULL;
+
+	tree_foreach (sess->usertree_alpha, (tree_traverse_func *)flat_cb, &list);
+	return g_slist_reverse (list);
+}
+
+static int
+double_cb (struct User *user, GList **list)
+{
+	*list = g_list_prepend(*list, user);
+	return TRUE;
+}
+
+GList *
+userlist_double_list(session *sess)
+{
+	GList *list = NULL;
+
+	tree_foreach (sess->usertree_alpha, (tree_traverse_func *)double_cb, &list);
+	return list;
+}
diff --git a/src/common/userlist.h b/src/common/userlist.h
new file mode 100644
index 00000000..28831acd
--- /dev/null
+++ b/src/common/userlist.h
@@ -0,0 +1,41 @@
+#include <time.h>
+
+#ifndef XCHAT_USERLIST_H
+#define XCHAT_USERLIST_H
+
+struct User
+{
+	char nick[NICKLEN];
+	char *hostname;
+	char *realname;
+	char *servername;
+	time_t lasttalk;
+	unsigned int access;	/* axs bit field */
+	char prefix[2]; /* @ + % */
+	unsigned int op:1;
+	unsigned int hop:1;
+	unsigned int voice:1;
+	unsigned int me:1;
+	unsigned int away:1;
+	unsigned int selected:1;
+};
+
+#define USERACCESS_SIZE 12
+
+int userlist_add_hostname (session *sess, char *nick,
+									char *hostname, char *realname,
+									char *servername, unsigned int away);
+void userlist_set_away (session *sess, char *nick, unsigned int away);
+struct User *userlist_find (session *sess, char *name);
+struct User *userlist_find_global (server *serv, char *name);
+void userlist_clear (session *sess);
+void userlist_free (session *sess);
+void userlist_add (session *sess, char *name, char *hostname);
+int userlist_remove (session *sess, char *name);
+int userlist_change (session *sess, char *oldname, char *newname);
+void userlist_update_mode (session *sess, char *name, char mode, char sign);
+GSList *userlist_flat_list (session *sess);
+GList *userlist_double_list (session *sess);
+void userlist_rehash (session *sess);
+
+#endif
diff --git a/src/common/util.c b/src/common/util.c
new file mode 100644
index 00000000..49517ec4
--- /dev/null
+++ b/src/common/util.c
@@ -0,0 +1,1729 @@
+/* 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
+ */
+
+#define __APPLE_API_STRICT_CONFORMANCE
+
+#define _FILE_OFFSET_BITS 64
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef WIN32
+#include <sys/timeb.h>
+#include <process.h>
+#else
+#include <sys/types.h>
+#include <pwd.h>
+#include <sys/time.h>
+#include <sys/utsname.h>
+#endif
+#include <fcntl.h>
+#include <dirent.h>
+#include <errno.h>
+#include "xchat.h"
+#include "xchatc.h"
+#include <glib/gmarkup.h>
+#include <ctype.h>
+#include "util.h"
+#include "../../config.h"
+
+#define WANTSOCKET
+#include "inet.h"
+
+#if defined (USING_FREEBSD) || defined (__APPLE__)
+#include <sys/sysctl.h>
+#endif
+#ifdef SOCKS
+#include <socks.h>
+#endif
+
+#ifndef HAVE_SNPRINTF
+#define snprintf g_snprintf
+#endif
+
+#ifdef USE_DEBUG
+
+#undef free
+#undef malloc
+#undef realloc
+#undef strdup
+
+int current_mem_usage;
+
+struct mem_block
+{
+	char *file;
+	void *buf;
+	int size;
+	int line;
+	int total;
+	struct mem_block *next;
+};
+
+struct mem_block *mroot = NULL;
+
+void *
+xchat_malloc (int size, char *file, int line)
+{
+	void *ret;
+	struct mem_block *new;
+
+	current_mem_usage += size;
+	ret = malloc (size);
+	if (!ret)
+	{
+		printf ("Out of memory! (%d)\n", current_mem_usage);
+		exit (255);
+	}
+
+	new = malloc (sizeof (struct mem_block));
+	new->buf = ret;
+	new->size = size;
+	new->next = mroot;
+	new->line = line;
+	new->file = strdup (file);
+	mroot = new;
+
+	printf ("%s:%d Malloc'ed %d bytes, now \033[35m%d\033[m\n", file, line,
+				size, current_mem_usage);
+
+	return ret;
+}
+
+void *
+xchat_realloc (char *old, int len, char *file, int line)
+{
+	char *ret;
+
+	ret = xchat_malloc (len, file, line);
+	if (ret)
+	{
+		strcpy (ret, old);
+		xchat_dfree (old, file, line);
+	}
+	return ret;
+}
+
+void *
+xchat_strdup (char *str, char *file, int line)
+{
+	void *ret;
+	struct mem_block *new;
+	int size;
+
+	size = strlen (str) + 1;
+	current_mem_usage += size;
+	ret = malloc (size);
+	if (!ret)
+	{
+		printf ("Out of memory! (%d)\n", current_mem_usage);
+		exit (255);
+	}
+	strcpy (ret, str);
+
+	new = malloc (sizeof (struct mem_block));
+	new->buf = ret;
+	new->size = size;
+	new->next = mroot;
+	new->line = line;
+	new->file = strdup (file);
+	mroot = new;
+
+	printf ("%s:%d strdup (\"%-.40s\") size: %d, total: \033[35m%d\033[m\n",
+				file, line, str, size, current_mem_usage);
+
+	return ret;
+}
+
+void
+xchat_mem_list (void)
+{
+	struct mem_block *cur, *p;
+	GSList *totals = 0;
+	GSList *list;
+
+	cur = mroot;
+	while (cur)
+	{
+		list = totals;
+		while (list)
+		{
+			p = list->data;
+			if (p->line == cur->line &&
+					strcmp (p->file, cur->file) == 0)
+			{
+				p->total += p->size;
+				break;
+			}
+			list = list->next;
+		}
+		if (!list)
+		{
+			cur->total = cur->size;
+			totals = g_slist_prepend (totals, cur);
+		}
+		cur = cur->next;
+	}
+
+	fprintf (stderr, "file              line   size    num  total\n");  
+	list = totals;
+	while (list)
+	{
+		cur = list->data;
+		fprintf (stderr, "%-15.15s %6d %6d %6d %6d\n", cur->file, cur->line,
+					cur->size, cur->total/cur->size, cur->total);
+		list = list->next;
+	}
+}
+
+void
+xchat_dfree (void *buf, char *file, int line)
+{
+	struct mem_block *cur, *last;
+
+	if (buf == NULL)
+	{
+		printf ("%s:%d \033[33mTried to free NULL\033[m\n", file, line);
+		return;
+	}
+
+	last = NULL;
+	cur = mroot;
+	while (cur)
+	{
+		if (buf == cur->buf)
+			break;
+		last = cur;
+		cur = cur->next;
+	}
+	if (cur == NULL)
+	{
+		printf ("%s:%d \033[31mTried to free unknown block %lx!\033[m\n",
+				  file, line, (unsigned long) buf);
+		/*      abort(); */
+		free (buf);
+		return;
+	}
+	current_mem_usage -= cur->size;
+	printf ("%s:%d Free'ed %d bytes, usage now \033[35m%d\033[m\n",
+				file, line, cur->size, current_mem_usage);
+	if (last)
+		last->next = cur->next;
+	else
+		mroot = cur->next;
+	free (cur->file);
+	free (cur);
+}
+
+#define malloc(n) xchat_malloc(n, __FILE__, __LINE__)
+#define realloc(n, m) xchat_realloc(n, m, __FILE__, __LINE__)
+#define free(n) xchat_dfree(n, __FILE__, __LINE__)
+#define strdup(n) xchat_strdup(n, __FILE__, __LINE__)
+
+#endif /* MEMORY_DEBUG */
+
+char *
+file_part (char *file)
+{
+	char *filepart = file;
+	if (!file)
+		return "";
+	while (1)
+	{
+		switch (*file)
+		{
+		case 0:
+			return (filepart);
+		case '/':
+#ifdef WIN32
+		case '\\':
+#endif
+			filepart = file + 1;
+			break;
+		}
+		file++;
+	}
+}
+
+void
+path_part (char *file, char *path, int pathlen)
+{
+	unsigned char t;
+	char *filepart = file_part (file);
+	t = *filepart;
+	*filepart = 0;
+	safe_strcpy (path, file, pathlen);
+	*filepart = t;
+}
+
+char *				/* like strstr(), but nocase */
+nocasestrstr (const char *s, const char *wanted)
+{
+	register const int len = strlen (wanted);
+
+	if (len == 0)
+		return (char *)s;
+	while (rfc_tolower(*s) != rfc_tolower(*wanted) || strncasecmp (s, wanted, len))
+		if (*s++ == '\0')
+			return (char *)NULL;
+	return (char *)s;
+}
+
+char *
+errorstring (int err)
+{
+	switch (err)
+	{
+	case -1:
+		return "";
+	case 0:
+		return _("Remote host closed socket");
+#ifndef WIN32
+	}
+#else
+	case WSAECONNREFUSED:
+		return _("Connection refused");
+	case WSAENETUNREACH:
+	case WSAEHOSTUNREACH:
+		return _("No route to host");
+	case WSAETIMEDOUT:
+		return _("Connection timed out");
+	case WSAEADDRNOTAVAIL:
+		return _("Cannot assign that address");
+	case WSAECONNRESET:
+		return _("Connection reset by peer");
+	}
+
+	/* can't use strerror() on Winsock errors! */
+	if (err >= WSABASEERR)
+	{
+		static char tbuf[384];
+		OSVERSIONINFO osvi;
+
+		osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
+		GetVersionEx (&osvi);
+
+		/* FormatMessage works on WSA*** errors starting from Win2000 */
+		if (osvi.dwMajorVersion >= 5)
+		{
+			if (FormatMessageA (FORMAT_MESSAGE_FROM_SYSTEM |
+									  FORMAT_MESSAGE_IGNORE_INSERTS |
+									  FORMAT_MESSAGE_MAX_WIDTH_MASK,
+									  NULL, err,
+									  MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
+									  tbuf, sizeof (tbuf), NULL))
+			{
+				int len;
+				char *utf;
+
+				tbuf[sizeof (tbuf) - 1] = 0;
+				len = strlen (tbuf);
+				if (len >= 2)
+					tbuf[len - 2] = 0;	/* remove the cr-lf */
+
+				/* now convert to utf8 */
+				utf = g_locale_to_utf8 (tbuf, -1, 0, 0, 0);
+				if (utf)
+				{
+					safe_strcpy (tbuf, utf, sizeof (tbuf));
+					g_free (utf);
+					return tbuf;
+				}
+			}
+		}	/* ! if (osvi.dwMajorVersion >= 5) */
+
+		/* fallback to error number */
+		sprintf (tbuf, "%s %d", _("Error"), err);
+		return tbuf;
+	} /* ! if (err >= WSABASEERR) */
+#endif	/* ! WIN32 */
+
+	return strerror (err);
+}
+
+int
+waitline (int sok, char *buf, int bufsize, int use_recv)
+{
+	int i = 0;
+
+	while (1)
+	{
+		if (use_recv)
+		{
+			if (recv (sok, &buf[i], 1, 0) < 1)
+				return -1;
+		} else
+		{
+			if (read (sok, &buf[i], 1) < 1)
+				return -1;
+		}
+		if (buf[i] == '\n' || bufsize == i + 1)
+		{
+			buf[i] = 0;
+			return i;
+		}
+		i++;
+	}
+}
+
+/* checks for "~" in a file and expands */
+
+char *
+expand_homedir (char *file)
+{
+#ifndef WIN32
+	char *ret, *user;
+	struct passwd *pw;
+
+	if (*file == '~')
+	{
+		if (file[1] != '\0' && file[1] != '/')
+		{
+			user = strdup(file);
+			if (strchr(user,'/') != NULL)
+				*(strchr(user,'/')) = '\0';
+			if ((pw = getpwnam(user + 1)) == NULL)
+			{
+				free(user);
+				return strdup(file);
+			}
+			free(user);
+			user = strchr(file, '/') != NULL ? strchr(file,'/') : file;
+			ret = malloc(strlen(user) + strlen(pw->pw_dir) + 1);
+			strcpy(ret, pw->pw_dir);
+			strcat(ret, user);
+		}
+		else
+		{
+			ret = malloc (strlen (file) + strlen (g_get_home_dir ()) + 1);
+			sprintf (ret, "%s%s", g_get_home_dir (), file + 1);
+		}
+		return ret;
+	}
+#endif
+	return strdup (file);
+}
+
+gchar *
+strip_color (const char *text, int len, int flags)
+{
+	char *new_str;
+
+	if (len == -1)
+		len = strlen (text);
+
+	new_str = g_malloc (len + 2);
+	strip_color2 (text, len, new_str, flags);
+
+	if (flags & STRIP_ESCMARKUP)
+	{
+		char *esc = g_markup_escape_text (new_str, -1);
+		g_free (new_str);
+		return esc;
+	}
+
+	return new_str;
+}
+
+/* CL: strip_color2 strips src and writes the output at dst; pass the same pointer
+	in both arguments to strip in place. */
+int
+strip_color2 (const char *src, int len, char *dst, int flags)
+{
+	int rcol = 0, bgcol = 0;
+	char *start = dst;
+
+	if (len == -1) len = strlen (src);
+	while (len-- > 0)
+	{
+		if (rcol > 0 && (isdigit ((unsigned char)*src) ||
+			(*src == ',' && isdigit ((unsigned char)src[1]) && !bgcol)))
+		{
+			if (src[1] != ',') rcol--;
+			if (*src == ',')
+			{
+				rcol = 2;
+				bgcol = 1;
+			}
+		} else
+		{
+			rcol = bgcol = 0;
+			switch (*src)
+			{
+			case '\003':			  /*ATTR_COLOR: */
+				if (!(flags & STRIP_COLOR)) goto pass_char;
+				rcol = 2;
+				break;
+			case HIDDEN_CHAR:	/* CL: invisible text (for event formats only) */	/* this takes care of the topic */
+				if (!(flags & STRIP_HIDDEN)) goto pass_char;
+				break;
+			case '\007':			  /*ATTR_BEEP: */
+			case '\017':			  /*ATTR_RESET: */
+			case '\026':			  /*ATTR_REVERSE: */
+			case '\002':			  /*ATTR_BOLD: */
+			case '\037':			  /*ATTR_UNDERLINE: */
+			case '\035':			  /*ATTR_ITALICS: */
+				if (!(flags & STRIP_ATTRIB)) goto pass_char;
+				break;
+			default:
+			pass_char:
+				*dst++ = *src;
+			}
+		}
+		src++;
+	}
+	*dst = 0;
+
+	return (int) (dst - start);
+}
+
+int
+strip_hidden_attribute (char *src, char *dst)
+{
+	int len = 0;
+	while (*src != '\000')
+	{
+		if (*src != HIDDEN_CHAR)
+		{
+			*dst++ = *src;
+			len++;
+		}
+		src++;
+	}
+	return len;
+}
+
+#if defined (USING_LINUX) || defined (USING_FREEBSD) || defined (__APPLE__)
+
+static void
+get_cpu_info (double *mhz, int *cpus)
+{
+
+#ifdef USING_LINUX
+
+	char buf[256];
+	int fh;
+
+	*mhz = 0;
+	*cpus = 0;
+
+	fh = open ("/proc/cpuinfo", O_RDONLY);	/* linux 2.2+ only */
+	if (fh == -1)
+	{
+		*cpus = 1;
+		return;
+	}
+
+	while (1)
+	{
+		if (waitline (fh, buf, sizeof buf, FALSE) < 0)
+			break;
+		if (!strncmp (buf, "cycle frequency [Hz]\t:", 22))	/* alpha */
+		{
+			*mhz = atoi (buf + 23) / 1000000;
+		} else if (!strncmp (buf, "cpu MHz\t\t:", 10))	/* i386 */
+		{
+			*mhz = atof (buf + 11) + 0.5;
+		} else if (!strncmp (buf, "clock\t\t:", 8))	/* PPC */
+		{
+			*mhz = atoi (buf + 9);
+		} else if (!strncmp (buf, "processor\t", 10))
+		{
+			(*cpus)++;
+		}
+	}
+	close (fh);
+	if (!*cpus)
+		*cpus = 1;
+
+#endif
+#ifdef USING_FREEBSD
+
+	int mib[2], ncpu;
+	u_long freq;
+	size_t len;
+
+	freq = 0;
+	*mhz = 0;
+	*cpus = 0;
+
+	mib[0] = CTL_HW;
+	mib[1] = HW_NCPU;
+
+	len = sizeof(ncpu);
+	sysctl(mib, 2, &ncpu, &len, NULL, 0);
+
+	len = sizeof(freq);
+	sysctlbyname("machdep.tsc_freq", &freq, &len, NULL, 0);
+
+	*cpus = ncpu;
+	*mhz = (freq / 1000000);
+
+#endif
+#ifdef __APPLE__
+
+	int mib[2], ncpu;
+	unsigned long long freq;
+	size_t len;
+
+	freq = 0;
+	*mhz = 0;
+	*cpus = 0;
+
+	mib[0] = CTL_HW;
+	mib[1] = HW_NCPU;
+
+	len = sizeof(ncpu);
+	sysctl(mib, 2, &ncpu, &len, NULL, 0);
+
+	len = sizeof(freq);
+        sysctlbyname("hw.cpufrequency", &freq, &len, NULL, 0);
+
+	*cpus = ncpu;
+	*mhz = (freq / 1000000);
+
+#endif
+
+}
+#endif
+
+#ifdef WIN32
+
+static int
+get_mhz (void)
+{
+	HKEY hKey;
+	int result, data, dataSize;
+
+	if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, "Hardware\\Description\\System\\"
+		"CentralProcessor\\0", 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
+	{
+		dataSize = sizeof (data);
+		result = RegQueryValueEx (hKey, "~MHz", 0, 0, (LPBYTE)&data, &dataSize);
+		RegCloseKey (hKey);
+		if (result == ERROR_SUCCESS)
+			return data;
+	}
+	return 0;	/* fails on Win9x */
+}
+
+char *
+get_cpu_str (void)
+{
+	static char verbuf[64];
+	OSVERSIONINFO osvi;
+	SYSTEM_INFO si;
+	double mhz;
+
+	osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
+	GetVersionEx (&osvi);
+	GetSystemInfo (&si);
+
+	mhz = get_mhz ();
+	if (mhz)
+	{
+		double cpuspeed = ( mhz > 1000 ) ? mhz / 1000 : mhz;
+		const char *cpuspeedstr = ( mhz > 1000 ) ? "GHz" : "MHz";
+		sprintf (verbuf, "Windows %ld.%ld [i%d86/%.2f%s]",
+					osvi.dwMajorVersion, osvi.dwMinorVersion, si.wProcessorLevel, 
+					cpuspeed, cpuspeedstr);
+	} else
+		sprintf (verbuf, "Windows %ld.%ld [i%d86]",
+			osvi.dwMajorVersion, osvi.dwMinorVersion, si.wProcessorLevel);
+
+	return verbuf;
+}
+
+#else
+
+char *
+get_cpu_str (void)
+{
+#if defined (USING_LINUX) || defined (USING_FREEBSD) || defined (__APPLE__)
+	double mhz;
+#endif
+	int cpus = 1;
+	struct utsname un;
+	static char *buf = NULL;
+
+	if (buf)
+		return buf;
+
+	buf = malloc (128);
+
+	uname (&un);
+
+#if defined (USING_LINUX) || defined (USING_FREEBSD) || defined (__APPLE__)
+	get_cpu_info (&mhz, &cpus);
+	if (mhz)
+	{
+		double cpuspeed = ( mhz > 1000 ) ? mhz / 1000 : mhz;
+		const char *cpuspeedstr = ( mhz > 1000 ) ? "GHz" : "MHz";
+		snprintf (buf, 128,
+					(cpus == 1) ? "%s %s [%s/%.2f%s]" : "%s %s [%s/%.2f%s/SMP]",
+					un.sysname, un.release, un.machine,
+					cpuspeed, cpuspeedstr);
+	} else
+#endif
+		snprintf (buf, 128,
+					(cpus == 1) ? "%s %s [%s]" : "%s %s [%s/SMP]",
+					un.sysname, un.release, un.machine);
+
+	return buf;
+}
+
+#endif
+
+int
+buf_get_line (char *ibuf, char **buf, int *position, int len)
+{
+	int pos = *position, spos = pos;
+
+	if (pos == len)
+		return 0;
+
+	while (ibuf[pos++] != '\n')
+	{
+		if (pos == len)
+			return 0;
+	}
+	pos--;
+	ibuf[pos] = 0;
+	*buf = &ibuf[spos];
+	pos++;
+	*position = pos;
+	return 1;
+}
+
+int match(const char *mask, const char *string)
+{
+  register const char *m = mask, *s = string;
+  register char ch;
+  const char *bm, *bs;		/* Will be reg anyway on a decent CPU/compiler */
+
+  /* Process the "head" of the mask, if any */
+  while ((ch = *m++) && (ch != '*'))
+    switch (ch)
+    {
+      case '\\':
+	if (*m == '?' || *m == '*')
+	  ch = *m++;
+      default:
+	if (rfc_tolower(*s) != rfc_tolower(ch))
+	  return 0;
+      case '?':
+	if (!*s++)
+	  return 0;
+    };
+  if (!ch)
+    return !(*s);
+
+  /* We got a star: quickly find if/where we match the next char */
+got_star:
+  bm = m;			/* Next try rollback here */
+  while ((ch = *m++))
+    switch (ch)
+    {
+      case '?':
+	if (!*s++)
+	  return 0;
+      case '*':
+	bm = m;
+	continue;		/* while */
+      case '\\':
+	if (*m == '?' || *m == '*')
+	  ch = *m++;
+      default:
+	goto break_while;	/* C is structured ? */
+    };
+break_while:
+  if (!ch)
+    return 1;			/* mask ends with '*', we got it */
+  ch = rfc_tolower(ch);
+  while (rfc_tolower(*s++) != ch)
+    if (!*s)
+      return 0;
+  bs = s;			/* Next try start from here */
+
+  /* Check the rest of the "chunk" */
+  while ((ch = *m++))
+  {
+    switch (ch)
+    {
+      case '*':
+	goto got_star;
+      case '\\':
+	if (*m == '?' || *m == '*')
+	  ch = *m++;
+      default:
+	if (rfc_tolower(*s) != rfc_tolower(ch))
+	{
+	  if (!*s)
+	    return 0;
+	  m = bm;
+	  s = bs;
+	  goto got_star;
+	};
+      case '?':
+	if (!*s++)
+	  return 0;
+    };
+  };
+  if (*s)
+  {
+    m = bm;
+    s = bs;
+    goto got_star;
+  };
+  return 1;
+}
+
+void
+for_files (char *dirname, char *mask, void callback (char *file))
+{
+	DIR *dir;
+	struct dirent *ent;
+	char *buf;
+
+	dir = opendir (dirname);
+	if (dir)
+	{
+		while ((ent = readdir (dir)))
+		{
+			if (strcmp (ent->d_name, ".") && strcmp (ent->d_name, ".."))
+			{
+				if (match (mask, ent->d_name))
+				{
+					buf = malloc (strlen (dirname) + strlen (ent->d_name) + 2);
+					sprintf (buf, "%s/%s", dirname, ent->d_name);
+					callback (buf);
+					free (buf);
+				}
+			}
+		}
+		closedir (dir);
+	}
+}
+
+/*void
+tolowerStr (char *str)
+{
+	while (*str)
+	{
+		*str = rfc_tolower (*str);
+		str++;
+	}
+}*/
+
+typedef struct
+{
+	char *code, *country;
+} domain_t;
+
+static int
+country_compare (const void *a, const void *b)
+{
+	return strcasecmp (a, ((domain_t *)b)->code);
+}
+
+static const domain_t domain[] =
+{
+		{"AC", N_("Ascension Island") },
+		{"AD", N_("Andorra") },
+		{"AE", N_("United Arab Emirates") },
+		{"AF", N_("Afghanistan") },
+		{"AG", N_("Antigua and Barbuda") },
+		{"AI", N_("Anguilla") },
+		{"AL", N_("Albania") },
+		{"AM", N_("Armenia") },
+		{"AN", N_("Netherlands Antilles") },
+		{"AO", N_("Angola") },
+		{"AQ", N_("Antarctica") },
+		{"AR", N_("Argentina") },
+		{"ARPA", N_("Reverse DNS") },
+		{"AS", N_("American Samoa") },
+		{"AT", N_("Austria") },
+		{"ATO", N_("Nato Fiel") },
+		{"AU", N_("Australia") },
+		{"AW", N_("Aruba") },
+		{"AX", N_("Aland Islands") },
+		{"AZ", N_("Azerbaijan") },
+		{"BA", N_("Bosnia and Herzegovina") },
+		{"BB", N_("Barbados") },
+		{"BD", N_("Bangladesh") },
+		{"BE", N_("Belgium") },
+		{"BF", N_("Burkina Faso") },
+		{"BG", N_("Bulgaria") },
+		{"BH", N_("Bahrain") },
+		{"BI", N_("Burundi") },
+		{"BIZ", N_("Businesses"), },
+		{"BJ", N_("Benin") },
+		{"BM", N_("Bermuda") },
+		{"BN", N_("Brunei Darussalam") },
+		{"BO", N_("Bolivia") },
+		{"BR", N_("Brazil") },
+		{"BS", N_("Bahamas") },
+		{"BT", N_("Bhutan") },
+		{"BV", N_("Bouvet Island") },
+		{"BW", N_("Botswana") },
+		{"BY", N_("Belarus") },
+		{"BZ", N_("Belize") },
+		{"CA", N_("Canada") },
+		{"CC", N_("Cocos Islands") },
+		{"CD", N_("Democratic Republic of Congo") },
+		{"CF", N_("Central African Republic") },
+		{"CG", N_("Congo") },
+		{"CH", N_("Switzerland") },
+		{"CI", N_("Cote d'Ivoire") },
+		{"CK", N_("Cook Islands") },
+		{"CL", N_("Chile") },
+		{"CM", N_("Cameroon") },
+		{"CN", N_("China") },
+		{"CO", N_("Colombia") },
+		{"COM", N_("Internic Commercial") },
+		{"CR", N_("Costa Rica") },
+		{"CS", N_("Serbia and Montenegro") },
+		{"CU", N_("Cuba") },
+		{"CV", N_("Cape Verde") },
+		{"CX", N_("Christmas Island") },
+		{"CY", N_("Cyprus") },
+		{"CZ", N_("Czech Republic") },
+		{"DE", N_("Germany") },
+		{"DJ", N_("Djibouti") },
+		{"DK", N_("Denmark") },
+		{"DM", N_("Dominica") },
+		{"DO", N_("Dominican Republic") },
+		{"DZ", N_("Algeria") },
+		{"EC", N_("Ecuador") },
+		{"EDU", N_("Educational Institution") },
+		{"EE", N_("Estonia") },
+		{"EG", N_("Egypt") },
+		{"EH", N_("Western Sahara") },
+		{"ER", N_("Eritrea") },
+		{"ES", N_("Spain") },
+		{"ET", N_("Ethiopia") },
+		{"EU", N_("European Union") },
+		{"FI", N_("Finland") },
+		{"FJ", N_("Fiji") },
+		{"FK", N_("Falkland Islands") },
+		{"FM", N_("Micronesia") },
+		{"FO", N_("Faroe Islands") },
+		{"FR", N_("France") },
+		{"GA", N_("Gabon") },
+		{"GB", N_("Great Britain") },
+		{"GD", N_("Grenada") },
+		{"GE", N_("Georgia") },
+		{"GF", N_("French Guiana") },
+		{"GG", N_("British Channel Isles") },
+		{"GH", N_("Ghana") },
+		{"GI", N_("Gibraltar") },
+		{"GL", N_("Greenland") },
+		{"GM", N_("Gambia") },
+		{"GN", N_("Guinea") },
+		{"GOV", N_("Government") },
+		{"GP", N_("Guadeloupe") },
+		{"GQ", N_("Equatorial Guinea") },
+		{"GR", N_("Greece") },
+		{"GS", N_("S. Georgia and S. Sandwich Isles") },
+		{"GT", N_("Guatemala") },
+		{"GU", N_("Guam") },
+		{"GW", N_("Guinea-Bissau") },
+		{"GY", N_("Guyana") },
+		{"HK", N_("Hong Kong") },
+		{"HM", N_("Heard and McDonald Islands") },
+		{"HN", N_("Honduras") },
+		{"HR", N_("Croatia") },
+		{"HT", N_("Haiti") },
+		{"HU", N_("Hungary") },
+		{"ID", N_("Indonesia") },
+		{"IE", N_("Ireland") },
+		{"IL", N_("Israel") },
+		{"IM", N_("Isle of Man") },
+		{"IN", N_("India") },
+		{"INFO", N_("Informational") },
+		{"INT", N_("International") },
+		{"IO", N_("British Indian Ocean Territory") },
+		{"IQ", N_("Iraq") },
+		{"IR", N_("Iran") },
+		{"IS", N_("Iceland") },
+		{"IT", N_("Italy") },
+		{"JE", N_("Jersey") },
+		{"JM", N_("Jamaica") },
+		{"JO", N_("Jordan") },
+		{"JP", N_("Japan") },
+		{"KE", N_("Kenya") },
+		{"KG", N_("Kyrgyzstan") },
+		{"KH", N_("Cambodia") },
+		{"KI", N_("Kiribati") },
+		{"KM", N_("Comoros") },
+		{"KN", N_("St. Kitts and Nevis") },
+		{"KP", N_("North Korea") },
+		{"KR", N_("South Korea") },
+		{"KW", N_("Kuwait") },
+		{"KY", N_("Cayman Islands") },
+		{"KZ", N_("Kazakhstan") },
+		{"LA", N_("Laos") },
+		{"LB", N_("Lebanon") },
+		{"LC", N_("Saint Lucia") },
+		{"LI", N_("Liechtenstein") },
+		{"LK", N_("Sri Lanka") },
+		{"LR", N_("Liberia") },
+		{"LS", N_("Lesotho") },
+		{"LT", N_("Lithuania") },
+		{"LU", N_("Luxembourg") },
+		{"LV", N_("Latvia") },
+		{"LY", N_("Libya") },
+		{"MA", N_("Morocco") },
+		{"MC", N_("Monaco") },
+		{"MD", N_("Moldova") },
+		{"MED", N_("United States Medical") },
+		{"MG", N_("Madagascar") },
+		{"MH", N_("Marshall Islands") },
+		{"MIL", N_("Military") },
+		{"MK", N_("Macedonia") },
+		{"ML", N_("Mali") },
+		{"MM", N_("Myanmar") },
+		{"MN", N_("Mongolia") },
+		{"MO", N_("Macau") },
+		{"MP", N_("Northern Mariana Islands") },
+		{"MQ", N_("Martinique") },
+		{"MR", N_("Mauritania") },
+		{"MS", N_("Montserrat") },
+		{"MT", N_("Malta") },
+		{"MU", N_("Mauritius") },
+		{"MV", N_("Maldives") },
+		{"MW", N_("Malawi") },
+		{"MX", N_("Mexico") },
+		{"MY", N_("Malaysia") },
+		{"MZ", N_("Mozambique") },
+		{"NA", N_("Namibia") },
+		{"NC", N_("New Caledonia") },
+		{"NE", N_("Niger") },
+		{"NET", N_("Internic Network") },
+		{"NF", N_("Norfolk Island") },
+		{"NG", N_("Nigeria") },
+		{"NI", N_("Nicaragua") },
+		{"NL", N_("Netherlands") },
+		{"NO", N_("Norway") },
+		{"NP", N_("Nepal") },
+		{"NR", N_("Nauru") },
+		{"NU", N_("Niue") },
+		{"NZ", N_("New Zealand") },
+		{"OM", N_("Oman") },
+		{"ORG", N_("Internic Non-Profit Organization") },
+		{"PA", N_("Panama") },
+		{"PE", N_("Peru") },
+		{"PF", N_("French Polynesia") },
+		{"PG", N_("Papua New Guinea") },
+		{"PH", N_("Philippines") },
+		{"PK", N_("Pakistan") },
+		{"PL", N_("Poland") },
+		{"PM", N_("St. Pierre and Miquelon") },
+		{"PN", N_("Pitcairn") },
+		{"PR", N_("Puerto Rico") },
+		{"PS", N_("Palestinian Territory") },
+		{"PT", N_("Portugal") },
+		{"PW", N_("Palau") },
+		{"PY", N_("Paraguay") },
+		{"QA", N_("Qatar") },
+		{"RE", N_("Reunion") },
+		{"RO", N_("Romania") },
+		{"RPA", N_("Old School ARPAnet") },
+		{"RU", N_("Russian Federation") },
+		{"RW", N_("Rwanda") },
+		{"SA", N_("Saudi Arabia") },
+		{"SB", N_("Solomon Islands") },
+		{"SC", N_("Seychelles") },
+		{"SD", N_("Sudan") },
+		{"SE", N_("Sweden") },
+		{"SG", N_("Singapore") },
+		{"SH", N_("St. Helena") },
+		{"SI", N_("Slovenia") },
+		{"SJ", N_("Svalbard and Jan Mayen Islands") },
+		{"SK", N_("Slovak Republic") },
+		{"SL", N_("Sierra Leone") },
+		{"SM", N_("San Marino") },
+		{"SN", N_("Senegal") },
+		{"SO", N_("Somalia") },
+		{"SR", N_("Suriname") },
+		{"ST", N_("Sao Tome and Principe") },
+		{"SU", N_("Former USSR") },
+		{"SV", N_("El Salvador") },
+		{"SY", N_("Syria") },
+		{"SZ", N_("Swaziland") },
+		{"TC", N_("Turks and Caicos Islands") },
+		{"TD", N_("Chad") },
+		{"TF", N_("French Southern Territories") },
+		{"TG", N_("Togo") },
+		{"TH", N_("Thailand") },
+		{"TJ", N_("Tajikistan") },
+		{"TK", N_("Tokelau") },
+		{"TL", N_("East Timor") },
+		{"TM", N_("Turkmenistan") },
+		{"TN", N_("Tunisia") },
+		{"TO", N_("Tonga") },
+		{"TP", N_("East Timor") },
+		{"TR", N_("Turkey") },
+		{"TT", N_("Trinidad and Tobago") },
+		{"TV", N_("Tuvalu") },
+		{"TW", N_("Taiwan") },
+		{"TZ", N_("Tanzania") },
+		{"UA", N_("Ukraine") },
+		{"UG", N_("Uganda") },
+		{"UK", N_("United Kingdom") },
+		{"US", N_("United States of America") },
+		{"UY", N_("Uruguay") },
+		{"UZ", N_("Uzbekistan") },
+		{"VA", N_("Vatican City State") },
+		{"VC", N_("St. Vincent and the Grenadines") },
+		{"VE", N_("Venezuela") },
+		{"VG", N_("British Virgin Islands") },
+		{"VI", N_("US Virgin Islands") },
+		{"VN", N_("Vietnam") },
+		{"VU", N_("Vanuatu") },
+		{"WF", N_("Wallis and Futuna Islands") },
+		{"WS", N_("Samoa") },
+		{"YE", N_("Yemen") },
+		{"YT", N_("Mayotte") },
+		{"YU", N_("Yugoslavia") },
+		{"ZA", N_("South Africa") },
+		{"ZM", N_("Zambia") },
+		{"ZW", N_("Zimbabwe") },
+};
+
+char *
+country (char *hostname)
+{
+	char *p;
+	domain_t *dom;
+
+	if (!hostname || !*hostname || isdigit ((unsigned char) hostname[strlen (hostname) - 1]))
+		return _("Unknown");
+	if ((p = strrchr (hostname, '.')))
+		p++;
+	else
+		p = hostname;
+
+	dom = bsearch (p, domain, sizeof (domain) / sizeof (domain_t),
+						sizeof (domain_t), country_compare);
+
+	if (!dom)
+		return _("Unknown");
+
+	return _(dom->country);
+}
+
+void
+country_search (char *pattern, void *ud, void (*print)(void *, char *, ...))
+{
+	const domain_t *dom;
+	int i;
+
+	for (i = 0; i < sizeof (domain) / sizeof (domain_t); i++)
+	{
+		dom = &domain[i];
+		if (match (pattern, dom->country) || match (pattern, _(dom->country)))
+		{
+			print (ud, "%s = %s\n", dom->code, _(dom->country));
+		}
+	}
+}
+
+/* I think gnome1.0.x isn't necessarily linked against popt, ah well! */
+/* !!! For now use this inlined function, or it would break fe-text building */
+/* .... will find a better solution later. */
+/*#ifndef USE_GNOME*/
+
+/* this is taken from gnome-libs 1.2.4 */
+#define POPT_ARGV_ARRAY_GROW_DELTA 5
+
+int my_poptParseArgvString(const char * s, int * argcPtr, char *** argvPtr) {
+    char * buf, * bufStart, * dst;
+    const char * src;
+    char quote = '\0';
+    int argvAlloced = POPT_ARGV_ARRAY_GROW_DELTA;
+    char ** argv = malloc(sizeof(*argv) * argvAlloced);
+    const char ** argv2;
+    int argc = 0;
+    int i, buflen;
+
+    buflen = strlen(s) + 1;
+/*    bufStart = buf = alloca(buflen);*/
+	 bufStart = buf = malloc (buflen);
+    memset(buf, '\0', buflen);
+
+    src = s;
+    argv[argc] = buf;
+
+    while (*src) {
+	if (quote == *src) {
+	    quote = '\0';
+	} else if (quote) {
+	    if (*src == '\\') {
+		src++;
+		if (!*src) {
+		    free(argv);
+			 free(bufStart);
+		    return 1;
+		}
+		if (*src != quote) *buf++ = '\\';
+	    }
+	    *buf++ = *src;
+	/*} else if (isspace((unsigned char) *src)) {*/
+	} else if (*src == ' ') {
+	    if (*argv[argc]) {
+		buf++, argc++;
+		if (argc == argvAlloced) {
+		    argvAlloced += POPT_ARGV_ARRAY_GROW_DELTA;
+		    argv = realloc(argv, sizeof(*argv) * argvAlloced);
+		}
+		argv[argc] = buf;
+	    }
+	} else switch (*src) {
+	  case '"':
+	  case '\'':
+	    quote = *src;
+	    break;
+	  case '\\':
+	    src++;
+	    if (!*src) {
+		free(argv);
+		free(bufStart);
+		return 1;
+	    }
+	    /* fallthrough */
+	  default:
+	    *buf++ = *src;
+	}
+
+	src++;
+    }
+
+    if (strlen(argv[argc])) {
+	argc++, buf++;
+    }
+
+    dst = malloc((argc + 1) * sizeof(*argv) + (buf - bufStart));
+    argv2 = (void *) dst;
+    dst += (argc + 1) * sizeof(*argv);
+    memcpy((void *)argv2, argv, argc * sizeof(*argv));
+    argv2[argc] = NULL;
+    memcpy(dst, bufStart, buf - bufStart);
+
+    for (i = 0; i < argc; i++) {
+	argv2[i] = dst + (argv[i] - bufStart);
+    }
+
+    free(argv);
+
+    *argvPtr = (char **)argv2;	/* XXX don't change the API */
+    *argcPtr = argc;
+
+	 free (bufStart);
+
+    return 0;
+}
+
+int
+util_exec (const char *cmd)
+{
+	int pid;
+	char **argv;
+	int argc;
+	int fd;
+
+	if (my_poptParseArgvString (cmd, &argc, &argv) != 0)
+		return -1;
+
+#ifndef WIN32
+	pid = fork ();
+	if (pid == -1)
+		return -1;
+	if (pid == 0)
+	{
+		/* Now close all open file descriptors except stdin, stdout and stderr */
+		for (fd = 3; fd < 1024; fd++) close(fd);
+		execvp (argv[0], argv);
+		_exit (0);
+	} else
+	{
+		free (argv);
+		return pid;
+	}
+#else
+	spawnvp (_P_DETACH, argv[0], argv);
+	free (argv);
+	return 0;
+#endif
+}
+
+int
+util_execv (char * const argv[])
+{
+	int pid, fd;
+
+#ifndef WIN32
+	pid = fork ();
+	if (pid == -1)
+		return -1;
+	if (pid == 0)
+	{
+		/* Now close all open file descriptors except stdin, stdout and stderr */
+		for (fd = 3; fd < 1024; fd++) close(fd);
+		execv (argv[0], argv);
+		_exit (0);
+	} else
+	{
+		return pid;
+	}
+#else
+	spawnv (_P_DETACH, argv[0], argv);
+	return 0;
+#endif
+}
+
+unsigned long
+make_ping_time (void)
+{
+#ifndef WIN32
+	struct timeval timev;
+	gettimeofday (&timev, 0);
+#else
+	GTimeVal timev;
+	g_get_current_time (&timev);
+#endif
+	return (timev.tv_sec - 50000) * 1000000 + timev.tv_usec;
+}
+
+
+/************************************************************************
+ *    This technique was borrowed in part from the source code to 
+ *    ircd-hybrid-5.3 to implement case-insensitive string matches which
+ *    are fully compliant with Section 2.2 of RFC 1459, the copyright
+ *    of that code being (C) 1990 Jarkko Oikarinen and under the GPL.
+ *    
+ *    A special thanks goes to Mr. Okarinen for being the one person who
+ *    seems to have ever noticed this section in the original RFC and
+ *    written code for it.  Shame on all the rest of you (myself included).
+ *    
+ *        --+ Dagmar d'Surreal
+ */
+
+int
+rfc_casecmp (const char *s1, const char *s2)
+{
+	register unsigned char *str1 = (unsigned char *) s1;
+	register unsigned char *str2 = (unsigned char *) s2;
+	register int res;
+
+	while ((res = rfc_tolower (*str1) - rfc_tolower (*str2)) == 0)
+	{
+		if (*str1 == '\0')
+			return 0;
+		str1++;
+		str2++;
+	}
+	return (res);
+}
+
+int
+rfc_ncasecmp (char *str1, char *str2, int n)
+{
+	register unsigned char *s1 = (unsigned char *) str1;
+	register unsigned char *s2 = (unsigned char *) str2;
+	register int res;
+
+	while ((res = rfc_tolower (*s1) - rfc_tolower (*s2)) == 0)
+	{
+		s1++;
+		s2++;
+		n--;
+		if (n == 0 || (*s1 == '\0' && *s2 == '\0'))
+			return 0;
+	}
+	return (res);
+}
+
+const unsigned char rfc_tolowertab[] =
+	{ 0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa,
+	0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14,
+	0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
+	0x1e, 0x1f,
+	' ', '!', '"', '#', '$', '%', '&', 0x27, '(', ')',
+	'*', '+', ',', '-', '.', '/',
+	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+	':', ';', '<', '=', '>', '?',
+	'@', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
+	'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
+	't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~',
+	'_',
+	'`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
+	'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
+	't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~',
+	0x7f,
+	0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
+	0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+	0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
+	0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
+	0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9,
+	0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
+	0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9,
+	0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
+	0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9,
+	0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
+	0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,
+	0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
+	0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
+	0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+	0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
+	0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
+};
+
+/*static unsigned char touppertab[] =
+	{ 0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa,
+	0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14,
+	0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
+	0x1e, 0x1f,
+	' ', '!', '"', '#', '$', '%', '&', 0x27, '(', ')',
+	'*', '+', ',', '-', '.', '/',
+	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+	':', ';', '<', '=', '>', '?',
+	'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
+	'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
+	'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^',
+	0x5f,
+	'`', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
+	'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
+	'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^',
+	0x7f,
+	0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
+	0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+	0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
+	0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
+	0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9,
+	0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
+	0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9,
+	0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
+	0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9,
+	0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
+	0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,
+	0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
+	0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
+	0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+	0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
+	0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
+};*/
+
+/*static int
+rename_utf8 (char *oldname, char *newname)
+{
+	int sav, res;
+	char *fso, *fsn;
+
+	fso = xchat_filename_from_utf8 (oldname, -1, 0, 0, 0);
+	if (!fso)
+		return FALSE;
+	fsn = xchat_filename_from_utf8 (newname, -1, 0, 0, 0);
+	if (!fsn)
+	{
+		g_free (fso);
+		return FALSE;
+	}
+
+	res = rename (fso, fsn);
+	sav = errno;
+	g_free (fso);
+	g_free (fsn);
+	errno = sav;
+	return res;
+}
+
+static int
+unlink_utf8 (char *fname)
+{
+	int res;
+	char *fs;
+
+	fs = xchat_filename_from_utf8 (fname, -1, 0, 0, 0);
+	if (!fs)
+		return FALSE;
+
+	res = unlink (fs);
+	g_free (fs);
+	return res;
+}*/
+
+static gboolean
+file_exists_utf8 (char *fname)
+{
+	int res;
+	char *fs;
+
+	fs = xchat_filename_from_utf8 (fname, -1, 0, 0, 0);
+	if (!fs)
+		return FALSE;
+
+	res = access (fs, F_OK);
+	g_free (fs);
+	if (res == 0)
+		return TRUE;
+	return FALSE;
+}
+
+static gboolean
+copy_file (char *dl_src, char *dl_dest, int permissions)	/* FS encoding */
+{
+	int tmp_src, tmp_dest;
+	gboolean ok = FALSE;
+	char dl_tmp[4096];
+	int return_tmp, return_tmp2;
+
+	if ((tmp_src = open (dl_src, O_RDONLY | OFLAGS)) == -1)
+	{
+		fprintf (stderr, "Unable to open() file '%s' (%s) !", dl_src,
+				  strerror (errno));
+		return FALSE;
+	}
+
+	if ((tmp_dest =
+		 open (dl_dest, O_WRONLY | O_CREAT | O_TRUNC | OFLAGS, permissions)) < 0)
+	{
+		close (tmp_src);
+		fprintf (stderr, "Unable to create file '%s' (%s) !", dl_src,
+				  strerror (errno));
+		return FALSE;
+	}
+
+	for (;;)
+	{
+		return_tmp = read (tmp_src, dl_tmp, sizeof (dl_tmp));
+
+		if (!return_tmp)
+		{
+			ok = TRUE;
+			break;
+		}
+
+		if (return_tmp < 0)
+		{
+			fprintf (stderr, "download_move_to_completed_dir(): "
+				"error reading while moving file to save directory (%s)",
+				 strerror (errno));
+			break;
+		}
+
+		return_tmp2 = write (tmp_dest, dl_tmp, return_tmp);
+
+		if (return_tmp2 < 0)
+		{
+			fprintf (stderr, "download_move_to_completed_dir(): "
+				"error writing while moving file to save directory (%s)",
+				 strerror (errno));
+			break;
+		}
+
+		if (return_tmp < sizeof (dl_tmp))
+		{
+			ok = TRUE;
+			break;
+		}
+	}
+
+	close (tmp_dest);
+	close (tmp_src);
+	return ok;
+}
+
+/* Takes care of moving a file from a temporary download location to a completed location. Now in UTF-8. */
+void
+move_file_utf8 (char *src_dir, char *dst_dir, char *fname, int dccpermissions)
+{
+	char src[4096];
+	char dst[4096];
+	int res, i;
+	char *src_fs;	/* FileSystem encoding */
+	char *dst_fs;
+
+	/* if dcc_dir and dcc_completed_dir are the same then we are done */
+	if (0 == strcmp (src_dir, dst_dir) ||
+		 0 == dst_dir[0])
+		return;			/* Already in "completed dir" */
+
+	snprintf (src, sizeof (src), "%s/%s", src_dir, fname);
+	snprintf (dst, sizeof (dst), "%s/%s", dst_dir, fname);
+
+	/* already exists in completed dir? Append a number */
+	if (file_exists_utf8 (dst))
+	{
+		for (i = 0; ; i++)
+		{
+			snprintf (dst, sizeof (dst), "%s/%s.%d", dst_dir, fname, i);
+			if (!file_exists_utf8 (dst))
+				break;
+		}
+	}
+
+	/* convert UTF-8 to filesystem encoding */
+	src_fs = xchat_filename_from_utf8 (src, -1, 0, 0, 0);
+	if (!src_fs)
+		return;
+	dst_fs = xchat_filename_from_utf8 (dst, -1, 0, 0, 0);
+	if (!dst_fs)
+	{
+		g_free (src_fs);
+		return;
+	}
+
+	/* first try a simple rename move */
+	res = rename (src_fs, dst_fs);
+
+	if (res == -1 && (errno == EXDEV || errno == EPERM))
+	{
+		/* link failed because either the two paths aren't on the */
+		/* same filesystem or the filesystem doesn't support hard */
+		/* links, so we have to do a copy. */
+		if (copy_file (src_fs, dst_fs, dccpermissions))
+			unlink (src_fs);
+	}
+
+	g_free (dst_fs);
+	g_free (src_fs);
+}
+
+int
+mkdir_utf8 (char *dir)
+{
+	int ret;
+
+	dir = xchat_filename_from_utf8 (dir, -1, 0, 0, 0);
+	if (!dir)
+		return -1;
+
+#ifdef WIN32
+	ret = mkdir (dir);
+#else
+	ret = mkdir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
+#endif
+	g_free (dir);
+
+	return ret;
+}
+
+/* separates a string according to a 'sep' char, then calls the callback
+   function for each token found */
+
+int
+token_foreach (char *str, char sep,
+					int (*callback) (char *str, void *ud), void *ud)
+{
+	char t, *start = str;
+
+	while (1)
+	{
+		if (*str == sep || *str == 0)
+		{
+			t = *str;
+			*str = 0;
+			if (callback (start, ud) < 1)
+			{
+				*str = t;
+				return FALSE;
+			}
+			*str = t;
+
+			if (*str == 0)
+				break;
+			str++;
+			start = str;
+
+		} else
+		{
+			/* chars $00-$7f can never be embedded in utf-8 */
+			str++;
+		}
+	}
+
+	return TRUE;
+}
+
+/* 31 bit string hash functions */
+
+guint32
+str_hash (const char *key)
+{
+	const char *p = key;
+	guint32 h = *p;
+
+	if (h)
+		for (p += 1; *p != '\0'; p++)
+			h = (h << 5) - h + *p;
+
+	return h;
+}
+
+guint32
+str_ihash (const unsigned char *key)
+{
+	const char *p = key;
+	guint32 h = rfc_tolowertab [(guint)*p];
+
+	if (h)
+		for (p += 1; *p != '\0'; p++)
+			h = (h << 5) - h + rfc_tolowertab [(guint)*p];
+
+	return h;
+}
+
+/* features: 1. "src" must be valid, NULL terminated UTF-8 */
+/*           2. "dest" will be left with valid UTF-8 - no partial chars! */
+
+void
+safe_strcpy (char *dest, const char *src, int bytes_left)
+{
+	int mbl;
+
+	while (1)
+	{
+		mbl = g_utf8_skip[*((unsigned char *)src)];
+
+		if (bytes_left < (mbl + 1)) /* can't fit with NULL? */
+		{
+			*dest = 0;
+			break;
+		}
+
+		if (mbl == 1)	/* one byte char */
+		{
+			*dest = *src;
+			if (*src == 0)
+				break;	/* it all fit */
+			dest++;
+			src++;
+			bytes_left--;
+		}
+		else				/* multibyte char */
+		{
+			memcpy (dest, src, mbl);
+			dest += mbl;
+			src += mbl;
+			bytes_left -= mbl;
+		}
+	}
+}
diff --git a/src/common/util.h b/src/common/util.h
new file mode 100644
index 00000000..fce45def
--- /dev/null
+++ b/src/common/util.h
@@ -0,0 +1,54 @@
+/************************************************************************
+ *    This technique was borrowed in part from the source code to 
+ *    ircd-hybrid-5.3 to implement case-insensitive string matches which
+ *    are fully compliant with Section 2.2 of RFC 1459, the copyright
+ *    of that code being (C) 1990 Jarkko Oikarinen and under the GPL.
+ *    
+ *    A special thanks goes to Mr. Okarinen for being the one person who
+ *    seems to have ever noticed this section in the original RFC and
+ *    written code for it.  Shame on all the rest of you (myself included).
+ *    
+ *        --+ Dagmar d'Surreal
+ */
+
+#ifndef XCHAT_UTIL_H
+#define XCHAT_UTIL_H
+
+#define rfc_tolower(c) (rfc_tolowertab[(unsigned char)(c)])
+
+extern const unsigned char rfc_tolowertab[];
+
+int my_poptParseArgvString(const char * s, int * argcPtr, char *** argvPtr);
+char *expand_homedir (char *file);
+void path_part (char *file, char *path, int pathlen);
+int match (const char *mask, const char *string);
+char *file_part (char *file);
+void for_files (char *dirname, char *mask, void callback (char *file));
+int rfc_casecmp (const char *, const char *);
+int rfc_ncasecmp (char *, char *, int);
+int buf_get_line (char *, char **, int *, int len);
+char *nocasestrstr (const char *text, const char *tofind);
+char *country (char *);
+void country_search (char *pattern, void *ud, void (*print)(void *, char *, ...));
+char *get_cpu_str (void);
+int util_exec (const char *cmd);
+int util_execv (char * const argv[]);
+#define STRIP_COLOR 1
+#define STRIP_ATTRIB 2
+#define STRIP_HIDDEN 4
+#define STRIP_ESCMARKUP 8
+#define STRIP_ALL 7
+gchar *strip_color (const char *text, int len, int flags);
+int strip_color2 (const char *src, int len, char *dst, int flags);
+int strip_hidden_attribute (char *src, char *dst);
+char *errorstring (int err);
+int waitline (int sok, char *buf, int bufsize, int);
+unsigned long make_ping_time (void);
+void move_file_utf8 (char *src_dir, char *dst_dir, char *fname, int dccpermissions);
+int mkdir_utf8 (char *dir);
+int token_foreach (char *str, char sep, int (*callback) (char *str, void *ud), void *ud);
+guint32 str_hash (const char *key);
+guint32 str_ihash (const unsigned char *key);
+void safe_strcpy (char *dest, const char *src, int bytes_left);
+
+#endif
diff --git a/src/common/xchat-plugin.h b/src/common/xchat-plugin.h
new file mode 100644
index 00000000..30b19295
--- /dev/null
+++ b/src/common/xchat-plugin.h
@@ -0,0 +1,334 @@
+/* You can distribute this header with your plugins for easy compilation */
+#ifndef XCHAT_PLUGIN_H
+#define XCHAT_PLUGIN_H
+
+#include <time.h>
+
+#define XCHAT_IFACE_MAJOR	1
+#define XCHAT_IFACE_MINOR	9
+#define XCHAT_IFACE_MICRO	11
+#define XCHAT_IFACE_VERSION	((XCHAT_IFACE_MAJOR * 10000) + \
+				 (XCHAT_IFACE_MINOR * 100) + \
+				 (XCHAT_IFACE_MICRO))
+
+#define XCHAT_PRI_HIGHEST	127
+#define XCHAT_PRI_HIGH		64
+#define XCHAT_PRI_NORM		0
+#define XCHAT_PRI_LOW		(-64)
+#define XCHAT_PRI_LOWEST	(-128)
+
+#define XCHAT_FD_READ		1
+#define XCHAT_FD_WRITE		2
+#define XCHAT_FD_EXCEPTION	4
+#define XCHAT_FD_NOTSOCKET	8
+
+#define XCHAT_EAT_NONE		0	/* pass it on through! */
+#define XCHAT_EAT_XCHAT		1	/* don't let xchat see this event */
+#define XCHAT_EAT_PLUGIN	2	/* don't let other plugins see this event */
+#define XCHAT_EAT_ALL		(XCHAT_EAT_XCHAT|XCHAT_EAT_PLUGIN)	/* don't let anything see this event */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct _xchat_plugin xchat_plugin;
+typedef struct _xchat_list xchat_list;
+typedef struct _xchat_hook xchat_hook;
+#ifndef PLUGIN_C
+typedef struct _xchat_context xchat_context;
+#endif
+
+#ifndef PLUGIN_C
+struct _xchat_plugin
+{
+	/* these are only used on win32 */
+	xchat_hook *(*xchat_hook_command) (xchat_plugin *ph,
+		    const char *name,
+		    int pri,
+		    int (*callback) (char *word[], char *word_eol[], void *user_data),
+		    const char *help_text,
+		    void *userdata);
+	xchat_hook *(*xchat_hook_server) (xchat_plugin *ph,
+		   const char *name,
+		   int pri,
+		   int (*callback) (char *word[], char *word_eol[], void *user_data),
+		   void *userdata);
+	xchat_hook *(*xchat_hook_print) (xchat_plugin *ph,
+		  const char *name,
+		  int pri,
+		  int (*callback) (char *word[], void *user_data),
+		  void *userdata);
+	xchat_hook *(*xchat_hook_timer) (xchat_plugin *ph,
+		  int timeout,
+		  int (*callback) (void *user_data),
+		  void *userdata);
+	xchat_hook *(*xchat_hook_fd) (xchat_plugin *ph,
+		   int fd,
+		   int flags,
+		   int (*callback) (int fd, int flags, void *user_data),
+		   void *userdata);
+	void *(*xchat_unhook) (xchat_plugin *ph,
+	      xchat_hook *hook);
+	void (*xchat_print) (xchat_plugin *ph,
+	     const char *text);
+	void (*xchat_printf) (xchat_plugin *ph,
+	      const char *format, ...);
+	void (*xchat_command) (xchat_plugin *ph,
+	       const char *command);
+	void (*xchat_commandf) (xchat_plugin *ph,
+		const char *format, ...);
+	int (*xchat_nickcmp) (xchat_plugin *ph,
+	       const char *s1,
+	       const char *s2);
+	int (*xchat_set_context) (xchat_plugin *ph,
+		   xchat_context *ctx);
+	xchat_context *(*xchat_find_context) (xchat_plugin *ph,
+		    const char *servname,
+		    const char *channel);
+	xchat_context *(*xchat_get_context) (xchat_plugin *ph);
+	const char *(*xchat_get_info) (xchat_plugin *ph,
+		const char *id);
+	int (*xchat_get_prefs) (xchat_plugin *ph,
+		 const char *name,
+		 const char **string,
+		 int *integer);
+	xchat_list * (*xchat_list_get) (xchat_plugin *ph,
+		const char *name);
+	void (*xchat_list_free) (xchat_plugin *ph,
+		 xchat_list *xlist);
+	const char * const * (*xchat_list_fields) (xchat_plugin *ph,
+		   const char *name);
+	int (*xchat_list_next) (xchat_plugin *ph,
+		 xchat_list *xlist);
+	const char * (*xchat_list_str) (xchat_plugin *ph,
+		xchat_list *xlist,
+		const char *name);
+	int (*xchat_list_int) (xchat_plugin *ph,
+		xchat_list *xlist,
+		const char *name);
+	void * (*xchat_plugingui_add) (xchat_plugin *ph,
+		     const char *filename,
+		     const char *name,
+		     const char *desc,
+		     const char *version,
+		     char *reserved);
+	void (*xchat_plugingui_remove) (xchat_plugin *ph,
+			void *handle);
+	int (*xchat_emit_print) (xchat_plugin *ph,
+			const char *event_name, ...);
+	int (*xchat_read_fd) (xchat_plugin *ph,
+			void *src,
+			char *buf,
+			int *len);
+	time_t (*xchat_list_time) (xchat_plugin *ph,
+		xchat_list *xlist,
+		const char *name);
+	char *(*xchat_gettext) (xchat_plugin *ph,
+		const char *msgid);
+	void (*xchat_send_modes) (xchat_plugin *ph,
+		  const char **targets,
+		  int ntargets,
+		  int modes_per_line,
+		  char sign,
+		  char mode);
+	char *(*xchat_strip) (xchat_plugin *ph,
+	     const char *str,
+	     int len,
+	     int flags);
+	void (*xchat_free) (xchat_plugin *ph,
+	    void *ptr);
+};
+#endif
+
+
+xchat_hook *
+xchat_hook_command (xchat_plugin *ph,
+		    const char *name,
+		    int pri,
+		    int (*callback) (char *word[], char *word_eol[], void *user_data),
+		    const char *help_text,
+		    void *userdata);
+
+xchat_hook *
+xchat_hook_server (xchat_plugin *ph,
+		   const char *name,
+		   int pri,
+		   int (*callback) (char *word[], char *word_eol[], void *user_data),
+		   void *userdata);
+
+xchat_hook *
+xchat_hook_print (xchat_plugin *ph,
+		  const char *name,
+		  int pri,
+		  int (*callback) (char *word[], void *user_data),
+		  void *userdata);
+
+xchat_hook *
+xchat_hook_timer (xchat_plugin *ph,
+		  int timeout,
+		  int (*callback) (void *user_data),
+		  void *userdata);
+
+xchat_hook *
+xchat_hook_fd (xchat_plugin *ph,
+		int fd,
+		int flags,
+		int (*callback) (int fd, int flags, void *user_data),
+		void *userdata);
+
+void *
+xchat_unhook (xchat_plugin *ph,
+	      xchat_hook *hook);
+
+void
+xchat_print (xchat_plugin *ph,
+	     const char *text);
+
+void
+xchat_printf (xchat_plugin *ph,
+	      const char *format, ...);
+
+void
+xchat_command (xchat_plugin *ph,
+	       const char *command);
+
+void
+xchat_commandf (xchat_plugin *ph,
+		const char *format, ...);
+
+int
+xchat_nickcmp (xchat_plugin *ph,
+	       const char *s1,
+	       const char *s2);
+
+int
+xchat_set_context (xchat_plugin *ph,
+		   xchat_context *ctx);
+
+xchat_context *
+xchat_find_context (xchat_plugin *ph,
+		    const char *servname,
+		    const char *channel);
+
+xchat_context *
+xchat_get_context (xchat_plugin *ph);
+
+const char *
+xchat_get_info (xchat_plugin *ph,
+		const char *id);
+
+int
+xchat_get_prefs (xchat_plugin *ph,
+		 const char *name,
+		 const char **string,
+		 int *integer);
+
+xchat_list *
+xchat_list_get (xchat_plugin *ph,
+		const char *name);
+
+void
+xchat_list_free (xchat_plugin *ph,
+		 xchat_list *xlist);
+
+const char * const *
+xchat_list_fields (xchat_plugin *ph,
+		   const char *name);
+
+int
+xchat_list_next (xchat_plugin *ph,
+		 xchat_list *xlist);
+
+const char *
+xchat_list_str (xchat_plugin *ph,
+		xchat_list *xlist,
+		const char *name);
+
+int
+xchat_list_int (xchat_plugin *ph,
+		xchat_list *xlist,
+		const char *name);
+
+time_t
+xchat_list_time (xchat_plugin *ph,
+		 xchat_list *xlist,
+		 const char *name);
+
+void *
+xchat_plugingui_add (xchat_plugin *ph,
+		     const char *filename,
+		     const char *name,
+		     const char *desc,
+		     const char *version,
+		     char *reserved);
+
+void
+xchat_plugingui_remove (xchat_plugin *ph,
+			void *handle);
+
+int 
+xchat_emit_print (xchat_plugin *ph,
+		  const char *event_name, ...);
+
+char *
+xchat_gettext (xchat_plugin *ph,
+	       const char *msgid);
+
+void
+xchat_send_modes (xchat_plugin *ph,
+		  const char **targets,
+		  int ntargets,
+		  int modes_per_line,
+		  char sign,
+		  char mode);
+
+char *
+xchat_strip (xchat_plugin *ph,
+	     const char *str,
+	     int len,
+	     int flags);
+
+void
+xchat_free (xchat_plugin *ph,
+	    void *ptr);
+
+#if !defined(PLUGIN_C) && defined(WIN32)
+#ifndef XCHAT_PLUGIN_HANDLE
+#define XCHAT_PLUGIN_HANDLE (ph)
+#endif
+#define xchat_hook_command ((XCHAT_PLUGIN_HANDLE)->xchat_hook_command)
+#define xchat_hook_server ((XCHAT_PLUGIN_HANDLE)->xchat_hook_server)
+#define xchat_hook_print ((XCHAT_PLUGIN_HANDLE)->xchat_hook_print)
+#define xchat_hook_timer ((XCHAT_PLUGIN_HANDLE)->xchat_hook_timer)
+#define xchat_hook_fd ((XCHAT_PLUGIN_HANDLE)->xchat_hook_fd)
+#define xchat_unhook ((XCHAT_PLUGIN_HANDLE)->xchat_unhook)
+#define xchat_print ((XCHAT_PLUGIN_HANDLE)->xchat_print)
+#define xchat_printf ((XCHAT_PLUGIN_HANDLE)->xchat_printf)
+#define xchat_command ((XCHAT_PLUGIN_HANDLE)->xchat_command)
+#define xchat_commandf ((XCHAT_PLUGIN_HANDLE)->xchat_commandf)
+#define xchat_nickcmp ((XCHAT_PLUGIN_HANDLE)->xchat_nickcmp)
+#define xchat_set_context ((XCHAT_PLUGIN_HANDLE)->xchat_set_context)
+#define xchat_find_context ((XCHAT_PLUGIN_HANDLE)->xchat_find_context)
+#define xchat_get_context ((XCHAT_PLUGIN_HANDLE)->xchat_get_context)
+#define xchat_get_info ((XCHAT_PLUGIN_HANDLE)->xchat_get_info)
+#define xchat_get_prefs ((XCHAT_PLUGIN_HANDLE)->xchat_get_prefs)
+#define xchat_list_get ((XCHAT_PLUGIN_HANDLE)->xchat_list_get)
+#define xchat_list_free ((XCHAT_PLUGIN_HANDLE)->xchat_list_free)
+#define xchat_list_fields ((XCHAT_PLUGIN_HANDLE)->xchat_list_fields)
+#define xchat_list_str ((XCHAT_PLUGIN_HANDLE)->xchat_list_str)
+#define xchat_list_int ((XCHAT_PLUGIN_HANDLE)->xchat_list_int)
+#define xchat_list_time ((XCHAT_PLUGIN_HANDLE)->xchat_list_time)
+#define xchat_list_next ((XCHAT_PLUGIN_HANDLE)->xchat_list_next)
+#define xchat_plugingui_add ((XCHAT_PLUGIN_HANDLE)->xchat_plugingui_add)
+#define xchat_plugingui_remove ((XCHAT_PLUGIN_HANDLE)->xchat_plugingui_remove)
+#define xchat_emit_print ((XCHAT_PLUGIN_HANDLE)->xchat_emit_print)
+#define xchat_gettext ((XCHAT_PLUGIN_HANDLE)->xchat_gettext)
+#define xchat_send_modes ((XCHAT_PLUGIN_HANDLE)->xchat_send_modes)
+#define xchat_strip ((XCHAT_PLUGIN_HANDLE)->xchat_strip)
+#define xchat_free ((XCHAT_PLUGIN_HANDLE)->xchat_free)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/src/common/xchat.c b/src/common/xchat.c
new file mode 100644
index 00000000..afac9a0e
--- /dev/null
+++ b/src/common/xchat.c
@@ -0,0 +1,951 @@
+/* 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 <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define WANTSOCKET
+#include "inet.h"
+
+#ifndef WIN32
+#include <sys/wait.h>
+#include <signal.h>
+#endif
+
+#include "xchat.h"
+#include "fe.h"
+#include "util.h"
+#include "cfgfiles.h"
+#include "chanopt.h"
+#include "ignore.h"
+#include "xchat-plugin.h"
+#include "plugin.h"
+#include "plugin-timer.h"
+#include "notify.h"
+#include "server.h"
+#include "servlist.h"
+#include "outbound.h"
+#include "text.h"
+#include "url.h"
+#include "xchatc.h"
+
+#ifdef USE_OPENSSL
+#include <openssl/ssl.h>		  /* SSL_() */
+#include "ssl.h"
+#endif
+
+#ifdef USE_MSPROXY
+#include "msproxy.h"
+#endif
+
+#ifdef USE_LIBPROXY
+#include <proxy.h>
+#endif
+
+GSList *popup_list = 0;
+GSList *button_list = 0;
+GSList *dlgbutton_list = 0;
+GSList *command_list = 0;
+GSList *ctcp_list = 0;
+GSList *replace_list = 0;
+GSList *sess_list = 0;
+GSList *dcc_list = 0;
+GSList *ignore_list = 0;
+GSList *usermenu_list = 0;
+GSList *urlhandler_list = 0;
+GSList *tabmenu_list = 0;
+
+static int in_xchat_exit = FALSE;
+int xchat_is_quitting = FALSE;
+/* command-line args */
+int arg_dont_autoconnect = FALSE;
+int arg_skip_plugins = FALSE;
+char *arg_url = NULL;
+char *arg_command = NULL;
+gint arg_existing = FALSE;
+
+#ifdef USE_DBUS
+#include "dbus/dbus-client.h"
+#include "dbus/dbus-plugin.h"
+#endif /* USE_DBUS */
+
+struct session *current_tab;
+struct session *current_sess = 0;
+struct xchatprefs prefs;
+
+#ifdef USE_OPENSSL
+SSL_CTX *ctx = NULL;
+#endif
+
+#ifdef USE_LIBPROXY
+pxProxyFactory *libproxy_factory;
+#endif
+
+int
+is_session (session * sess)
+{
+	return g_slist_find (sess_list, sess) ? 1 : 0;
+}
+
+session *
+find_dialog (server *serv, char *nick)
+{
+	GSList *list = sess_list;
+	session *sess;
+
+	while (list)
+	{
+		sess = list->data;
+		if (sess->server == serv && sess->type == SESS_DIALOG)
+		{
+			if (!serv->p_cmp (nick, sess->channel))
+				return (sess);
+		}
+		list = list->next;
+	}
+	return 0;
+}
+
+session *
+find_channel (server *serv, char *chan)
+{
+	session *sess;
+	GSList *list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+		if ((!serv || serv == sess->server) && sess->type != SESS_DIALOG)
+		{
+			if (!serv->p_cmp (chan, sess->channel))
+				return sess;
+		}
+		list = list->next;
+	}
+	return 0;
+}
+
+static void
+lagcheck_update (void)
+{
+	server *serv;
+	GSList *list = serv_list;
+	
+	if (!prefs.lagometer)
+		return;
+
+	while (list)
+	{
+		serv = list->data;
+		if (serv->lag_sent)
+			fe_set_lag (serv, -1);
+
+		list = list->next;
+	}
+}
+
+void
+lag_check (void)
+{
+	server *serv;
+	GSList *list = serv_list;
+	unsigned long tim;
+	char tbuf[128];
+	time_t now = time (0);
+	int lag;
+
+	tim = make_ping_time ();
+
+	while (list)
+	{
+		serv = list->data;
+		if (serv->connected && serv->end_of_motd)
+		{
+			lag = now - serv->ping_recv;
+			if (prefs.pingtimeout && lag > prefs.pingtimeout && lag > 0)
+			{
+				sprintf (tbuf, "%d", lag);
+				EMIT_SIGNAL (XP_TE_PINGTIMEOUT, serv->server_session, tbuf, NULL,
+								 NULL, NULL, 0);
+				if (prefs.autoreconnect)
+					serv->auto_reconnect (serv, FALSE, -1);
+			} else
+			{
+				snprintf (tbuf, sizeof (tbuf), "LAG%lu", tim);
+				serv->p_ping (serv, "", tbuf);
+				serv->lag_sent = tim;
+				fe_set_lag (serv, -1);
+			}
+		}
+		list = list->next;
+	}
+}
+
+static int
+away_check (void)
+{
+	session *sess;
+	GSList *list;
+	int full, sent, loop = 0;
+
+	if (!prefs.away_track || prefs.away_size_max < 1)
+		return 1;
+
+doover:
+	/* request an update of AWAY status of 1 channel every 30 seconds */
+	full = TRUE;
+	sent = 0;	/* number of WHOs (users) requested */
+	list = sess_list;
+	while (list)
+	{
+		sess = list->data;
+
+		if (sess->server->connected &&
+			 sess->type == SESS_CHANNEL &&
+			 sess->channel[0] &&
+			 sess->total <= prefs.away_size_max)
+		{
+			if (!sess->done_away_check)
+			{
+				full = FALSE;
+
+				/* if we're under 31 WHOs, send another channels worth */
+				if (sent < 31 && !sess->doing_who)
+				{
+					sess->done_away_check = TRUE;
+					sess->doing_who = TRUE;
+					/* this'll send a WHO #channel */
+					sess->server->p_away_status (sess->server, sess->channel);
+					sent += sess->total;
+				}
+			}
+		}
+
+		list = list->next;
+	}
+
+	/* done them all, reset done_away_check to FALSE and start over */
+	if (full)
+	{
+		list = sess_list;
+		while (list)
+		{
+			sess = list->data;
+			sess->done_away_check = FALSE;
+			list = list->next;
+		}
+		loop++;
+		if (loop < 2)
+			goto doover;
+	}
+
+	return 1;
+}
+
+static int
+xchat_misc_checks (void)		/* this gets called every 1/2 second */
+{
+	static int count = 0;
+#ifdef USE_MSPROXY
+	static int count2 = 0;
+#endif
+
+	count++;
+
+	lagcheck_update ();			/* every 500ms */
+
+	if (count % 2)
+		dcc_check_timeouts ();	/* every 1 second */
+
+	if (count >= 60)				/* every 30 seconds */
+	{
+		if (prefs.lagometer)
+			lag_check ();
+		count = 0;
+	}
+
+#ifdef USE_MSPROXY	
+	count2++;
+	if (count2 >= 720)			/* 720 every 6 minutes */
+	{
+		msproxy_keepalive ();
+		count2 = 0;
+	}
+#endif
+
+	return 1;
+}
+
+/* executed when the first irc window opens */
+
+static void
+irc_init (session *sess)
+{
+	static int done_init = FALSE;
+	char buf[512];
+
+	if (done_init)
+		return;
+
+	done_init = TRUE;
+
+	plugin_add (sess, NULL, NULL, timer_plugin_init, NULL, NULL, FALSE);
+
+#ifdef USE_PLUGIN
+	if (!arg_skip_plugins)
+		plugin_auto_load (sess);	/* autoload ~/.xchat *.so */
+#endif
+
+#ifdef USE_DBUS
+	plugin_add (sess, NULL, NULL, dbus_plugin_init, NULL, NULL, FALSE);
+#endif
+
+	if (prefs.notify_timeout)
+		notify_tag = fe_timeout_add (prefs.notify_timeout * 1000,
+											  notify_checklist, 0);
+
+	fe_timeout_add (prefs.away_timeout * 1000, away_check, 0);
+	fe_timeout_add (500, xchat_misc_checks, 0);
+
+	if (arg_url != NULL)
+	{
+		snprintf (buf, sizeof (buf), "server %s", arg_url);
+		handle_command (sess, buf, FALSE);
+		g_free (arg_url);	/* from GOption */
+	}
+
+	if (arg_command != NULL)
+	{
+		g_free (arg_command);
+	}
+
+	/* load -e ~/.xchat2/startup.txt */
+	snprintf (buf, sizeof (buf), "%s/%s", get_xdir_fs (), "startup.txt");
+	load_perform_file (sess, buf);
+}
+
+static session *
+session_new (server *serv, char *from, int type, int focus)
+{
+	session *sess;
+
+	sess = malloc (sizeof (struct session));
+	memset (sess, 0, sizeof (struct session));
+
+	sess->server = serv;
+	sess->logfd = -1;
+	sess->scrollfd = -1;
+	sess->type = type;
+
+	sess->alert_beep = SET_DEFAULT;
+	sess->alert_taskbar = SET_DEFAULT;
+	sess->alert_tray = SET_DEFAULT;
+
+	sess->text_hidejoinpart = SET_DEFAULT;
+	sess->text_logging = SET_DEFAULT;
+	sess->text_scrollback = SET_DEFAULT;
+
+	if (from != NULL)
+		safe_strcpy (sess->channel, from, CHANLEN);
+
+	sess_list = g_slist_prepend (sess_list, sess);
+
+	fe_new_window (sess, focus);
+
+	return sess;
+}
+
+session *
+new_ircwindow (server *serv, char *name, int type, int focus)
+{
+	session *sess;
+
+	switch (type)
+	{
+	case SESS_SERVER:
+		serv = server_new ();
+		if (prefs.use_server_tab)
+			sess = session_new (serv, name, SESS_SERVER, focus);
+		else
+			sess = session_new (serv, name, SESS_CHANNEL, focus);
+		serv->server_session = sess;
+		serv->front_session = sess;
+		break;
+	case SESS_DIALOG:
+		sess = session_new (serv, name, type, focus);
+		log_open_or_close (sess);
+		break;
+	default:
+/*	case SESS_CHANNEL:
+	case SESS_NOTICES:
+	case SESS_SNOTICES:*/
+		sess = session_new (serv, name, type, focus);
+		break;
+	}
+
+	irc_init (sess);
+	scrollback_load (sess);
+	chanopt_load (sess);
+	plugin_emit_dummy_print (sess, "Open Context");
+
+	return sess;
+}
+
+static void
+exec_notify_kill (session * sess)
+{
+#ifndef WIN32
+	struct nbexec *re;
+	if (sess->running_exec != NULL)
+	{
+		re = sess->running_exec;
+		sess->running_exec = NULL;
+		kill (re->childpid, SIGKILL);
+		waitpid (re->childpid, NULL, WNOHANG);
+		fe_input_remove (re->iotag);
+		close (re->myfd);
+		if (re->linebuf)
+			free(re->linebuf);
+		free (re);
+	}
+#endif
+}
+
+static void
+send_quit_or_part (session * killsess)
+{
+	int willquit = TRUE;
+	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)
+			{
+				killserv->flush_queue (killserv);
+				server_sendquit (killsess);
+				killserv->sent_quit = TRUE;
+			}
+		} else
+		{
+			if (killsess->type == SESS_CHANNEL && killsess->channel[0] &&
+				 !killserv->sent_quit)
+			{
+				server_sendpart (killserv, killsess->channel, 0);
+			}
+		}
+	}
+}
+
+void
+session_free (session *killsess)
+{
+	server *killserv = killsess->server;
+	session *sess;
+	GSList *list;
+
+	plugin_emit_dummy_print (killsess, "Close Context");
+
+	if (current_tab == killsess)
+		current_tab = NULL;
+
+	if (killserv->server_session == killsess)
+		killserv->server_session = 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;
+				if (!killserv->server_session)
+					killserv->server_session = sess;
+				break;
+			}
+			list = list->next;
+		}
+	}
+
+	if (!killserv->server_session)
+		killserv->server_session = killserv->front_session;
+
+	sess_list = g_slist_remove (sess_list, killsess);
+
+	if (killsess->type == SESS_CHANNEL)
+		userlist_free (killsess);
+
+	exec_notify_kill (killsess);
+
+	log_close (killsess);
+	scrollback_close (killsess);
+	chanopt_save (killsess);
+
+	send_quit_or_part (killsess);
+
+	history_free (&killsess->history);
+	if (killsess->topic)
+		free (killsess->topic);
+	if (killsess->current_modes)
+		free (killsess->current_modes);
+
+	fe_session_callback (killsess);
+
+	if (current_sess == killsess)
+	{
+		current_sess = NULL;
+		if (sess_list)
+			current_sess = sess_list->data;
+	}
+
+	free (killsess);
+
+	if (!sess_list && !in_xchat_exit)
+		xchat_exit ();						/* 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;
+	}
+
+	server_free (killserv);
+}
+
+static void
+free_sessions (void)
+{
+	GSList *list = sess_list;
+
+	while (list)
+	{
+		fe_close_window (list->data);
+		list = sess_list;
+	}
+}
+
+
+static char defaultconf_ctcp[] =
+	"NAME TIME\n"				"CMD nctcp %s TIME %t\n\n"\
+	"NAME PING\n"				"CMD nctcp %s PING %d\n\n";
+
+static char defaultconf_replace[] =
+	"NAME teh\n"				"CMD the\n\n";
+/*	"NAME r\n"					"CMD are\n\n"\
+	"NAME u\n"					"CMD you\n\n"*/
+
+static char defaultconf_commands[] =
+	"NAME ACTION\n"		"CMD me &2\n\n"\
+	"NAME AME\n"			"CMD allchan me &2\n\n"\
+	"NAME ANICK\n"			"CMD allserv nick &2\n\n"\
+	"NAME AMSG\n"			"CMD allchan say &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 GREP\n"			"CMD lastlog -r &2\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 SQUERY\n"		"CMD quote SQUERY %2 :&3\n\n"\
+	"NAME SSLSERVER\n"	"CMD server -ssl &2\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";
+
+static char defaultconf_urlhandlers[] =
+		"NAME Open Link in Opera\n"		"CMD !opera -remote 'openURL(%s)'\n\n";
+
+#ifdef USE_SIGACTION
+/* Close and open log files on SIGUSR1. Usefull for log rotating */
+
+static void 
+sigusr1_handler (int signal, siginfo_t *si, void *un)
+{
+	GSList *list = sess_list;
+	session *sess;
+
+	while (list)
+	{
+		sess = list->data;
+		log_open_or_close (sess);
+		list = list->next;
+	}
+}
+
+/* Execute /SIGUSR2 when SIGUSR2 received */
+
+static void
+sigusr2_handler (int signal, siginfo_t *si, void *un)
+{
+	session *sess = current_sess;
+
+	if (sess)
+		handle_command (sess, "SIGUSR2", FALSE);
+}
+#endif
+
+static gint
+xchat_auto_connect (gpointer userdata)
+{
+	servlist_auto_connect (NULL);
+	return 0;
+}
+
+static void
+xchat_init (void)
+{
+	char buf[3068];
+	const char *cs = NULL;
+
+#ifdef WIN32
+	WSADATA wsadata;
+
+#ifdef USE_IPV6
+	if (WSAStartup(0x0202, &wsadata) != 0)
+	{
+		MessageBox (NULL, "Cannot find winsock 2.2+", "Error", MB_OK);
+		exit (0);
+	}
+#else
+	WSAStartup(0x0101, &wsadata);
+#endif	/* !USE_IPV6 */
+#endif	/* !WIN32 */
+
+#ifdef USE_SIGACTION
+	struct sigaction act;
+
+	/* ignore SIGPIPE's */
+	act.sa_handler = SIG_IGN;
+	act.sa_flags = 0;
+	sigemptyset (&act.sa_mask);
+	sigaction (SIGPIPE, &act, NULL);
+
+	/* Deal with SIGUSR1's & SIGUSR2's */
+	act.sa_sigaction = sigusr1_handler;
+	act.sa_flags = 0;
+	sigemptyset (&act.sa_mask);
+	sigaction (SIGUSR1, &act, NULL);
+
+	act.sa_sigaction = sigusr2_handler;
+	act.sa_flags = 0;
+	sigemptyset (&act.sa_mask);
+	sigaction (SIGUSR2, &act, NULL);
+#else
+#ifndef WIN32
+	/* good enough for these old systems */
+	signal (SIGPIPE, SIG_IGN);
+#endif
+#endif
+
+	if (g_get_charset (&cs))
+		prefs.utf8_locale = TRUE;
+
+	load_text_events ();
+	sound_load ();
+	notify_load ();
+	ignore_load ();
+
+	snprintf (buf, sizeof (buf),
+		"NAME %s~%s~\n"				"CMD query %%s\n\n"\
+		"NAME %s~%s~\n"				"CMD send %%s\n\n"\
+		"NAME %s~%s~\n"				"CMD whois %%s %%s\n\n"\
+		"NAME %s~%s~\n"				"CMD notify -n ASK %%s\n\n"\
+
+		"NAME SUB\n"					"CMD %s\n\n"\
+			"NAME %s\n"					"CMD op %%a\n\n"\
+			"NAME %s\n"					"CMD deop %%a\n\n"\
+			"NAME SEP\n"				"CMD \n\n"\
+			"NAME %s\n"					"CMD voice %%a\n\n"\
+			"NAME %s\n"					"CMD devoice %%a\n"\
+			"NAME SEP\n"				"CMD \n\n"\
+			"NAME SUB\n"				"CMD %s\n\n"\
+				"NAME %s\n"				"CMD kick %%s\n\n"\
+				"NAME %s\n"				"CMD ban %%s\n\n"\
+				"NAME SEP\n"			"CMD \n\n"\
+				"NAME %s *!*@*.host\n""CMD ban %%s 0\n\n"\
+				"NAME %s *!*@domain\n""CMD ban %%s 1\n\n"\
+				"NAME %s *!*user@*.host\n""CMD ban %%s 2\n\n"\
+				"NAME %s *!*user@domain\n""CMD ban %%s 3\n\n"\
+				"NAME SEP\n"			"CMD \n\n"\
+				"NAME %s *!*@*.host\n""CMD kickban %%s 0\n\n"\
+				"NAME %s *!*@domain\n""CMD kickban %%s 1\n\n"\
+				"NAME %s *!*user@*.host\n""CMD kickban %%s 2\n\n"\
+				"NAME %s *!*user@domain\n""CMD kickban %%s 3\n\n"\
+			"NAME ENDSUB\n"			"CMD \n\n"\
+		"NAME ENDSUB\n"				"CMD \n\n",
+
+		_("_Open Dialog Window"), "xchat-dialog",
+		_("_Send a File"), "gtk-floppy",
+		_("_User Info (WhoIs)"), "gtk-info",
+		_("_Add to Friends List"), "gtk-add",
+		_("O_perator Actions"),
+
+		_("Give Ops"),
+		_("Take Ops"),
+		_("Give Voice"),
+		_("Take Voice"),
+
+		_("Kick/Ban"),
+		_("Kick"),
+		_("Ban"),
+		_("Ban"),
+		_("Ban"),
+		_("Ban"),
+		_("Ban"),
+		_("KickBan"),
+		_("KickBan"),
+		_("KickBan"),
+		_("KickBan"));
+
+	list_loadconf ("popup.conf", &popup_list, buf);
+
+	snprintf (buf, sizeof (buf),
+		"NAME %s\n"				"CMD part\n\n"
+		"NAME %s\n"				"CMD getstr # join \"%s\"\n\n"
+		"NAME %s\n"				"CMD quote LINKS\n\n"
+		"NAME %s\n"				"CMD ping\n\n"
+		"NAME TOGGLE %s\n"	"CMD irc_hide_version\n\n",
+				_("Leave Channel"),
+				_("Join Channel..."),
+				_("Enter Channel to Join:"),
+				_("Server Links"),
+				_("Ping Server"),
+				_("Hide Version"));
+	list_loadconf ("usermenu.conf", &usermenu_list, buf);
+
+	snprintf (buf, sizeof (buf),
+		"NAME %s\n"		"CMD op %%a\n\n"
+		"NAME %s\n"		"CMD deop %%a\n\n"
+		"NAME %s\n"		"CMD ban %%s\n\n"
+		"NAME %s\n"		"CMD getstr \"%s\" \"kick %%s\" \"%s\"\n\n"
+		"NAME %s\n"		"CMD send %%s\n\n"
+		"NAME %s\n"		"CMD query %%s\n\n",
+				_("Op"),
+				_("DeOp"),
+				_("Ban"),
+				_("Kick"),
+				_("bye"),
+				_("Enter reason to kick %s:"),
+				_("Sendfile"),
+				_("Dialog"));
+	list_loadconf ("buttons.conf", &button_list, buf);
+
+	snprintf (buf, sizeof (buf),
+		"NAME %s\n"				"CMD whois %%s %%s\n\n"
+		"NAME %s\n"				"CMD send %%s\n\n"
+		"NAME %s\n"				"CMD dcc chat %%s\n\n"
+		"NAME %s\n"				"CMD clear\n\n"
+		"NAME %s\n"				"CMD ping %%s\n\n",
+				_("WhoIs"),
+				_("Send"),
+				_("Chat"),
+				_("Clear"),
+				_("Ping"));
+	list_loadconf ("dlgbuttons.conf", &dlgbutton_list, buf);
+
+	list_loadconf ("tabmenu.conf", &tabmenu_list, NULL);
+	list_loadconf ("ctcpreply.conf", &ctcp_list, defaultconf_ctcp);
+	list_loadconf ("commands.conf", &command_list, defaultconf_commands);
+	list_loadconf ("replace.conf", &replace_list, defaultconf_replace);
+	list_loadconf ("urlhandlers.conf", &urlhandler_list,
+						defaultconf_urlhandlers);
+
+	servlist_init ();							/* load server list */
+
+	/* if we got a URL, don't open the server list GUI */
+	if (!prefs.slist_skip && !arg_url)
+		fe_serverlist_open (NULL);
+
+	/* turned OFF via -a arg */
+	if (!arg_dont_autoconnect)
+	{
+		/* do any auto connects */
+		if (!servlist_have_auto ())	/* if no new windows open .. */
+		{
+			/* and no serverlist gui ... */
+			if (prefs.slist_skip || arg_url)
+				/* we'll have to open one. */
+				new_ircwindow (NULL, NULL, SESS_SERVER, 0);
+		} else
+		{
+			fe_idle_add (xchat_auto_connect, NULL);
+		}
+	} else
+	{
+		if (prefs.slist_skip || arg_url)
+			new_ircwindow (NULL, NULL, SESS_SERVER, 0);
+	}
+}
+
+void
+xchat_exit (void)
+{
+	xchat_is_quitting = TRUE;
+	in_xchat_exit = TRUE;
+	plugin_kill_all ();
+	fe_cleanup ();
+	if (prefs.autosave)
+	{
+		save_config ();
+		if (prefs.save_pevents)
+			pevent_save (NULL);
+	}
+	if (prefs.autosave_url)
+		url_autosave ();
+	sound_save ();
+	notify_save ();
+	ignore_save ();
+	free_sessions ();
+	chanopt_save_all ();
+	servlist_cleanup ();
+	fe_exit ();
+}
+
+#ifndef WIN32
+
+static int
+child_handler (gpointer userdata)
+{
+	int pid = GPOINTER_TO_INT (userdata);
+
+	if (waitpid (pid, 0, WNOHANG) == pid)
+		return 0;					  /* remove timeout handler */
+	return 1;						  /* keep the timeout handler */
+}
+
+#endif
+
+void
+xchat_exec (const char *cmd)
+{
+#ifdef WIN32
+	util_exec (cmd);
+#else
+	int 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, GINT_TO_POINTER (pid));
+#endif
+}
+
+void
+xchat_execv (char * const argv[])
+{
+#ifdef WIN32
+	util_execv (argv);
+#else
+	int pid = util_execv (argv);
+	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, GINT_TO_POINTER (pid));
+#endif
+}
+
+int
+main (int argc, char *argv[])
+{
+	int ret;
+	
+	srand (time (0));	/* CL: do this only once! */
+
+#ifdef SOCKS
+	SOCKSinit (argv[0]);
+#endif
+
+	ret = fe_args (argc, argv);
+	if (ret != -1)
+		return ret;
+	
+#ifdef USE_DBUS
+	xchat_remote ();
+#endif
+
+	load_config ();
+
+#ifdef USE_LIBPROXY
+	libproxy_factory = px_proxy_factory_new();
+#endif
+
+	fe_init ();
+
+	xchat_init ();
+
+	fe_main ();
+
+#ifdef USE_LIBPROXY
+	px_proxy_factory_free(libproxy_factory);
+#endif
+
+#ifdef USE_OPENSSL
+	if (ctx)
+		_SSL_context_free (ctx);
+#endif
+
+#ifdef USE_DEBUG
+	xchat_mem_list ();
+#endif
+
+#ifdef WIN32
+	WSACleanup ();
+#endif
+
+	return 0;
+}
diff --git a/src/common/xchat.h b/src/common/xchat.h
new file mode 100644
index 00000000..db7a6c4b
--- /dev/null
+++ b/src/common/xchat.h
@@ -0,0 +1,575 @@
+#include "../../config.h"
+
+#include <glib/gslist.h>
+#include <glib/glist.h>
+#include <glib/gutils.h>
+#include <glib/giochannel.h>
+#include <glib/gstrfuncs.h>
+#include <time.h>			/* need time_t */
+
+#ifndef XCHAT_H
+#define XCHAT_H
+
+#include "history.h"
+
+#ifndef HAVE_SNPRINTF
+#define snprintf g_snprintf
+#endif
+
+#ifndef HAVE_VSNPRINTF
+#define vsnprintf g_vsnprintf
+#endif
+
+#ifdef USE_DEBUG
+#define malloc(n) xchat_malloc(n, __FILE__, __LINE__)
+#define realloc(n, m) xchat_realloc(n, m, __FILE__, __LINE__)
+#define free(n) xchat_dfree(n, __FILE__, __LINE__)
+#define strdup(n) xchat_strdup(n, __FILE__, __LINE__)
+void *xchat_malloc (int size, char *file, int line);
+void *xchat_strdup (char *str, char *file, int line);
+void xchat_dfree (void *buf, char *file, int line);
+void *xchat_realloc (char *old, int len, char *file, int line);
+#endif
+
+#ifdef SOCKS
+#ifdef __sgi
+#include <sys/time.h>
+#define INCLUDE_PROTOTYPES 1
+#endif
+#include <socks.h>
+#endif
+
+#ifdef USE_OPENSSL
+#include <openssl/ssl.h>		  /* SSL_() */
+#endif
+
+#ifdef __EMX__						  /* for o/s 2 */
+#define OFLAGS O_BINARY
+#define strcasecmp stricmp
+#define strncasecmp strnicmp
+#define PATH_MAX MAXPATHLEN
+#define FILEPATH_LEN_MAX MAXPATHLEN
+#endif
+
+/* force a 32bit CMP.L */
+#define CMPL(a, c0, c1, c2, c3) (a == (guint32)(c0 | (c1 << 8) | (c2 << 16) | (c3 << 24)))
+#define WORDL(c0, c1, c2, c3) (guint32)(c0 | (c1 << 8) | (c2 << 16) | (c3 << 24))
+#define WORDW(c0, c1) (guint16)(c0 | (c1 << 8))
+
+#ifdef WIN32						/* for win32 */
+#define OFLAGS O_BINARY
+#define sleep(t) _sleep(t*1000)
+#include <direct.h>
+#define	F_OK	0
+#define	X_OK	1
+#define	W_OK	2
+#define	R_OK	4
+#ifndef S_ISDIR
+#define	S_ISDIR(m)	((m) & _S_IFDIR)
+#endif
+#define NETWORK_PRIVATE
+#else									/* for unix */
+#define OFLAGS 0
+#endif
+
+#define FONTNAMELEN	127
+#define PATHLEN		255
+#define DOMAINLEN	100
+#define NICKLEN		64				/* including the NULL, so 63 really */
+#define CHANLEN		300
+#define PDIWORDS		32
+#define USERNAMELEN 10
+#define HIDDEN_CHAR	8			/* invisible character for xtext */
+
+#if defined(ENABLE_NLS) && !defined(_)
+#  include <libintl.h>
+#  define _(x) gettext(x)
+#  ifdef gettext_noop
+#    define N_(String) gettext_noop (String)
+#  else
+#    define N_(String) (String)
+#  endif
+#endif
+#if !defined(_)
+#  define N_(String) (String)
+#  define _(x) (x)
+#endif
+
+struct nbexec
+{
+	int myfd;
+	int childpid;
+	int tochannel;					  /* making this int keeps the struct 4-byte aligned */
+	int iotag;
+	char *linebuf;
+	int buffill;
+	struct session *sess;
+};
+
+struct xchatprefs
+{
+	char nick1[NICKLEN];
+	char nick2[NICKLEN];
+	char nick3[NICKLEN];
+	char realname[127];
+	char username[127];
+	char nick_suffix[4];			/* Only ever holds a one-character string. */
+	char awayreason[256];
+	char quitreason[256];
+	char partreason[256];
+	char font_normal[FONTNAMELEN + 1];
+	char doubleclickuser[256];
+	char sounddir[PATHLEN + 1];
+	char soundcmd[PATHLEN + 1];
+	char background[PATHLEN + 1];
+	char dccdir[PATHLEN + 1];
+	char dcc_completed_dir[PATHLEN + 1];
+	char irc_extra_hilight[300];
+	char irc_no_hilight[300];
+	char irc_nick_hilight[300];
+	char dnsprogram[72];
+	char hostname[127];
+	char cmdchar[4];
+	char logmask[256];
+	char stamp_format[64];
+	char timestamp_log_format[64];
+	char irc_id_ytext[64];
+	char irc_id_ntext[64];
+
+	char proxy_host[64];
+	int proxy_port;
+	int proxy_type; /* 0=disabled, 1=wingate 2=socks4, 3=socks5, 4=http */
+	int proxy_use; /* 0=all 1=IRC_ONLY 2=DCC_ONLY */
+	unsigned int proxy_auth;
+	char proxy_user[32];
+	char proxy_pass[32];
+
+	int first_dcc_send_port;
+	int last_dcc_send_port;
+
+	int tint_red;
+	int tint_green;
+	int tint_blue;
+
+	int away_timeout;
+	int away_size_max;
+
+	int gui_pane_left_size;
+	int gui_pane_right_size;
+
+	int gui_ulist_pos;
+	int tab_pos;
+
+	int _tabs_position;
+	int tab_layout;
+	int max_auto_indent;
+	int dcc_blocksize;
+	int max_lines;
+	int notify_timeout;
+	int dcctimeout;
+	int dccstalltimeout;
+	int dcc_global_max_get_cps;
+	int dcc_global_max_send_cps;
+	int dcc_max_get_cps;
+	int dcc_max_send_cps;
+	int mainwindow_left;
+	int mainwindow_top;
+	int mainwindow_width;
+	int mainwindow_height;
+	int completion_sort;
+	int gui_win_state;
+	int gui_url_mod;
+	int gui_usermenu;
+	int gui_join_dialog;
+	int gui_quit_dialog;
+	int dialog_left;
+	int dialog_top;
+	int dialog_width;
+	int dialog_height;
+	int dccpermissions;
+	int recon_delay;
+	int bantype;
+	int userlist_sort;
+	guint32 local_ip;
+	guint32 dcc_ip;
+	char dcc_ip_str[DOMAINLEN + 1];
+
+	unsigned int tab_small;
+	unsigned int tab_sort;
+	unsigned int mainwindow_save;
+	unsigned int perc_color;
+	unsigned int perc_ascii;
+	unsigned int autosave;
+	unsigned int autodialog;
+	unsigned int autosave_url;
+	unsigned int autoreconnect;
+	unsigned int autoreconnectonfail;
+	unsigned int invisible;
+	unsigned int servernotice;
+	unsigned int wallops;
+	unsigned int skipmotd;
+	unsigned int autorejoin;
+	unsigned int colorednicks;
+	unsigned int chanmodebuttons;
+	unsigned int userlistbuttons;
+	unsigned int showhostname_in_userlist;
+	unsigned int nickcompletion;
+	unsigned int completion_amount;
+	unsigned int tabchannels;
+	unsigned int paned_userlist;
+	unsigned int autodccchat;
+	unsigned int autodccsend;
+	unsigned int autoresume;
+	unsigned int autoopendccsendwindow;
+	unsigned int autoopendccrecvwindow;
+	unsigned int autoopendccchatwindow;
+	unsigned int transparent;
+	unsigned int stripcolor;
+	unsigned int timestamp;
+	unsigned int fastdccsend;
+	unsigned int dcc_send_fillspaces;
+	unsigned int dcc_remove;
+	unsigned int slist_fav;
+	unsigned int slist_skip;
+	unsigned int slist_select;
+	unsigned int filterbeep;
+
+	unsigned int input_balloon_chans;
+	unsigned int input_balloon_hilight;
+	unsigned int input_balloon_priv;
+	unsigned int input_balloon_time;
+
+	unsigned int input_beep_chans;
+	unsigned int input_beep_hilight;
+	unsigned int input_beep_priv;
+
+	unsigned int input_flash_chans;
+	unsigned int input_flash_hilight;
+	unsigned int input_flash_priv;
+
+	unsigned int input_tray_chans;
+	unsigned int input_tray_hilight;
+	unsigned int input_tray_priv;
+
+	unsigned int truncchans;
+	unsigned int privmsgtab;
+	unsigned int irc_join_delay;
+	unsigned int logging;
+	unsigned int timestamp_logs;
+	unsigned int newtabstofront;
+	unsigned int dccwithnick;
+	unsigned int hidever;
+	unsigned int ip_from_server;
+	unsigned int raw_modes;
+	unsigned int show_away_once;
+	unsigned int show_away_message;
+	unsigned int auto_unmark_away;
+	unsigned int away_track;
+	unsigned int userhost;
+	unsigned int irc_whois_front;
+	unsigned int use_server_tab;
+	unsigned int notices_tabs;
+	unsigned int style_namelistgad;
+	unsigned int style_inputbox;
+	unsigned int windows_as_tabs;
+	unsigned int indent_nicks;
+	unsigned int text_replay;
+	unsigned int show_marker;
+	unsigned int show_separator;
+	unsigned int thin_separator;
+	unsigned int auto_indent;
+	unsigned int wordwrap;
+	unsigned int gui_input_spell;
+	unsigned int gui_tray;
+	unsigned int gui_tray_flags;
+	unsigned int gui_tweaks;
+	unsigned int _gui_ulist_left;
+	unsigned int throttle;
+	unsigned int topicbar;
+	unsigned int hideuserlist;
+	unsigned int hidemenu;
+	unsigned int perlwarnings;
+	unsigned int lagometer;
+	unsigned int throttlemeter;
+	unsigned int pingtimeout;
+	unsigned int whois_on_notifyonline;
+	unsigned int wait_on_exit;
+	unsigned int confmode;
+	unsigned int utf8_locale;
+	unsigned int identd;
+
+	unsigned int ctcp_number_limit;	/*flood */
+	unsigned int ctcp_time_limit;	/*seconds of floods */
+
+	unsigned int msg_number_limit;	/*same deal */
+	unsigned int msg_time_limit;
+
+	/* Tells us if we need to save, only when they've been edited.
+		This is so that we continue using internal defaults (which can
+		change in the next release) until the user edits them. */
+	unsigned int save_pevents:1;
+};
+
+/* Session types */
+#define SESS_SERVER	1
+#define SESS_CHANNEL	2
+#define SESS_DIALOG	3
+#define SESS_NOTICES	4
+#define SESS_SNOTICES	5
+
+/* Per-Channel Settings */
+#define SET_OFF 0
+#define SET_ON 1
+#define SET_DEFAULT 2 /* use global setting */
+
+typedef struct session
+{
+	/* Per-Channel Alerts */
+	/* use a byte, because we need a pointer to each element */
+	guint8 alert_beep;
+	guint8 alert_taskbar;
+	guint8 alert_tray;
+
+	/* Per-Channel Settings */
+	guint8 text_hidejoinpart;
+	guint8 text_logging;
+	guint8 text_scrollback;
+
+	struct server *server;
+	void *usertree_alpha;			/* pure alphabetical tree */
+	void *usertree;					/* ordered with Ops first */
+	struct User *me;					/* points to myself in the usertree */
+	char channel[CHANLEN];
+	char waitchannel[CHANLEN];		  /* waiting to join channel (/join sent) */
+	char willjoinchannel[CHANLEN];	  /* will issue /join for this channel */
+	char channelkey[64];			  /* XXX correct max length? */
+	int limit;						  /* channel user limit */
+	int logfd;
+	int scrollfd;							/* scrollback filedes */
+	int scrollwritten;					/* number of lines written */
+
+	char lastnick[NICKLEN];			  /* last nick you /msg'ed */
+
+	struct history history;
+
+	int ops;								/* num. of ops in channel */
+	int hops;						  /* num. of half-oped users */
+	int voices;							/* num. of voiced people */
+	int total;							/* num. of users in channel */
+
+	char *quitreason;
+	char *topic;
+	char *current_modes;					/* free() me */
+
+	int mode_timeout_tag;
+
+	struct session *lastlog_sess;
+	struct nbexec *running_exec;
+
+	struct session_gui *gui;		/* initialized by fe_new_window */
+	struct restore_gui *res;
+
+	int type;					/* SESS_* */
+
+	int new_data:1;			/* new data avail? (purple tab) */
+	int nick_said:1;		/* your nick mentioned? (blue tab) */
+	int msg_said:1;			/* new msg available? (red tab) */
+
+	int ignore_date:1;
+	int ignore_mode:1;
+	int ignore_names:1;
+	int end_of_names:1;
+	int doing_who:1;		/* /who sent on this channel */
+	int done_away_check:1;	/* done checking for away status changes */
+	unsigned int lastlog_regexp:1;	/* this is a lastlog and using regexp */
+} session;
+
+struct msproxy_state_t
+{
+	gint32				clientid;
+	gint32				serverid;
+	unsigned char		seq_recv;		/* seq number of last packet recv.	*/
+	unsigned char		seq_sent;		/* seq number of last packet sent.	*/
+};
+
+typedef struct server
+{
+	/*  server control operations (in server*.c) */
+	void (*connect)(struct server *, char *hostname, int port, int no_login);
+	void (*disconnect)(struct session *, int sendquit, int err);
+	int  (*cleanup)(struct server *);
+	void (*flush_queue)(struct server *);
+	void (*auto_reconnect)(struct server *, int send_quit, int err);
+	/* irc protocol functions (in proto*.c) */
+	void (*p_inline)(struct server *, char *buf, int len);
+	void (*p_invite)(struct server *, char *channel, char *nick);
+	void (*p_cycle)(struct server *, char *channel, char *key);
+	void (*p_ctcp)(struct server *, char *to, char *msg);
+	void (*p_nctcp)(struct server *, char *to, char *msg);
+	void (*p_quit)(struct server *, char *reason);
+	void (*p_kick)(struct server *, char *channel, char *nick, char *reason);
+	void (*p_part)(struct server *, char *channel, char *reason);
+	void (*p_ns_identify)(struct server *, char *pass);
+	void (*p_ns_ghost)(struct server *, char *usname, char *pass);
+	void (*p_join)(struct server *, char *channel, char *key);
+	void (*p_join_list)(struct server *, GSList *channels, GSList *keys);
+	void (*p_login)(struct server *, char *user, char *realname);
+	void (*p_join_info)(struct server *, char *channel);
+	void (*p_mode)(struct server *, char *target, char *mode);
+	void (*p_user_list)(struct server *, char *channel);
+	void (*p_away_status)(struct server *, char *channel);
+	void (*p_whois)(struct server *, char *nicks);
+	void (*p_get_ip)(struct server *, char *nick);
+	void (*p_get_ip_uh)(struct server *, char *nick);
+	void (*p_set_back)(struct server *);
+	void (*p_set_away)(struct server *, char *reason);
+	void (*p_message)(struct server *, char *channel, char *text);
+	void (*p_action)(struct server *, char *channel, char *act);
+	void (*p_notice)(struct server *, char *channel, char *text);
+	void (*p_topic)(struct server *, char *channel, char *topic);
+	void (*p_list_channels)(struct server *, char *arg, int min_users);
+	void (*p_change_nick)(struct server *, char *new_nick);
+	void (*p_names)(struct server *, char *channel);
+	void (*p_ping)(struct server *, char *to, char *timestring);
+/*	void (*p_set_away)(struct server *);*/
+	int (*p_raw)(struct server *, char *raw);
+	int (*p_cmp)(const char *s1, const char *s2);
+
+	int port;
+	int sok;					/* is equal to sok4 or sok6 (the one we are using) */
+	int sok4;					/* tcp4 socket */
+	int sok6;					/* tcp6 socket */
+	int proxy_type;
+	int proxy_sok;				/* Additional information for MS Proxy beast */
+	int proxy_sok4;
+	int proxy_sok6;
+	struct msproxy_state_t msp_state;
+	int id;					/* unique ID number (for plugin API) */
+#ifdef USE_OPENSSL
+	SSL *ssl;
+	int ssl_do_connect_tag;
+#else
+	void *ssl;
+#endif
+	int childread;
+	int childwrite;
+	int childpid;
+	int iotag;
+	int recondelay_tag;				/* reconnect delay timeout */
+	int joindelay_tag;				/* waiting before we send JOIN */
+	char hostname[128];				/* real ip number */
+	char servername[128];			/* what the server says is its name */
+	char password[86];
+	char nick[NICKLEN];
+	char linebuf[2048];				/* RFC says 512 chars including \r\n */
+	char *last_away_reason;
+	int pos;								/* current position in linebuf */
+	int nickcount;
+	int nickservtype;					/* 0=/MSG nickserv 1=/NICKSERV 2=/NS */
+
+	char *chantypes;					/* for 005 numeric - free me */
+	char *chanmodes;					/* for 005 numeric - free me */
+	char *nick_prefixes;				/* e.g. "*@%+" */
+	char *nick_modes;					/* e.g. "aohv" */
+	char *bad_nick_prefixes;		/* for ircd that doesn't give the modes */
+	int modes_per_line;				/* 6 on undernet, 4 on efnet etc... */
+
+	void *network;						/* points to entry in servlist.c or NULL! */
+
+	GSList *outbound_queue;
+	time_t next_send;						/* cptr->since in ircu */
+	time_t prev_now;					/* previous now-time */
+	int sendq_len;						/* queue size */
+	int lag;								/* milliseconds */
+
+	struct session *front_session;	/* front-most window/tab */
+	struct session *server_session;	/* server window/tab */
+
+	struct server_gui *gui;		  /* initialized by fe_new_server */
+
+	unsigned int ctcp_counter;	  /*flood */
+	time_t ctcp_last_time;
+
+	unsigned int msg_counter;	  /*counts the msg tab opened in a certain time */
+	time_t msg_last_time;
+
+	/*time_t connect_time;*/				/* when did it connect? */
+	time_t lag_sent;
+	time_t ping_recv;					/* when we last got a ping reply */
+	time_t away_time;					/* when we were marked away */
+
+	char *encoding;					/* NULL for system */
+	char *autojoin;			/* list of channels & keys to join */
+
+	unsigned int motd_skipped:1;
+	unsigned int connected:1;
+	unsigned int connecting:1;
+	unsigned int no_login:1;
+	unsigned int skip_next_userhost:1;/* used for "get my ip from server" */
+	unsigned int skip_next_whois:1;	/* hide whois output */
+	unsigned int inside_whois:1;
+	unsigned int doing_dns:1;			/* /dns has been done */
+	unsigned int end_of_motd:1;		/* end of motd reached (logged in) */
+	unsigned int sent_quit:1;			/* sent a QUIT already? */
+	unsigned int use_listargs:1;		/* undernet and dalnet need /list >0,<10000 */
+	unsigned int is_away:1;
+	unsigned int reconnect_away:1;	/* whether to reconnect in is_away state */
+	unsigned int dont_use_proxy:1;	/* to proxy or not to proxy */
+	unsigned int supports_watch:1;	/* supports the WATCH command */
+	unsigned int bad_prefix:1;			/* gave us a bad PREFIX= 005 number */
+	unsigned int have_namesx:1;		/* 005 tokens NAMESX and UHNAMES */
+	unsigned int have_uhnames:1;
+	unsigned int have_whox:1;		/* have undernet's WHOX features */
+	unsigned int have_capab:1;		/* supports CAPAB (005 tells us) */
+	unsigned int have_idmsg:1;		/* freenode's IDENTIFY-MSG */
+	unsigned int have_except:1;	/* ban exemptions +e */
+	unsigned int using_cp1255:1;	/* encoding is CP1255/WINDOWS-1255? */
+	unsigned int using_irc:1;		/* encoding is "IRC" (CP1252/UTF-8 hybrid)? */
+	unsigned int use_who:1;			/* whether to use WHO command to get dcc_ip */
+#ifdef USE_OPENSSL
+	unsigned int use_ssl:1;				  /* is server SSL capable? */
+	unsigned int accept_invalid_cert:1;/* ignore result of server's cert. verify */
+#endif
+} server;
+
+typedef int (*cmd_callback) (struct session * sess, char *tbuf, char *word[],
+									  char *word_eol[]);
+
+struct commands
+{
+	char *name;
+	cmd_callback callback;
+	char needserver;
+	char needchannel;
+	gint16 handle_quotes;
+	char *help;
+};
+
+struct away_msg
+{
+	struct server *server;
+	char nick[NICKLEN];
+	char *message;
+};
+
+/* not just for popups, but used for usercommands, ctcp replies,
+   userlist buttons etc */
+
+struct popup
+{
+	char *cmd;
+	char *name;
+};
+
+/* CL: get a random int in the range [0..n-1]. DON'T use rand() % n, it gives terrible results. */
+#define RAND_INT(n) ((int)(rand() / (RAND_MAX + 1.0) * (n)))
+
+#if defined(WIN32) && GLIB_CHECK_VERSION(2,4,0)
+#define xchat_filename_from_utf8 g_locale_from_utf8
+#define xchat_filename_to_utf8 g_locale_to_utf8
+#else
+#define xchat_filename_from_utf8 g_filename_from_utf8
+#define xchat_filename_to_utf8 g_filename_to_utf8
+#endif
+
+#endif
diff --git a/src/common/xchatc.h b/src/common/xchatc.h
new file mode 100644
index 00000000..c22e17dd
--- /dev/null
+++ b/src/common/xchatc.h
@@ -0,0 +1,39 @@
+#ifndef XCHAT_C_H
+#define XCHAT_C_H
+
+extern struct xchatprefs prefs;
+
+extern int xchat_is_quitting;
+extern gint arg_skip_plugins;	/* command-line args */
+extern gint arg_dont_autoconnect;
+extern char *arg_url;
+extern char *arg_command;
+extern gint arg_existing;
+
+extern session *current_sess;
+extern session *current_tab;
+
+extern GSList *popup_list;
+extern GSList *button_list;
+extern GSList *dlgbutton_list;
+extern GSList *command_list;
+extern GSList *ctcp_list;
+extern GSList *replace_list;
+extern GSList *sess_list;
+extern GSList *dcc_list;
+extern GSList *ignore_list;
+extern GSList *usermenu_list;
+extern GSList *urlhandler_list;
+extern GSList *tabmenu_list;
+
+session * find_channel (server *serv, char *chan);
+session * find_dialog (server *serv, char *nick);
+session * new_ircwindow (server *serv, char *name, int type, int focus);
+int is_session (session * sess);
+void session_free (session *killsess);
+void lag_check (void);
+void xchat_exit (void);
+void xchat_exec (const char *cmd);
+void xchat_execv (char * const argv[]);
+
+#endif