summary refs log tree commit diff stats
path: root/src/common/outbound.c
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/outbound.c
parentf16af8be941b596dedac3bf4e371ee2d21f4b598 (diff)
add xchat r1489
Diffstat (limited to 'src/common/outbound.c')
-rw-r--r--src/common/outbound.c4403
1 files changed, 4403 insertions, 0 deletions
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);
+	}
+}*/