diff -Npur pidgin-2.6.6-old//libpurple/conversation.h pidgin-2.6.6-new//libpurple/conversation.h
--- pidgin-2.6.6-old//libpurple/conversation.h	2010-02-16 17:34:06.000000000 +0800
+++ pidgin-2.6.6-new//libpurple/conversation.h	2014-05-08 13:34:32.000000000 +0800
@@ -139,8 +139,8 @@ typedef enum
 	PURPLE_CBFLAGS_HALFOP        = 0x0002, /**< Half-op                      */
 	PURPLE_CBFLAGS_OP            = 0x0004, /**< Channel Op or Moderator      */
 	PURPLE_CBFLAGS_FOUNDER       = 0x0008, /**< Channel Founder              */
-	PURPLE_CBFLAGS_TYPING        = 0x0010  /**< Currently typing             */
-
+	PURPLE_CBFLAGS_TYPING        = 0x0010, /**< Currently typing             */
+        PURPLE_CBFLAGS_AWAY          = 0x0020  /**< Currently away. @since 2.8.0 */
 } PurpleConvChatBuddyFlags;
 
 #include "account.h"
diff -Npur pidgin-2.6.6-old//libpurple/protocols/irc/cmds.c pidgin-2.6.6-new//libpurple/protocols/irc/cmds.c
--- pidgin-2.6.6-old//libpurple/protocols/irc/cmds.c	2010-02-16 17:34:06.000000000 +0800
+++ pidgin-2.6.6-new//libpurple/protocols/irc/cmds.c	2014-05-08 13:34:32.000000000 +0800
@@ -73,7 +73,7 @@ int irc_cmd_ctcp(struct irc_conn *irc, c
 	/* we have defined args as args[0] is target and args[1] is ctcp command */
 	char *buf;
 	GString *string;
-	
+
 	/* check if we have args */
 	if (!args || !args[0] || !args[1])
 		return 0;
@@ -89,7 +89,7 @@ int irc_cmd_ctcp(struct irc_conn *irc, c
 
 	irc_send(irc, buf);
 	g_free(buf);
-	
+
 	return 1;
 }
 
@@ -238,16 +238,16 @@ int irc_cmd_mode(struct irc_conn *irc, c
 		if (!args[0] && irc_ischannel(target))
 			buf = irc_format(irc, "vc", "MODE", target);
 		else if (args[0] && (*args[0] == '+' || *args[0] == '-'))
-			buf = irc_format(irc, "vcv", "MODE", target, args[0]);
+			buf = irc_format(irc, "vcn", "MODE", target, args[0]);
 		else if (args[0])
-			buf = irc_format(irc, "vv", "MODE", args[0]);
+			buf = irc_format(irc, "vn", "MODE", args[0]);
 		else
 			return 0;
 	} else if (!strcmp(cmd, "umode")) {
 		if (!args[0])
 			return 0;
 		gc = purple_account_get_connection(irc->account);
-		buf = irc_format(irc, "vnv", "MODE", purple_connection_get_display_name(gc), args[0]);
+		buf = irc_format(irc, "vnc", "MODE", purple_connection_get_display_name(gc), args[0]);
 	} else {
 		return 0;
 	}
@@ -437,7 +437,7 @@ int irc_cmd_quote(struct irc_conn *irc,
 	if (!args || !args[0])
 		return 0;
 
-	buf = irc_format(irc, "v", args[0]);
+	buf = irc_format(irc, "n", args[0]);
 	irc_send(irc, buf);
 	g_free(buf);
 
@@ -600,7 +600,7 @@ int irc_cmd_whowas(struct irc_conn *irc,
 		return 0;
 
 	buf = irc_format(irc, "vn", "WHOWAS", args[0]);
-	
+
 	irc->whois.nick = g_strdup(args[0]);
 	irc_send(irc, buf);
 	g_free(buf);
diff -Npur pidgin-2.6.6-old//libpurple/protocols/irc/irc.c pidgin-2.6.6-new//libpurple/protocols/irc/irc.c
--- pidgin-2.6.6-old//libpurple/protocols/irc/irc.c	2010-02-16 17:34:06.000000000 +0800
+++ pidgin-2.6.6-new//libpurple/protocols/irc/irc.c	2014-05-08 13:34:32.000000000 +0800
@@ -4,7 +4,7 @@
  * purple
  *
  * Copyright (C) 2003, Robbert Haarman <purple@inglorion.net>
- * Copyright (C) 2003, Ethan Blanton <eblanton@cs.purdue.edu>
+ * Copyright (C) 2003, 2012 Ethan Blanton <elb@pidgin.im>
  * Copyright (C) 2000-2003, Rob Flynn <rob@tgflinux.com>
  * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
  *
@@ -39,7 +39,7 @@
 
 #define PING_TIMEOUT 60
 
-static void irc_buddy_append(char *name, struct irc_buddy *ib, GString *string);
+static void irc_ison_buddy_init(char *name, struct irc_buddy *ib, GList **list);
 
 static const char *irc_blist_icon(PurpleAccount *a, PurpleBuddy *b);
 static GList *irc_status_types(PurpleAccount *account);
@@ -99,7 +99,11 @@ static int do_send(struct irc_conn *irc,
 static int irc_send_raw(PurpleConnection *gc, const char *buf, int len)
 {
 	struct irc_conn *irc = (struct irc_conn*)gc->proto_data;
-	return do_send(irc, buf, len);
+	if (len == -1) {
+		len = strlen(buf);
+	}
+	irc_send_len(irc, buf, len);
+	return len;
 }
 
 static void
@@ -142,16 +146,19 @@ irc_send_cb(gpointer data, gint source,
 
 int irc_send(struct irc_conn *irc, const char *buf)
 {
-	int ret, buflen;
+    return irc_send_len(irc, buf, strlen(buf));
+}
+
+int irc_send_len(struct irc_conn *irc, const char *buf, int buflen)
+{
+	int ret;
  	char *tosend= g_strdup(buf);
 
 	purple_signal_emit(_irc_plugin, "irc-sending-text", purple_account_get_connection(irc->account), &tosend);
+	
 	if (tosend == NULL)
 		return 0;
 
-	buflen = strlen(tosend);
-
-
 	/* If we're not buffering writes, try to send immediately */
 	if (!irc->writeh)
 		ret = do_send(irc, tosend, buflen);
@@ -186,43 +193,63 @@ int irc_send(struct irc_conn *irc, const
 /* XXX I don't like messing directly with these buddies */
 gboolean irc_blist_timeout(struct irc_conn *irc)
 {
-	GString *string;
-	char *list, *buf;
-
-	if (irc->ison_outstanding)
+	if (irc->ison_outstanding) {
 		return TRUE;
+	}
 
-	string = g_string_sized_new(512);
+	g_hash_table_foreach(irc->buddies, (GHFunc)irc_ison_buddy_init,
+	                     (gpointer *)&irc->buddies_outstanding);
 
-	g_hash_table_foreach(irc->buddies, (GHFunc)irc_buddy_append, (gpointer)string);
+	irc_buddy_query(irc);
 
-	list = g_string_free(string, FALSE);
-	if (!list || !strlen(list)) {
-		g_free(list);
-		return TRUE;
-	}
+	return TRUE;
+}
 
-	buf = irc_format(irc, "vn", "ISON", list);
-	g_free(list);
-	irc_send(irc, buf);
-	g_free(buf);
+void irc_buddy_query(struct irc_conn *irc)
+{
+	GList *lp;
+	GString *string;
+	struct irc_buddy *ib;
+	char *buf;
+
+	string = g_string_sized_new(512);
 
-	irc->ison_outstanding = TRUE;
+	while ((lp = g_list_first(irc->buddies_outstanding))) {
+		ib = (struct irc_buddy *)lp->data;
+		if (string->len + strlen(ib->name) + 1 > 450)
+			break;
+		g_string_append_printf(string, "%s ", ib->name);
+		ib->new_online_status = FALSE;
+		irc->buddies_outstanding = g_list_remove_link(irc->buddies_outstanding, lp);
+	}
 
-	return TRUE;
+	if (string->len) {
+		buf = irc_format(irc, "vn", "ISON", string->str);
+		irc_send(irc, buf);
+		g_free(buf);
+		irc->ison_outstanding = TRUE;
+	} else
+		irc->ison_outstanding = FALSE;
+
+	g_string_free(string, TRUE);
 }
 
-static void irc_buddy_append(char *name, struct irc_buddy *ib, GString *string)
+static void irc_ison_buddy_init(char *name, struct irc_buddy *ib, GList **list)
 {
-	ib->flag = FALSE;
-	g_string_append_printf(string, "%s ", name);
+	*list = g_list_append(*list, ib);
 }
 
+
 static void irc_ison_one(struct irc_conn *irc, struct irc_buddy *ib)
 {
 	char *buf;
 
-	ib->flag = FALSE;
+	if (irc->buddies_outstanding != NULL) {
+		irc->buddies_outstanding = g_list_append(irc->buddies_outstanding, ib);
+		return;
+	}
+
+	ib->new_online_status = FALSE;
 	buf = irc_format(irc, "vn", "ISON", ib->name);
 	irc_send(irc, buf);
 	g_free(buf);
@@ -362,14 +389,21 @@ static void irc_login(PurpleAccount *acc
 
 static gboolean do_login(PurpleConnection *gc) {
 	char *buf, *tmp = NULL;
-	char *hostname, *server;
-	const char *hosttmp;
-	const char *username, *realname;
+	char *server;
+	const char *nickname, *identname, *realname;
 	struct irc_conn *irc = gc->proto_data;
 	const char *pass = purple_connection_get_password(gc);
+#ifdef HAVE_CYRUS_SASL
+	const gboolean use_sasl = purple_account_get_bool(irc->account, "sasl", FALSE);
+#endif
 
 	if (pass && *pass) {
-		buf = irc_format(irc, "vv", "PASS", pass);
+#ifdef HAVE_CYRUS_SASL
+		if (use_sasl)
+			buf = irc_format(irc, "vv:", "CAP", "REQ", "sasl");
+		else /* intended to fall through */
+#endif
+			buf = irc_format(irc, "v:", "PASS", pass);
 		if (irc_send(irc, buf) < 0) {
 			g_free(buf);
 			return FALSE;
@@ -378,30 +412,19 @@ static gboolean do_login(PurpleConnectio
 	}
 
 	realname = purple_account_get_string(irc->account, "realname", "");
-	username = purple_account_get_string(irc->account, "username", "");
+	identname = purple_account_get_string(irc->account, "username", "");
 
-	if (username == NULL || *username == '\0') {
-		username = g_get_user_name();
+	if (identname == NULL || *identname == '\0') {
+		identname = g_get_user_name();
 	}
 
-	if (username != NULL && strchr(username, ' ') != NULL) {
-		tmp = g_strdup(username);
+	if (identname != NULL && strchr(identname, ' ') != NULL) {
+		tmp = g_strdup(identname);
 		while ((buf = strchr(tmp, ' ')) != NULL) {
 			*buf = '_';
 		}
 	}
 
-	hosttmp = purple_get_host_name();
-	if (*hosttmp == ':') {
-		/* This is either an IPv6 address, or something which
-		 * doesn't belong here.  Either way, we need to escape
-		 * it. */
-		hostname = g_strdup_printf("0%s", hosttmp);
-	} else {
-		/* Ugly, I know. */
-		hostname = g_strdup(hosttmp);
-	}
-
 	if (*irc->server == ':') {
 		/* Same as hostname, above. */
 		server = g_strdup_printf("0%s", irc->server);
@@ -409,19 +432,18 @@ static gboolean do_login(PurpleConnectio
 		server = g_strdup(irc->server);
 	}
 
-	buf = irc_format(irc, "vvvv:", "USER", tmp ? tmp : username, hostname, server,
+	buf = irc_format(irc, "vvvv:", "USER", tmp ? tmp : identname, "*", server,
 	                 strlen(realname) ? realname : IRC_DEFAULT_ALIAS);
 	g_free(tmp);
-	g_free(hostname);
 	g_free(server);
 	if (irc_send(irc, buf) < 0) {
 		g_free(buf);
 		return FALSE;
 	}
 	g_free(buf);
-	username = purple_connection_get_display_name(gc);
-	buf = irc_format(irc, "vn", "NICK", username);
-	irc->reqnick = g_strdup(username);
+	nickname = purple_connection_get_display_name(gc);
+	buf = irc_format(irc, "vn", "NICK", nickname);
+	irc->reqnick = g_strdup(nickname);
 	irc->nickused = FALSE;
 	if (irc_send(irc, buf) < 0) {
 		g_free(buf);
@@ -513,6 +535,17 @@ static void irc_close(PurpleConnection *
 	g_free(irc->mode_chars);
 	g_free(irc->reqnick);
 
+#ifdef HAVE_CYRUS_SASL
+	if (irc->sasl_conn) {
+		sasl_dispose(&irc->sasl_conn);
+		irc->sasl_conn = NULL;
+	}
+	g_free(irc->sasl_cb);
+	if(irc->sasl_mechs)
+		g_string_free(irc->sasl_mechs, TRUE);
+#endif
+
+
 	g_free(irc);
 }
 
@@ -944,7 +977,12 @@ static PurplePluginProtocolInfo prpl_inf
 	sizeof(PurplePluginProtocolInfo),    /* struct_size */
 	NULL,                    /* get_account_text_table */
 	NULL,                    /* initiate_media */
-	NULL					 /* can_do_media */
+	NULL,					 /* get_media_caps */
+	NULL,					 /* get_moods */
+	NULL,					 /* set_public_alias */
+	NULL,					 /* get_public_alias */
+	NULL,					 /* add_buddy_with_invite */
+	NULL					 /* add_buddies_with_invite */
 };
 
 static gboolean load_plugin (PurplePlugin *plugin) {
@@ -1013,7 +1051,7 @@ static void _init_plugin(PurplePlugin *p
 	option = purple_account_option_bool_new(_("Auto-detect incoming UTF-8"), "autodetect_utf8", IRC_DEFAULT_AUTODETECT);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 
-	option = purple_account_option_string_new(_("Username"), "username", "");
+	option = purple_account_option_string_new(_("Ident name"), "username", "");
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 
 	option = purple_account_option_string_new(_("Real name"), "realname", "");
@@ -1027,6 +1065,16 @@ static void _init_plugin(PurplePlugin *p
 	option = purple_account_option_bool_new(_("Use SSL"), "ssl", FALSE);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 
+#ifdef HAVE_CYRUS_SASL
+	option = purple_account_option_bool_new(_("Authenticate with SASL"), "sasl", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	option = purple_account_option_bool_new(
+						_("Allow plaintext SASL auth over unencrypted connection"),
+						"auth_plain_in_clear", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+#endif
+
 	_irc_plugin = plugin;
 
 	purple_prefs_remove("/plugins/prpl/irc/quitmsg");
diff -Npur pidgin-2.6.6-old//libpurple/protocols/irc/irc.h pidgin-2.6.6-new//libpurple/protocols/irc/irc.h
--- pidgin-2.6.6-old//libpurple/protocols/irc/irc.h	2010-02-16 17:34:06.000000000 +0800
+++ pidgin-2.6.6-new//libpurple/protocols/irc/irc.h	2014-05-08 13:34:32.000000000 +0800
@@ -3,7 +3,7 @@
  *
  * purple
  *
- * Copyright (C) 2003, Ethan Blanton <eblanton@cs.purdue.edu>
+ * Copyright (C) 2003, 2012 Ethan Blanton <elb@pidgin.im>
  *
  * 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
@@ -25,6 +25,10 @@
 
 #include <glib.h>
 
+#ifdef HAVE_CYRUS_SASL
+#include <sasl/sasl.h>
+#endif
+
 #include "circbuffer.h"
 #include "ft.h"
 #include "roomlist.h"
@@ -58,6 +62,7 @@ struct irc_conn {
 	GHashTable *buddies;
 
 	gboolean ison_outstanding;
+	GList *buddies_outstanding;
 
 	char *inbuf;
 	int inbuflen;
@@ -67,9 +72,11 @@ struct irc_conn {
 	GString *names;
 	struct _whois {
 		char *nick;
+		char *real;
+		char *login;
+		char *ident;
+		char *host;
 		char *away;
-		char *userhost;
-		char *name;
 		char *server;
 		char *serverinfo;
 		GString *channels;
@@ -91,19 +98,30 @@ struct irc_conn {
 	char *mode_chars;
 	char *reqnick;
 	gboolean nickused;
+#ifdef HAVE_CYRUS_SASL
+	sasl_conn_t *sasl_conn;
+	const char *current_mech;
+	GString *sasl_mechs;
+	gboolean mech_works;
+	sasl_callback_t *sasl_cb;
+#endif
 };
 
 struct irc_buddy {
 	char *name;
 	gboolean online;
 	gboolean flag;
+ 	gboolean new_online_status;
 	int ref;
 };
 
 typedef int (*IRCCmdCallback) (struct irc_conn *irc, const char *cmd, const char *target, const char **args);
 
 int irc_send(struct irc_conn *irc, const char *buf);
+int irc_send_len(struct irc_conn *irc, const char *buf, int len);
 gboolean irc_blist_timeout(struct irc_conn *irc);
+gboolean irc_who_channel_timeout(struct irc_conn *irc);
+void irc_buddy_query(struct irc_conn *irc);
 
 char *irc_escape_privmsg(const char *text, gssize length);
 
@@ -157,10 +175,19 @@ void irc_msg_quit(struct irc_conn *irc,
 void irc_msg_regonly(struct irc_conn *irc, const char *name, const char *from, char **args);
 void irc_msg_time(struct irc_conn *irc, const char *name, const char *from, char **args);
 void irc_msg_topic(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_topicinfo(struct irc_conn *irc, const char *name, const char *from, char **args);
 void irc_msg_unavailable(struct irc_conn *irc, const char *name, const char *from, char **args);
 void irc_msg_unknown(struct irc_conn *irc, const char *name, const char *from, char **args);
 void irc_msg_wallops(struct irc_conn *irc, const char *name, const char *from, char **args);
 void irc_msg_whois(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_who(struct irc_conn *irc, const char *name, const char *from, char **args);
+#ifdef HAVE_CYRUS_SASL
+void irc_msg_cap(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_auth(struct irc_conn *irc, char *arg);
+void irc_msg_authok(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_authtryagain(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_authfail(struct irc_conn *irc, const char *name, const char *from, char **args);
+#endif
 
 void irc_msg_ignore(struct irc_conn *irc, const char *name, const char *from, char **args);
 
diff -Npur pidgin-2.6.6-old//libpurple/protocols/irc/Makefile.am pidgin-2.6.6-new//libpurple/protocols/irc/Makefile.am
--- pidgin-2.6.6-old//libpurple/protocols/irc/Makefile.am	2010-02-16 17:34:06.000000000 +0800
+++ pidgin-2.6.6-new//libpurple/protocols/irc/Makefile.am	2014-05-08 13:35:44.000000000 +0800
@@ -27,7 +27,7 @@ else
 st =
 pkg_LTLIBRARIES   = libirc.la
 libirc_la_SOURCES = $(IRCSOURCES)
-libirc_la_LIBADD  = $(GLIB_LIBS)
+libirc_la_LIBADD  = $(GLIB_LIBS) $(SASL_LIBS)
 
 endif
 
diff -Npur pidgin-2.6.6-old//libpurple/protocols/irc/msgs.c pidgin-2.6.6-new//libpurple/protocols/irc/msgs.c
--- pidgin-2.6.6-old//libpurple/protocols/irc/msgs.c	2010-02-16 17:34:06.000000000 +0800
+++ pidgin-2.6.6-new//libpurple/protocols/irc/msgs.c	2014-05-08 13:34:32.000000000 +0800
@@ -3,7 +3,7 @@
  *
  * purple
  *
- * Copyright (C) 2003, Ethan Blanton <eblanton@cs.purdue.edu>
+ * Copyright (C) 2003, 2012 Ethan Blanton <elb@pidgin.im>
  *
  * 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
@@ -20,6 +20,12 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
+/*
+ * Note: If you change any of these functions to use additional args you
+ * MUST ensure the arg count is correct in parse.c. Otherwise it may be
+ * possible for a malicious server or man-in-the-middle to trigger a crash.
+ */
+
 #include "internal.h"
 
 #include "conversation.h"
@@ -32,6 +38,10 @@
 #include <stdio.h>
 #include <stdlib.h>
 
+#ifdef HAVE_CYRUS_SASL
+#include <sasl/sasl.h>
+#endif
+
 static char *irc_mask_nick(const char *mask);
 static char *irc_mask_userhost(const char *mask);
 static void irc_chat_remove_buddy(PurpleConversation *convo, char *data[2]);
@@ -42,6 +52,10 @@ static void irc_msg_handle_privmsg(struc
                                    const char *from, const char *to,
                                    const char *rawmsg, gboolean notice);
 
+#ifdef HAVE_CYRUS_SASL
+static void irc_sasl_finish(struct irc_conn *irc);
+#endif
+
 static char *irc_mask_nick(const char *mask)
 {
 	char *end, *buf;
@@ -91,7 +105,7 @@ static void irc_connected(struct irc_con
 
 	/* If we're away then set our away message */
 	status = purple_account_get_active_status(irc->account);
-	if (!purple_status_get_type(status) != PURPLE_STATUS_AVAILABLE) {
+	if (purple_status_get_type(status) != PURPLE_STATUS_AVAILABLE) {
 		PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
 		prpl_info->set_status(irc->account, status);
 	}
@@ -112,9 +126,78 @@ static void irc_connected(struct irc_con
 		irc->timer = purple_timeout_add_seconds(45, (GSourceFunc)irc_blist_timeout, (gpointer)irc);
 }
 
+/* This function is ugly, but it's really an error handler. */
 void irc_msg_default(struct irc_conn *irc, const char *name, const char *from, char **args)
 {
-	char *clean;
+	int i;
+	const char *end, *cur, *numeric = NULL;
+	char *clean, *tmp, *convname;
+	PurpleConversation *convo;
+
+	for (cur = args[0], i = 0; i < 4; i++) {
+		end = strchr(cur, ' ');
+		if (end == NULL) {
+			goto undirected;
+		}
+		/* Check for 3-digit numeric in second position */
+		if (i == 1) {
+			if (end - cur != 3
+			    || !isdigit(cur[0]) || !isdigit(cur[1])
+			    || !isdigit(cur[2])) {
+				goto undirected;
+			}
+			/* Save the numeric for printing to the channel */
+			numeric = cur;
+		}
+		/* Don't advance cur if we're on the final iteration. */
+		if (i != 3) {
+			cur = end + 1;
+		}
+	}
+
+	/* At this point, cur is the beginning of the fourth position,
+	 * end is the following space, and there are remaining
+	 * arguments.  We'll check to see if this argument is a
+	 * currently active conversation (private message or channel,
+	 * either one), and print the numeric to that conversation if it
+	 * is. */
+
+	tmp = g_strndup(cur, end - cur);
+	convname = purple_utf8_salvage(tmp);
+	g_free(tmp);
+
+	/* Check for an existing conversation */
+	convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY,
+						      convname,
+						      irc->account);
+	g_free(convname);
+
+	if (convo == NULL) {
+		goto undirected;
+	}
+
+	/* end + 1 is the first argument past the target.  The initial
+	 * arguments we've skipped are routing info, numeric, recipient
+	 * (this account's nick, most likely), and target (this
+	 * channel).  If end + 1 is an ASCII :, skip it, because it's
+	 * meaningless in this context.  This won't catch all
+	 * :-arguments, but it'll catch the easy case. */
+	if (*++end == ':') {
+		end++;
+	}
+
+	/* We then print "numeric: remainder". */
+	clean = purple_utf8_salvage(end);
+	tmp = g_strdup_printf("%.3s: %s", numeric, clean);
+	g_free(clean);
+	purple_conversation_write(convo, "", tmp,
+				  PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NO_LOG
+				  |PURPLE_MESSAGE_RAW|PURPLE_MESSAGE_NO_LINKIFY,
+				  time(NULL));
+	g_free(tmp);
+	return;
+
+  undirected:
 	/* This, too, should be escaped somehow (smarter) */
 	clean = purple_utf8_salvage(args[0]);
 	purple_debug(PURPLE_DEBUG_INFO, "irc", "Unrecognized message: %s\n", clean);
@@ -126,9 +209,6 @@ void irc_msg_features(struct irc_conn *i
 	gchar **features;
 	int i;
 
-	if (!args || !args[0] || !args[1])
-		return;
-
 	features = g_strsplit(args[1], " ", -1);
 	for (i = 0; features[i]; i++) {
 		char *val;
@@ -143,9 +223,6 @@ void irc_msg_features(struct irc_conn *i
 
 void irc_msg_luser(struct irc_conn *irc, const char *name, const char *from, char **args)
 {
-	if (!args || !args[0])
-		return;
-
 	if (!strcmp(name, "251")) {
 		/* 251 is required, so we pluck our nick from here and
 		 * finalize connection */
@@ -161,9 +238,6 @@ void irc_msg_away(struct irc_conn *irc,
 	PurpleConnection *gc;
 	char *msg;
 
-	if (!args || !args[1])
-		return;
-
 	if (irc->whois.nick && !purple_utf8_strcasecmp(irc->whois.nick, args[1])) {
 		/* We're doing a whois, show this in the whois dialog */
 		irc_msg_whois(irc, name, from, args);
@@ -182,8 +256,7 @@ void irc_msg_badmode(struct irc_conn *ir
 {
 	PurpleConnection *gc = purple_account_get_connection(irc->account);
 
-	if (!args || !args[1] || !gc)
-		return;
+	g_return_if_fail(gc);
 
 	purple_notify_error(gc, NULL, _("Bad mode"), args[1]);
 }
@@ -192,17 +265,12 @@ void irc_msg_ban(struct irc_conn *irc, c
 {
 	PurpleConversation *convo;
 
-	if (!args || !args[0] || !args[1])
-		return;
-
 	convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
 						      args[1], irc->account);
 
 	if (!strcmp(name, "367")) {
 		char *msg = NULL;
 		/* Ban list entry */
-		if (!args[2])
-			return;
 		if (args[3] && args[4]) {
 			/* This is an extended syntax, not in RFC 1459 */
 			int t1 = atoi(args[4]);
@@ -238,8 +306,7 @@ void irc_msg_banned(struct irc_conn *irc
 	PurpleConnection *gc = purple_account_get_connection(irc->account);
 	char *buf;
 
-	if (!args || !args[1] || !gc)
-		return;
+	g_return_if_fail(gc);
 
 	buf = g_strdup_printf(_("You are banned from %s."), args[1]);
 	purple_notify_error(gc, _("Banned"), _("Banned"), buf);
@@ -251,9 +318,6 @@ void irc_msg_banfull(struct irc_conn *ir
 	PurpleConversation *convo;
 	char *buf, *nick;
 
-	if (!args || !args[0] || !args[1] || !args[2])
-		return;
-
 	convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, args[1], irc->account);
 	if (!convo)
 		return;
@@ -272,9 +336,6 @@ void irc_msg_chanmode(struct irc_conn *i
 	PurpleConversation *convo;
 	char *buf, *escaped;
 
-	if (!args || !args[1] || !args[2])
-		return;
-
 	convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, args[1], irc->account);
 	if (!convo)	/* XXX punt on channels we are not in for now */
 		return;
@@ -305,8 +366,9 @@ void irc_msg_whois(struct irc_conn *irc,
 	if (!strcmp(name, "301")) {
 		irc->whois.away = g_strdup(args[2]);
 	} else if (!strcmp(name, "311") || !strcmp(name, "314")) {
-		irc->whois.userhost = g_strdup_printf("%s@%s", args[2], args[3]);
-		irc->whois.name = g_strdup(args[5]);
+		irc->whois.ident = g_strdup(args[2]);
+		irc->whois.host = g_strdup(args[3]);
+		irc->whois.real = g_strdup(args[5]);
 	} else if (!strcmp(name, "312")) {
 		irc->whois.server = g_strdup(args[2]);
 		irc->whois.serverinfo = g_strdup(args[3]);
@@ -324,6 +386,11 @@ void irc_msg_whois(struct irc_conn *irc,
 		}
 	} else if (!strcmp(name, "320")) {
 		irc->whois.identified = 1;
+	} else if (!strcmp(name, "330")) {
+		purple_debug(PURPLE_DEBUG_INFO, "irc", "330 %s: 1=[%s] 2=[%s] 3=[%s]",
+				name, args[1], args[2], args[3]);
+		if (!strcmp(args[3], "is logged in as"))
+			irc->whois.login = g_strdup(args[2]);
 	}
 }
 
@@ -334,12 +401,12 @@ void irc_msg_endwhois(struct irc_conn *i
 	PurpleNotifyUserInfo *user_info;
 
 	if (!irc->whois.nick) {
-		purple_debug(PURPLE_DEBUG_WARNING, "irc", "Unexpected End of %s for %s\n", !strcmp(name, "369") ? "WHOWAS" : "WHOIS" 
+		purple_debug(PURPLE_DEBUG_WARNING, "irc", "Unexpected End of %s for %s\n", !strcmp(name, "369") ? "WHOWAS" : "WHOIS"
 											     , args[1]);
 		return;
 	}
 	if (purple_utf8_strcasecmp(irc->whois.nick, args[1])) {
-		purple_debug(PURPLE_DEBUG_WARNING, "irc", "Received end of %s for %s, expecting %s\n", !strcmp(name, "369") ? "WHOWAS" : "WHOIS" 
+		purple_debug(PURPLE_DEBUG_WARNING, "irc", "Received end of %s for %s, expecting %s\n", !strcmp(name, "369") ? "WHOWAS" : "WHOIS"
 													 , args[1], irc->whois.nick);
 		return;
 	}
@@ -360,12 +427,28 @@ void irc_msg_endwhois(struct irc_conn *i
 		purple_notify_user_info_add_pair(user_info, _("Away"), tmp);
 		g_free(tmp);
 	}
-	if (irc->whois.userhost) {
-		tmp = g_markup_escape_text(irc->whois.name, strlen(irc->whois.name));
-		g_free(irc->whois.name);
-		purple_notify_user_info_add_pair(user_info, _("Username"), irc->whois.userhost);
+	if (irc->whois.real) {
+		tmp = g_markup_escape_text(irc->whois.real, strlen(irc->whois.real));
+		g_free(irc->whois.real);
 		purple_notify_user_info_add_pair(user_info, _("Real name"), tmp);
-		g_free(irc->whois.userhost);
+		g_free(tmp);
+	}
+	if (irc->whois.login) {
+		tmp = g_markup_escape_text(irc->whois.login, strlen(irc->whois.login));
+		g_free(irc->whois.login);
+		purple_notify_user_info_add_pair(user_info, _("Login name"), tmp);
+		g_free(tmp);
+	}
+	if (irc->whois.ident) {
+		tmp = g_markup_escape_text(irc->whois.ident, strlen(irc->whois.ident));
+		g_free(irc->whois.ident);
+		purple_notify_user_info_add_pair(user_info, _("Ident name"), tmp);
+		g_free(tmp);
+	}
+	if (irc->whois.host) {
+		tmp = g_markup_escape_text(irc->whois.host, strlen(irc->whois.host));
+		g_free(irc->whois.host);
+		purple_notify_user_info_add_pair(user_info, _("Host name"), tmp);
 		g_free(tmp);
 	}
 	if (irc->whois.server) {
@@ -400,6 +483,56 @@ void irc_msg_endwhois(struct irc_conn *i
 	memset(&irc->whois, 0, sizeof(irc->whois));
 }
 
+void irc_msg_who(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	if (!strcmp(name, "352")) {
+		PurpleConversation *conv;
+		PurpleConvChat *chat;
+		PurpleConvChatBuddy *cb;
+		
+		char *cur, *userhost, *realname;
+		
+		PurpleConvChatBuddyFlags flags;
+		GList *keys = NULL, *values = NULL;
+
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, args[1], irc->account);
+		if (!conv) {
+			purple_debug(PURPLE_DEBUG_ERROR, "irc","Got a WHO response for %s, which doesn't exist\n", args[1]);
+			return;
+		}
+
+		cb = purple_conv_chat_cb_find(PURPLE_CONV_CHAT(conv), args[5]);
+		if (!cb) {
+			purple_debug(PURPLE_DEBUG_ERROR, "irc", "Got a WHO response for %s who isn't a buddy.\n", args[5]);
+			return;
+		}
+
+		chat = PURPLE_CONV_CHAT(conv);
+		
+		userhost = g_strdup_printf("%s@%s", args[2], args[3]);
+
+		/* The final argument is a :-argument, but annoyingly
+		 * contains two "words", the hop count and real name. */
+		for (cur = args[7]; *cur; cur++) {
+			if (*cur == ' ') {
+				cur++;
+				break;
+			}
+		}
+		flags = cb->flags;
+
+		/* FIXME: I'm not sure this is really a good idea, now
+		 * that we no longer do periodic WHO.  It seems to me
+		 * like it's more likely to be confusing than not.
+		 * Comments? */
+		if (args[6][0] == 'G' && !(flags & PURPLE_CBFLAGS_AWAY)) {
+			purple_conv_chat_user_set_flags(chat, cb->name, flags | PURPLE_CBFLAGS_AWAY);
+		} else if(args[6][0] == 'H' && (flags & PURPLE_CBFLAGS_AWAY)) {
+			purple_conv_chat_user_set_flags(chat, cb->name, flags & ~PURPLE_CBFLAGS_AWAY);
+		}
+	}
+}
+
 void irc_msg_list(struct irc_conn *irc, const char *name, const char *from, char **args)
 {
 	if (!irc->roomlist)
@@ -421,9 +554,6 @@ void irc_msg_list(struct irc_conn *irc,
 		PurpleRoomlistRoom *room;
 		char *topic;
 
-		if (!args[0] || !args[1] || !args[2] || !args[3])
-			return;
-
 		if (!purple_roomlist_get_in_progress(irc->roomlist)) {
 			purple_debug_warning("irc", "Buggy server didn't send RPL_LISTSTART.\n");
 			purple_roomlist_set_in_progress(irc->roomlist, TRUE);
@@ -445,13 +575,9 @@ void irc_msg_topic(struct irc_conn *irc,
 	PurpleConversation *convo;
 
 	if (!strcmp(name, "topic")) {
-		if (!args[0] || !args[1])
-			return;
 		chan = args[0];
 		topic = irc_mirc2txt (args[1]);
 	} else {
-		if (!args[0] || !args[1] || !args[2])
-			return;
 		chan = args[1];
 		topic = irc_mirc2txt (args[2]);
 	}
@@ -496,13 +622,41 @@ void irc_msg_topic(struct irc_conn *irc,
 	g_free(topic);
 }
 
+void irc_msg_topicinfo(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	PurpleConversation *convo;
+	struct tm *tm;
+	time_t t;
+	char *msg, *timestamp, *datestamp;
+
+	convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, args[1], irc->account);
+	if (!convo) {
+		purple_debug(PURPLE_DEBUG_ERROR, "irc", "Got topic info for %s, which doesn't exist\n", args[1]);
+		return;
+	}
+
+	t = (time_t)atol(args[3]);
+	if (t == 0) {
+		purple_debug(PURPLE_DEBUG_ERROR, "irc", "Got apparently nonsensical topic timestamp %s\n", args[3]);
+		return;
+	}
+	tm = localtime(&t);
+
+	timestamp = g_strdup(purple_time_format(tm));
+	datestamp = g_strdup(purple_date_format_short(tm));
+	msg = g_strdup_printf(_("Topic for %s set by %s at %s on %s"), args[1], args[2], timestamp, datestamp);
+	purple_conv_chat_write(PURPLE_CONV_CHAT(convo), "", msg, PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LINKIFY, time(NULL));
+	g_free(timestamp);
+	g_free(datestamp);
+	g_free(msg);
+}
+
 void irc_msg_unknown(struct irc_conn *irc, const char *name, const char *from, char **args)
 {
 	PurpleConnection *gc = purple_account_get_connection(irc->account);
 	char *buf;
 
-	if (!args || !args[1] || !gc)
-		return;
+	g_return_if_fail(gc);
 
 	buf = g_strdup_printf(_("Unknown message '%s'"), args[1]);
 	purple_notify_error(gc, _("Unknown message"), buf, _("The IRC server received a message it did not understand."));
@@ -532,7 +686,7 @@ void irc_msg_names(struct irc_conn *irc,
 			else
 				purple_conv_im_write(PURPLE_CONV_IM(convo), "", msg, PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NO_LOG, time(NULL));
 			g_free(msg);
-		} else {
+		} else if (cur != NULL) {
 			GList *users = NULL;
 			GList *flags = NULL;
 
@@ -594,12 +748,6 @@ void irc_msg_motd(struct irc_conn *irc,
 {
 	char *escaped;
 
-	if (!args || !args[0])
-		return;
-
-	if (!irc->motd)
-		irc->motd = g_string_new("");
-
 	if (!strcmp(name, "375")) {
 		if (irc->motd)
 			g_string_free(irc->motd, TRUE);
@@ -610,6 +758,15 @@ void irc_msg_motd(struct irc_conn *irc,
 		 * finalize the connection here if it is not already done. */
 		irc_connected(irc, args[0]);
 		return;
+	} else if (!strcmp(name, "422")) {
+		/* in case there is no 251, and no MOTD set, finalize the connection.
+		 * (and clear the motd for good measure). */
+
+		if (irc->motd)
+			g_string_free(irc->motd, TRUE);
+
+		irc_connected(irc, args[0]);
+		return;
 	}
 
 	if (!irc->motd) {
@@ -627,11 +784,9 @@ void irc_msg_motd(struct irc_conn *irc,
 
 void irc_msg_time(struct irc_conn *irc, const char *name, const char *from, char **args)
 {
-	PurpleConnection *gc;
+	PurpleConnection *gc = purple_account_get_connection(irc->account);
 
-	gc = purple_account_get_connection(irc->account);
-	if (gc == NULL || args == NULL || args[2] == NULL)
-		return;
+	g_return_if_fail(gc);
 
 	purple_notify_message(gc, PURPLE_NOTIFY_MSG_INFO, _("Time Response"),
 			    _("The IRC server's local time is:"),
@@ -642,8 +797,7 @@ void irc_msg_nochan(struct irc_conn *irc
 {
 	PurpleConnection *gc = purple_account_get_connection(irc->account);
 
-	if (gc == NULL || args == NULL || args[1] == NULL)
-		return;
+	g_return_if_fail(gc);
 
 	purple_notify_error(gc, NULL, _("No such channel"), args[1]);
 }
@@ -704,9 +858,6 @@ void irc_msg_notop(struct irc_conn *irc,
 {
 	PurpleConversation *convo;
 
-	if (!args || !args[1] || !args[2])
-		return;
-
 	convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, args[1], irc->account);
 	if (!convo)
 		return;
@@ -720,8 +871,7 @@ void irc_msg_invite(struct irc_conn *irc
 	GHashTable *components;
 	gchar *nick;
 
-	if (!args || !args[1] || !gc)
-		return;
+	g_return_if_fail(gc);
 
 	components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
 	nick = irc_mask_nick(from);
@@ -737,8 +887,7 @@ void irc_msg_inviteonly(struct irc_conn
 	PurpleConnection *gc = purple_account_get_connection(irc->account);
 	char *buf;
 
-	if (!args || !args[1] || !gc)
-		return;
+	g_return_if_fail(gc);
 
 	buf = g_strdup_printf(_("Joining %s requires an invitation."), args[1]);
 	purple_notify_error(gc, _("Invitation only"), _("Invitation only"), buf);
@@ -751,22 +900,20 @@ void irc_msg_ison(struct irc_conn *irc,
 	struct irc_buddy *ib;
 	int i;
 
-	if (!args || !args[1])
-		return;
-
 	nicks = g_strsplit(args[1], " ", -1);
-
 	for (i = 0; nicks[i]; i++) {
 		if ((ib = g_hash_table_lookup(irc->buddies, (gconstpointer)nicks[i])) == NULL) {
 			continue;
 		}
-		ib->flag = TRUE;
+		ib->new_online_status = TRUE;
 	}
-
 	g_strfreev(nicks);
 
-	g_hash_table_foreach(irc->buddies, (GHFunc)irc_buddy_status, (gpointer)irc);
-	irc->ison_outstanding = FALSE;
+	if (irc->ison_outstanding)
+		irc_buddy_query(irc);
+
+	if (!irc->ison_outstanding)
+		g_hash_table_foreach(irc->buddies, (GHFunc)irc_buddy_status, (gpointer)irc);
 }
 
 static void irc_buddy_status(char *name, struct irc_buddy *ib, struct irc_conn *irc)
@@ -777,10 +924,10 @@ static void irc_buddy_status(char *name,
 	if (!gc || !buddy)
 		return;
 
-	if (ib->online && !ib->flag) {
+	if (ib->online && !ib->new_online_status) {
 		purple_prpl_got_user_status(irc->account, name, "offline", NULL);
 		ib->online = FALSE;
-	} else if (!ib->online && ib->flag) {
+	} else if (!ib->online && ib->new_online_status) {
 		purple_prpl_got_user_status(irc->account, name, "available", NULL);
 		ib->online = TRUE;
 	}
@@ -790,14 +937,16 @@ void irc_msg_join(struct irc_conn *irc,
 {
 	PurpleConnection *gc = purple_account_get_connection(irc->account);
 	PurpleConversation *convo;
-	char *nick = irc_mask_nick(from), *userhost;
+	PurpleConvChat *chat;
+	PurpleConvChatBuddy *cb;
+
+	char *nick, *userhost, *buf;
 	struct irc_buddy *ib;
 	static int id = 1;
 
-	if (!gc) {
-		g_free(nick);
-		return;
-	}
+	g_return_if_fail(gc);
+
+	nick = irc_mask_nick(from);
 
 	if (!purple_utf8_strcasecmp(nick, purple_connection_get_display_name(gc))) {
 		/* We are joining a channel for the first time */
@@ -813,6 +962,12 @@ void irc_msg_join(struct irc_conn *irc,
 		}
 		purple_conversation_set_data(convo, IRC_NAMES_FLAG,
 					   GINT_TO_POINTER(FALSE));
+		
+		// Get the real name and user host for all participants.
+		buf = irc_format(irc, "vc", "WHO", args[0]);
+		irc_send(irc, buf);
+		g_free(buf);
+		
 		/* Until purple_conversation_present does something that
 		 * one would expect in Pidgin, this call produces buggy
 		 * behavior both for the /join and auto-join cases. */
@@ -828,10 +983,12 @@ void irc_msg_join(struct irc_conn *irc,
 	}
 
 	userhost = irc_mask_userhost(from);
-	purple_conv_chat_add_user(PURPLE_CONV_CHAT(convo), nick, userhost, PURPLE_CBFLAGS_NONE, TRUE);
-
+	chat = PURPLE_CONV_CHAT(convo);
+	
+	purple_conv_chat_add_user(chat, nick, userhost, PURPLE_CBFLAGS_NONE, TRUE);
+	
 	if ((ib = g_hash_table_lookup(irc->buddies, nick)) != NULL) {
-		ib->flag = TRUE;
+		ib->new_online_status = TRUE;
 		irc_buddy_status(nick, ib, irc);
 	}
 
@@ -843,15 +1000,14 @@ void irc_msg_kick(struct irc_conn *irc,
 {
 	PurpleConnection *gc = purple_account_get_connection(irc->account);
 	PurpleConversation *convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, args[0], irc->account);
-	char *nick = irc_mask_nick(from), *buf;
+	char *nick, *buf;
 
-	if (!gc) {
-		g_free(nick);
-		return;
-	}
+	g_return_if_fail(gc);
+
+	nick = irc_mask_nick(from);
 
 	if (!convo) {
-		purple_debug(PURPLE_DEBUG_ERROR, "irc", "Recieved a KICK for unknown channel %s\n", args[0]);
+		purple_debug(PURPLE_DEBUG_ERROR, "irc", "Received a KICK for unknown channel %s\n", args[0]);
 		g_free(nick);
 		return;
 	}
@@ -991,9 +1147,6 @@ void irc_msg_nickused(struct irc_conn *i
 	char *newnick, *buf, *end;
 	PurpleConnection *gc = purple_account_get_connection(irc->account);
 
-	if (!args || !args[1])
-		return;
-
 	if (gc && purple_connection_get_state(gc) == PURPLE_CONNECTED) {
 		/* We only want to do the following dance if the connection
 		   has not been successfully completed.  If it has, just
@@ -1032,9 +1185,6 @@ void irc_msg_nickused(struct irc_conn *i
 
 void irc_msg_notice(struct irc_conn *irc, const char *name, const char *from, char **args)
 {
-	if (!args || !args[0] || !args[1])
-		return;
-
 	irc_msg_handle_privmsg(irc, name, from, args[0], args[1], TRUE);
 }
 
@@ -1042,8 +1192,7 @@ void irc_msg_nochangenick(struct irc_con
 {
 	PurpleConnection *gc = purple_account_get_connection(irc->account);
 
-	if (!args || !args[2] || !gc)
-		return;
+	g_return_if_fail(gc);
 
 	purple_notify_error(gc, _("Cannot change nick"), _("Could not change nick"), args[2]);
 }
@@ -1054,8 +1203,7 @@ void irc_msg_part(struct irc_conn *irc,
 	PurpleConversation *convo;
 	char *nick, *msg, *channel;
 
-	if (!args || !args[0] || !gc)
-		return;
+	g_return_if_fail(gc);
 
 	/* Undernet likes to :-quote the channel name, for no good reason
 	 * that I can see.  This catches that. */
@@ -1088,8 +1236,6 @@ void irc_msg_part(struct irc_conn *irc,
 void irc_msg_ping(struct irc_conn *irc, const char *name, const char *from, char **args)
 {
 	char *buf;
-	if (!args || !args[0])
-		return;
 
 	buf = irc_format(irc, "v:", "PONG", args[0]);
 	irc_send(irc, buf);
@@ -1103,9 +1249,6 @@ void irc_msg_pong(struct irc_conn *irc,
 	char **parts, *msg;
 	time_t oldstamp;
 
-	if (!args || !args[1])
-		return;
-
 	parts = g_strsplit(args[1], " ", 2);
 
 	if (!parts[0] || !parts[1]) {
@@ -1139,9 +1282,6 @@ void irc_msg_pong(struct irc_conn *irc,
 
 void irc_msg_privmsg(struct irc_conn *irc, const char *name, const char *from, char **args)
 {
-	if (!args || !args[0] || !args[1])
-		return;
-
 	irc_msg_handle_privmsg(irc, name, from, args[0], args[1], FALSE);
 }
 
@@ -1195,8 +1335,7 @@ void irc_msg_regonly(struct irc_conn *ir
 	PurpleConversation *convo;
 	char *msg;
 
-	if (!args || !args[1] || !args[2] || !gc)
-		return;
+	g_return_if_fail(gc);
 
 	convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, args[1], irc->account);
 	if (convo) {
@@ -1218,8 +1357,7 @@ void irc_msg_quit(struct irc_conn *irc,
 	struct irc_buddy *ib;
 	char *data[2];
 
-	if (!args || !args[0] || !gc)
-		return;
+	g_return_if_fail(gc);
 
 	data[0] = irc_mask_nick(from);
 	data[1] = args[0];
@@ -1227,7 +1365,7 @@ void irc_msg_quit(struct irc_conn *irc,
 	g_slist_foreach(gc->buddy_chats, (GFunc)irc_chat_remove_buddy, data);
 
 	if ((ib = g_hash_table_lookup(irc->buddies, data[0])) != NULL) {
-		ib->flag = FALSE;
+		ib->new_online_status = FALSE;
 		irc_buddy_status(data[0], ib, irc);
 	}
 	g_free(data[0]);
@@ -1239,9 +1377,6 @@ void irc_msg_unavailable(struct irc_conn
 {
 	PurpleConnection *gc = purple_account_get_connection(irc->account);
 
-	if (!args || !args[1])
-		return;
-
 	purple_notify_error(gc, NULL, _("Nick or channel is temporarily unavailable."), args[1]);
 }
 
@@ -1250,8 +1385,7 @@ void irc_msg_wallops(struct irc_conn *ir
 	PurpleConnection *gc = purple_account_get_connection(irc->account);
 	char *nick, *msg;
 
-	if (!args || !args[0] || !gc)
-		return;
+	g_return_if_fail(gc);
 
 	nick = irc_mask_nick(from);
 	msg = g_strdup_printf (_("Wallops from %s"), nick);
@@ -1260,6 +1394,382 @@ void irc_msg_wallops(struct irc_conn *ir
 	g_free(msg);
 }
 
+#ifdef HAVE_CYRUS_SASL
+static int
+irc_sasl_cb_secret(sasl_conn_t *conn, void *ctx, int id, sasl_secret_t **secret)
+{
+	struct irc_conn *irc = ctx;
+	sasl_secret_t *sasl_secret;
+	const char *pw;
+	size_t len;
+
+	pw = purple_account_get_password(irc->account);
+
+	if (!conn || !secret || id != SASL_CB_PASS)
+		return SASL_BADPARAM;
+
+	len = strlen(pw);
+	/* Not an off-by-one because sasl_secret_t defines char data[1] */
+	/* TODO: This can probably be moved to glib's allocator */
+	sasl_secret = malloc(sizeof(sasl_secret_t) + len);
+	if (!sasl_secret)
+		return SASL_NOMEM;
+
+	sasl_secret->len = len;
+	strcpy((char*)sasl_secret->data, pw);
+
+	*secret = sasl_secret;
+	return SASL_OK;
+}
+
+static int
+irc_sasl_cb_log(void *context, int level, const char *message)
+{
+	if(level <= SASL_LOG_TRACE)
+		purple_debug_info("sasl", "%s\n", message);
+
+	return SASL_OK;
+}
+
+static int
+irc_sasl_cb_simple(void *ctx, int id, const char **res, unsigned *len)
+{
+	struct irc_conn *irc = ctx;
+	PurpleConnection *gc = purple_account_get_connection(irc->account);
+
+	switch(id) {
+		case SASL_CB_AUTHNAME:
+			*res = purple_connection_get_display_name(gc);
+			break;
+		case SASL_CB_USER:
+			*res = "";
+			break;
+		default:
+			return SASL_BADPARAM;
+	}
+	if (len) *len = strlen((char *)*res);
+	return SASL_OK;
+}
+
+static void
+irc_auth_start_cyrus(struct irc_conn *irc)
+{
+	int ret = 0;
+	char *buf;
+	sasl_security_properties_t secprops;
+	PurpleAccount *account = irc->account;
+	PurpleConnection *gc = purple_account_get_connection(account);
+
+	gboolean plaintext;
+	gboolean again = FALSE;
+
+	/* Set up security properties and options */
+	secprops.min_ssf = 0;
+	secprops.security_flags = SASL_SEC_NOANONYMOUS;
+
+	if (!irc->gsc) {
+		secprops.max_ssf = -1;
+		secprops.maxbufsize = 4096;
+		plaintext = purple_account_get_bool(account, "auth_plain_in_clear", FALSE);
+		if (!plaintext)
+			secprops.security_flags |= SASL_SEC_NOPLAINTEXT;
+	} else {
+		secprops.max_ssf = 0;
+		secprops.maxbufsize = 0;
+		plaintext = TRUE;
+	}
+
+	secprops.property_names = 0;
+	secprops.property_values = 0;
+
+	do {
+		gchar *tmp = NULL;
+		again = FALSE;
+
+		ret = sasl_client_new("irc", irc->server, NULL, NULL, irc->sasl_cb, 0, &irc->sasl_conn);
+
+		if (ret != SASL_OK) {
+			purple_debug_error("irc", "sasl_client_new failed: %d\n", ret);
+			tmp = g_strdup_printf(_("Failed to initialize SASL authentication: %s"),
+				sasl_errdetail(irc->sasl_conn));
+			purple_connection_error_reason (gc,
+				PURPLE_CONNECTION_ERROR_OTHER_ERROR, tmp);
+			g_free(tmp);
+			return;
+		}
+
+		sasl_setprop(irc->sasl_conn, SASL_AUTH_EXTERNAL, irc->account->username);
+		sasl_setprop(irc->sasl_conn, SASL_SEC_PROPS, &secprops);
+
+		ret = sasl_client_start(irc->sasl_conn, irc->sasl_mechs->str, NULL, NULL, NULL, &irc->current_mech);
+
+		switch (ret) {
+			case SASL_OK:
+			case SASL_CONTINUE:
+				irc->mech_works = FALSE;
+				break;
+			case SASL_NOMECH:
+				purple_connection_error_reason (gc,
+					PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
+					_("SASL authentication failed: No worthy authentication mechanisms found."));
+
+				irc_sasl_finish(irc);
+				return;
+			case SASL_BADPARAM:
+			case SASL_NOMEM:
+				tmp = g_strdup_printf(_("SASL authentication failed: %s"), sasl_errdetail(irc->sasl_conn));
+				purple_connection_error_reason (gc,
+					PURPLE_CONNECTION_ERROR_OTHER_ERROR, tmp);
+				g_free(tmp);
+
+				irc_sasl_finish(irc);
+				return;
+			default:
+				purple_debug_error("irc", "sasl_client_start failed: %s\n", sasl_errdetail(irc->sasl_conn));
+
+				if (irc->current_mech && *irc->current_mech) {
+					char *pos;
+					if ((pos = strstr(irc->sasl_mechs->str, irc->current_mech))) {
+						size_t index = pos - irc->sasl_mechs->str;
+						g_string_erase(irc->sasl_mechs, index, strlen(irc->current_mech));
+
+						/* Remove space which separated this mech from the next */
+						if ((irc->sasl_mechs->str)[index] == ' ') {
+							g_string_erase(irc->sasl_mechs, index, 1);
+						}
+					}
+
+					again = TRUE;
+				}
+				irc_sasl_finish(irc);
+		}
+	} while (again);
+
+	purple_debug_info("irc", "Using SASL: %s\n", irc->current_mech);
+
+	buf = irc_format(irc, "vv", "AUTHENTICATE", irc->current_mech);
+	irc_send(irc, buf);
+	g_free(buf);
+}
+
+/* SASL authentication */
+void
+irc_msg_cap(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	int ret = 0;
+	int id = 0;
+	PurpleConnection *gc = purple_account_get_connection(irc->account);
+	const char *mech_list = NULL;
+
+	if (strncmp(args[2], "sasl ", 6))
+		return;
+	if (strncmp(args[1], "ACK", 4)) {
+		const char *tmp = _("SASL authentication failed: Server does not support SASL authentication.");
+		purple_connection_error_reason (gc,
+			PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, tmp);
+
+		irc_sasl_finish(irc);
+		return;
+	}
+
+	if ((ret = sasl_client_init(NULL)) != SASL_OK) {
+		const char *tmp = _("SASL authentication failed: Initializing SASL failed.");
+		purple_connection_error_reason (gc,
+			PURPLE_CONNECTION_ERROR_OTHER_ERROR, tmp);
+		return;
+	}
+
+	irc->sasl_cb = g_new0(sasl_callback_t, 5);
+
+	irc->sasl_cb[id].id = SASL_CB_AUTHNAME;
+	irc->sasl_cb[id].proc = irc_sasl_cb_simple;
+	irc->sasl_cb[id].context = (void *)irc;
+	id++;
+
+	irc->sasl_cb[id].id = SASL_CB_USER;
+	irc->sasl_cb[id].proc = irc_sasl_cb_simple;
+	irc->sasl_cb[id].context = (void *)irc;
+	id++;
+
+	irc->sasl_cb[id].id = SASL_CB_PASS;
+	irc->sasl_cb[id].proc = irc_sasl_cb_secret;
+	irc->sasl_cb[id].context = (void *)irc;
+	id++;
+
+	irc->sasl_cb[id].id = SASL_CB_LOG;
+	irc->sasl_cb[id].proc = irc_sasl_cb_log;
+	irc->sasl_cb[id].context = (void *)irc;
+	id++;
+
+	irc->sasl_cb[id].id = SASL_CB_LIST_END;
+
+	/* We need to do this to be able to list the mechanisms. */
+	ret = sasl_client_new("irc", irc->server, NULL, NULL, irc->sasl_cb, 0, &irc->sasl_conn);
+
+	sasl_listmech(irc->sasl_conn, NULL, "", " ", "", &mech_list, NULL, NULL);
+	purple_debug_info("irc", "SASL: we have available: %s\n", mech_list);
+
+	if (ret != SASL_OK) {
+		gchar *tmp;
+
+		purple_debug_error("irc", "sasl_client_new failed: %d\n", ret);
+		tmp = g_strdup_printf(_("Failed to initialize SASL authentication: %s"),
+			sasl_errdetail(irc->sasl_conn));
+		purple_connection_error_reason (gc,
+			PURPLE_CONNECTION_ERROR_OTHER_ERROR, tmp);
+		g_free(tmp);
+
+		return;
+	}
+
+	irc->sasl_mechs = g_string_new(mech_list);
+
+	irc_auth_start_cyrus(irc);
+}
+
+void
+irc_msg_auth(struct irc_conn *irc, char *arg)
+{
+	PurpleConnection *gc = purple_account_get_connection(irc->account);
+	char *buf, *authinfo;
+	char *serverin = NULL;
+	unsigned serverinlen = 0;
+	const gchar *c_out;
+	unsigned int clen;
+	int ret;
+
+	irc->mech_works = TRUE;
+
+	if (!arg)
+		return;
+
+	if (arg[0] != '+') {
+		serverin = arg;
+		serverinlen = strlen(serverin);
+	}
+
+	ret = sasl_client_step(irc->sasl_conn, serverin, serverinlen,
+		NULL, &c_out, &clen);
+
+	if (ret != SASL_OK && ret != SASL_CONTINUE) {
+
+		gchar *tmp = g_strdup_printf(_("SASL authentication failed: %s"),
+			sasl_errdetail(irc->sasl_conn));
+		purple_connection_error_reason (gc,
+			PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, tmp);
+		g_free(tmp);
+
+		irc_sasl_finish(irc);
+
+		return;
+	}
+
+	if (clen > 0)
+		authinfo = purple_base64_encode((const guchar*)c_out, clen);
+	else
+		authinfo = g_strdup("+");
+
+	buf = irc_format(irc, "vv", "AUTHENTICATE", authinfo);
+	irc_send(irc, buf);
+	g_free(buf);
+	g_free(authinfo);
+}
+
+void
+irc_msg_authok(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	char *buf;
+
+	sasl_dispose(&irc->sasl_conn);
+	irc->sasl_conn = NULL;
+	purple_debug_info("irc", "Succesfully authenticated using SASL.\n");
+
+	/* Finish auth session */
+	buf = irc_format(irc, "vv", "CAP", "END");
+	irc_send(irc, buf);
+	g_free(buf);
+}
+
+void
+irc_msg_authtryagain(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	PurpleConnection *gc = purple_account_get_connection(irc->account);
+
+	/* We already received at least one AUTHENTICATE reply from the
+	 * server. This suggests it supports this mechanism, but the
+	 * password was incorrect. It would be better to abort and inform
+	 * the user than to try again with a different mechanism, so they
+	 * aren't told the server supports no worthy mechanisms.
+	 */
+	if (irc->mech_works) {
+		purple_connection_error_reason (gc,
+			PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Incorrect Password"));
+
+		irc_sasl_finish(irc);
+
+		return;
+	}
+
+	if (irc->current_mech) {
+		char *pos;
+		if ((pos = strstr(irc->sasl_mechs->str, irc->current_mech))) {
+			size_t index = pos - irc->sasl_mechs->str;
+			g_string_erase(irc->sasl_mechs, index, strlen(irc->current_mech));
+
+			/* Remove space which separated this mech from the next */
+			if ((irc->sasl_mechs->str)[index] == ' ') {
+				g_string_erase(irc->sasl_mechs, index, 1);
+			}
+		}
+	}
+	if (*irc->sasl_mechs->str) {
+		sasl_dispose(&irc->sasl_conn);
+
+		purple_debug_info("irc", "Now trying with %s\n", irc->sasl_mechs->str);
+		irc_auth_start_cyrus(irc);
+	} else {
+		purple_connection_error_reason (gc,
+			PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
+			_("SASL authentication failed: No worthy mechanisms found"));
+
+		irc_sasl_finish(irc);
+	}
+}
+
+void
+irc_msg_authfail(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	PurpleConnection *gc = purple_account_get_connection(irc->account);
+
+	/* Only show an error if we did not abort ourselves. */
+	if (irc->sasl_conn) {
+		purple_debug_info("irc", "SASL authentication failed: %s", sasl_errdetail(irc->sasl_conn));
+
+		purple_connection_error_reason (gc,
+			PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Incorrect Password"));
+	}
+
+	irc_sasl_finish(irc);
+}
+
+static void
+irc_sasl_finish(struct irc_conn *irc)
+{
+	char *buf;
+
+	sasl_dispose(&irc->sasl_conn);
+	irc->sasl_conn = NULL;
+
+	g_free(irc->sasl_cb);
+	irc->sasl_cb = NULL;
+
+	/* Auth failed, abort */
+	buf = irc_format(irc, "vv", "CAP", "END");
+	irc_send(irc, buf);
+	g_free(buf);
+}
+#endif
+
 void irc_msg_ignore(struct irc_conn *irc, const char *name, const char *from, char **args)
 {
 	return;
diff -Npur pidgin-2.6.6-old//libpurple/protocols/irc/parse.c pidgin-2.6.6-new//libpurple/protocols/irc/parse.c
--- pidgin-2.6.6-old//libpurple/protocols/irc/parse.c	2010-02-16 17:34:06.000000000 +0800
+++ pidgin-2.6.6-new//libpurple/protocols/irc/parse.c	2014-05-08 13:34:32.000000000 +0800
@@ -50,69 +50,86 @@ extern PurplePlugin *_irc_plugin;
 static struct _irc_msg {
 	char *name;
 	char *format;
+
+	/** The required parameter count, based on values we use, not protocol
+	 *  specification. */
+	int req_cnt;
+
 	void (*cb)(struct irc_conn *irc, const char *name, const char *from, char **args);
 } _irc_msgs[] = {
-	{ "005", "n*", irc_msg_features },	/* Feature list			*/
-	{ "251", "n:", irc_msg_luser },		/* Client & Server count	*/
-	{ "255", "n:", irc_msg_luser },		/* Client & Server count Mk. II	*/
-	{ "301", "nn:", irc_msg_away },		/* User is away			*/
-	{ "303", "n:", irc_msg_ison },		/* ISON reply			*/
-	{ "311", "nnvvv:", irc_msg_whois },	/* Whois user			*/
-	{ "312", "nnv:", irc_msg_whois },	/* Whois server			*/
-	{ "313", "nn:", irc_msg_whois },	/* Whois ircop			*/
-	{ "317", "nnvv", irc_msg_whois },	/* Whois idle			*/
-	{ "318", "nt:", irc_msg_endwhois },	/* End of WHOIS			*/
-	{ "319", "nn:", irc_msg_whois },	/* Whois channels		*/
-	{ "320", "nn:", irc_msg_whois },	/* Whois (fn ident)		*/
-	{ "314", "nnnvv:", irc_msg_whois },	/* Whowas user			*/
-	{ "369", "nt:", irc_msg_endwhois },	/* End of WHOWAS		*/
-	{ "321", "*", irc_msg_list },		/* Start of list		*/
-	{ "322", "ncv:", irc_msg_list },	/* List.			*/
-	{ "323", ":", irc_msg_list },		/* End of list.			*/
-	{ "324", "ncv:", irc_msg_chanmode },	/* Channel modes		*/
-	{ "331", "nc:",	irc_msg_topic },	/* No channel topic		*/
-	{ "332", "nc:", irc_msg_topic },	/* Channel topic		*/
-	{ "333", "*", irc_msg_ignore },		/* Topic setter stuff		*/
-	{ "353", "nvc:", irc_msg_names },	/* Names list			*/
-	{ "366", "nc:", irc_msg_names },	/* End of names			*/
-	{ "367", "ncnnv", irc_msg_ban },	/* Ban list			*/
-	{ "368", "nc:", irc_msg_ban },		/* End of ban list		*/
-	{ "372", "n:", irc_msg_motd },		/* MOTD				*/
-	{ "375", "n:", irc_msg_motd },		/* Start MOTD			*/
-	{ "376", "n:", irc_msg_motd },		/* End of MOTD			*/
-	{ "391", "nv:", irc_msg_time },		/* Time reply			*/
-	{ "401", "nt:", irc_msg_nonick },	/* No such nick/chan		*/
-	{ "406", "nt:", irc_msg_nonick },	/* No such nick for WHOWAS	*/
-	{ "403", "nc:", irc_msg_nochan },	/* No such channel		*/
-	{ "404", "nt:", irc_msg_nosend },	/* Cannot send to chan		*/
-	{ "421", "nv:", irc_msg_unknown },	/* Unknown command		*/
-	{ "432", "vn:", irc_msg_badnick },	/* Erroneous nickname		*/
-	{ "433", "vn:", irc_msg_nickused },	/* Nickname already in use	*/
-	{ "437", "nc:", irc_msg_unavailable },  /* Nick/channel is unavailable */
-	{ "438", "nn:", irc_msg_nochangenick },	/* Nick may not change		*/
-	{ "442", "nc:", irc_msg_notinchan },	/* Not in channel		*/
-	{ "473", "nc:", irc_msg_inviteonly },	/* Tried to join invite-only	*/
-	{ "474", "nc:", irc_msg_banned },	/* Banned from channel		*/
-	{ "477", "nc:", irc_msg_regonly },	/* Registration Required	*/
-	{ "478", "nct:", irc_msg_banfull },	/* Banlist is full		*/
-	{ "482", "nc:", irc_msg_notop },	/* Need to be op to do that	*/
-	{ "501", "n:", irc_msg_badmode },	/* Unknown mode flag		*/
-	{ "506", "nc:", irc_msg_nosend },	/* Must identify to send	*/
-	{ "515", "nc:", irc_msg_regonly },	/* Registration required	*/
-	{ "invite", "n:", irc_msg_invite },	/* Invited			*/
-	{ "join", ":", irc_msg_join },		/* Joined a channel		*/
-	{ "kick", "cn:", irc_msg_kick },	/* KICK				*/
-	{ "mode", "tv:", irc_msg_mode },	/* MODE for channel		*/
-	{ "nick", ":", irc_msg_nick },		/* Nick change			*/
-	{ "notice", "t:", irc_msg_notice },	/* NOTICE recv			*/
-	{ "part", "c:", irc_msg_part },		/* Parted a channel		*/
-	{ "ping", ":", irc_msg_ping },		/* Received PING from server	*/
-	{ "pong", "v:", irc_msg_pong },		/* Received PONG from server	*/
-	{ "privmsg", "t:", irc_msg_privmsg },	/* Received private message	*/
-	{ "topic", "c:", irc_msg_topic },	/* TOPIC command		*/
-	{ "quit", ":", irc_msg_quit },		/* QUIT notice			*/
-	{ "wallops", ":", irc_msg_wallops },	/* WALLOPS command		*/
-	{ NULL, NULL, NULL }
+	{ "005", "n*", 2, irc_msg_features },		/* Feature list			*/
+	{ "251", "n:", 1, irc_msg_luser },		/* Client & Server count	*/
+	{ "255", "n:", 1, irc_msg_luser },		/* Client & Server count Mk. II	*/
+	{ "301", "nn:", 3, irc_msg_away },		/* User is away			*/
+	{ "303", "n:", 2, irc_msg_ison },		/* ISON reply			*/
+	{ "311", "nnvvv:", 6, irc_msg_whois },		/* Whois user			*/
+	{ "312", "nnv:", 4, irc_msg_whois },		/* Whois server			*/
+	{ "313", "nn:", 2, irc_msg_whois },		/* Whois ircop			*/
+	{ "317", "nnvv", 3, irc_msg_whois },		/* Whois idle			*/
+	{ "318", "nt:", 2, irc_msg_endwhois },		/* End of WHOIS			*/
+	{ "319", "nn:", 3, irc_msg_whois },		/* Whois channels		*/
+	{ "320", "nn:", 2, irc_msg_whois },		/* Whois (fn ident)		*/
+	{ "330", "nnv:", 4, irc_msg_whois },		/* Whois (fn login)		*/
+	{ "314", "nnnvv:", 6, irc_msg_whois },		/* Whowas user			*/
+	{ "315", "nt:", 0, irc_msg_who },		/* end of WHO channel		*/
+	{ "369", "nt:", 2, irc_msg_endwhois },		/* End of WHOWAS		*/
+	{ "321", "*", 0, irc_msg_list },		/* Start of list		*/
+	{ "322", "ncv:", 4, irc_msg_list },		/* List.			*/
+	{ "323", ":", 0, irc_msg_list },		/* End of list.			*/
+	{ "324", "ncv:", 3, irc_msg_chanmode },		/* Channel modes		*/
+	{ "331", "nc:", 3, irc_msg_topic },		/* No channel topic		*/
+	{ "332", "nc:", 3, irc_msg_topic },		/* Channel topic		*/
+	{ "333", "ncvv", 4, irc_msg_topicinfo },	/* Topic setter stuff		*/
+	{ "352", "ncvvvnv:", 8, irc_msg_who },		/* Channel WHO			*/
+	{ "353", "nvc:", 4, irc_msg_names },		/* Names list			*/
+	{ "366", "nc:", 2, irc_msg_names },		/* End of names			*/
+	{ "367", "ncnnv", 3, irc_msg_ban },		/* Ban list			*/
+	{ "368", "nc:", 2, irc_msg_ban },		/* End of ban list		*/
+	{ "372", "n:", 1, irc_msg_motd },		/* MOTD				*/
+	{ "375", "n:", 1, irc_msg_motd },		/* Start MOTD			*/
+	{ "376", "n:", 1, irc_msg_motd },		/* End of MOTD			*/
+	{ "391", "nv:", 3, irc_msg_time },		/* Time reply			*/
+	{ "401", "nt:", 2, irc_msg_nonick },		/* No such nick/chan		*/
+	{ "406", "nt:", 2, irc_msg_nonick },		/* No such nick for WHOWAS	*/
+	{ "403", "nc:", 2, irc_msg_nochan },		/* No such channel		*/
+	{ "404", "nt:", 3, irc_msg_nosend },		/* Cannot send to chan		*/
+	{ "421", "nv:", 2, irc_msg_unknown },		/* Unknown command		*/
+	{ "422", "n:", 1, irc_msg_motd },		/* MOTD file missing		*/
+	{ "432", "vn:", 0, irc_msg_badnick },		/* Erroneous nickname		*/
+	{ "433", "vn:", 2, irc_msg_nickused },		/* Nickname already in use	*/
+	{ "437", "nc:", 2, irc_msg_unavailable },	/* Nick/channel is unavailable	*/
+	{ "438", "nn:", 3, irc_msg_nochangenick },	/* Nick may not change		*/
+	{ "442", "nc:", 3, irc_msg_notinchan },		/* Not in channel		*/
+	{ "473", "nc:", 2, irc_msg_inviteonly },	/* Tried to join invite-only	*/
+	{ "474", "nc:", 2, irc_msg_banned },		/* Banned from channel		*/
+	{ "477", "nc:", 3, irc_msg_regonly },		/* Registration Required	*/
+	{ "478", "nct:", 3, irc_msg_banfull },		/* Banlist is full		*/
+	{ "482", "nc:", 3, irc_msg_notop },		/* Need to be op to do that	*/
+	{ "501", "n:", 2, irc_msg_badmode },		/* Unknown mode flag		*/
+	{ "506", "nc:", 3, irc_msg_nosend },		/* Must identify to send	*/
+	{ "515", "nc:", 3, irc_msg_regonly },		/* Registration required	*/
+#ifdef HAVE_CYRUS_SASL
+	{ "903", "*", 0, irc_msg_authok},		/* SASL auth successful		*/
+	{ "904", "*", 0, irc_msg_authtryagain },	/* SASL auth failed, can recover*/
+	{ "905", "*", 0, irc_msg_authfail },		/* SASL auth failed		*/
+	{ "906", "*", 0, irc_msg_authfail },		/* SASL auth failed		*/
+	{ "907", "*", 0, irc_msg_authfail },		/* SASL auth failed		*/
+	{ "cap", "vv:", 3, irc_msg_cap },		/* SASL capable			*/
+#endif
+	{ "invite", "n:", 2, irc_msg_invite },		/* Invited			*/
+	{ "join", ":", 1, irc_msg_join },		/* Joined a channel		*/
+	{ "kick", "cn:", 3, irc_msg_kick },		/* KICK				*/
+	{ "mode", "tv:", 2, irc_msg_mode },		/* MODE for channel		*/
+	{ "nick", ":", 1, irc_msg_nick },		/* Nick change			*/
+	{ "notice", "t:", 2, irc_msg_notice },		/* NOTICE recv			*/
+	{ "part", "c:", 1, irc_msg_part },		/* Parted a channel		*/
+	{ "ping", ":", 1, irc_msg_ping },		/* Received PING from server	*/
+	{ "pong", "v:", 2, irc_msg_pong },		/* Received PONG from server	*/
+	{ "privmsg", "t:", 2, irc_msg_privmsg },	/* Received private message	*/
+	{ "topic", "c:", 2, irc_msg_topic },		/* TOPIC command		*/
+	{ "quit", ":", 1, irc_msg_quit },		/* QUIT notice			*/
+	{ "wallops", ":", 1, irc_msg_wallops },		/* WALLOPS command		*/
+	{ NULL, NULL, 0, NULL }
 };
 
 static struct _irc_user_cmd {
@@ -122,6 +139,7 @@ static struct _irc_user_cmd {
 	char *help;
 } _irc_cmds[] = {
 	{ "action", ":", irc_cmd_ctcp_action, N_("action &lt;action to perform&gt;:  Perform an action.") },
+	{ "authserv", ":", irc_cmd_service, N_("authserv: Send a command to authserv") },
 	{ "away", ":", irc_cmd_away, N_("away [message]:  Set an away message, or use no message to return from being away.") },
 	{ "ctcp", "t:", irc_cmd_ctcp, N_("ctcp <nick> <msg>: sends ctcp msg to nick.") },
 	{ "chanserv", ":", irc_cmd_service, N_("chanserv: Send a command to chanserv") },
@@ -256,6 +274,12 @@ static char *irc_recv_convert(struct irc
 	gboolean autodetect;
 	int i;
 
+	autodetect = purple_account_get_bool(irc->account, "autodetect_utf8", IRC_DEFAULT_AUTODETECT);
+
+	if (autodetect && g_utf8_validate(string, -1, NULL)) {
+		return g_strdup(string);
+	}
+
 	enclist = purple_account_get_string(irc->account, "encoding", IRC_DEFAULT_CHARSET);
 	encodings = g_strsplit(enclist, ",", -1);
 
@@ -264,12 +288,6 @@ static char *irc_recv_convert(struct irc
 		return purple_utf8_salvage(string);
 	}
 
-	autodetect = purple_account_get_bool(irc->account, "autodetect_utf8", IRC_DEFAULT_AUTODETECT);
-
-	if (autodetect && g_utf8_validate(string, -1, NULL)) {
-		return g_strdup(string);
-	}
-
 	for (i = 0; encodings[i] != NULL; i++) {
 		charset = encodings[i];
 		while (*charset == ' ')
@@ -455,6 +473,7 @@ char *irc_mirc2html(const char *string)
 				decoded = g_string_append(decoded, "</U>");
 			if (font)
 				decoded = g_string_append(decoded, "</FONT>");
+			bold = italic = underline = font = FALSE;
 			break;
 		default:
 			purple_debug(PURPLE_DEBUG_ERROR, "irc", "Unexpected mIRC formatting character %d\n", *cur);
@@ -546,14 +565,16 @@ char *irc_parse_ctcp(struct irc_conn *ir
 		return buf;
 	} else if (!strncmp(cur, "PING ", 5)) {
 		if (notice) { /* reply */
-			/* TODO: Should this read in the timestamp as a double? */
-			sscanf(cur, "PING %lu", &timestamp);
 			gc = purple_account_get_connection(irc->account);
 			if (!gc)
 				return NULL;
-			buf = g_strdup_printf(_("Reply time from %s: %lu seconds"), from, time(NULL) - timestamp);
-			purple_notify_info(gc, _("PONG"), _("CTCP PING reply"), buf);
-			g_free(buf);
+			/* TODO: Should this read in the timestamp as a double? */
+			if (sscanf(cur, "PING %lu", &timestamp) == 1) {
+				buf = g_strdup_printf(_("Reply time from %s: %lu seconds"), from, time(NULL) - timestamp);
+				purple_notify_info(gc, _("PONG"), _("CTCP PING reply"), buf);
+				g_free(buf);
+			} else
+				purple_debug(PURPLE_DEBUG_ERROR, "irc", "Unable to parse PING timestamp");
 			return NULL;
 		} else {
 			buf = irc_format(irc, "vt:", "NOTICE", from, msg);
@@ -647,6 +668,8 @@ void irc_parse_msg(struct irc_conn *irc,
 	char *cur, *end, *tmp, *from, *msgname, *fmt, **args, *msg;
 	guint i;
 	PurpleConnection *gc = purple_account_get_connection(irc->account);
+	gboolean fmt_valid;
+	int args_cnt;
 
 	irc->recv_time = time(NULL);
 
@@ -656,7 +679,7 @@ void irc_parse_msg(struct irc_conn *irc,
 	 * instead of a null terminated string.
 	 */
 	purple_signal_emit(_irc_plugin, "irc-receiving-text", gc, &input);
-	
+
 	if (!strncmp(input, "PING ", 5)) {
 		msg = irc_format(irc, "vv", "PONG", input + 5);
 		irc_send(irc, msg);
@@ -673,6 +696,11 @@ void irc_parse_msg(struct irc_conn *irc,
 				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 				_("Disconnected."));
 		return;
+#ifdef HAVE_CYRUS_SASL
+	} else if (!strncmp(input, "AUTHENTICATE ", 13)) {
+		irc_msg_auth(irc, input + 13);
+		return;
+#endif
 	}
 
 	if (input[0] != ':' || (cur = strchr(input, ' ')) == NULL) {
@@ -698,12 +726,21 @@ void irc_parse_msg(struct irc_conn *irc,
 	}
 	g_free(msgname);
 
+	fmt_valid = TRUE;
 	args = g_new0(char *, strlen(msgent->format));
+	args_cnt = 0;
 	for (cur = end, fmt = msgent->format, i = 0; fmt[i] && *cur++; i++) {
 		switch (fmt[i]) {
 		case 'v':
 			if (!(end = strchr(cur, ' '))) end = cur + strlen(cur);
-			args[i] = g_strndup(cur, end - cur);
+			/* This is a string of unknown encoding which we do not
+			 * want to transcode, but it may or may not be valid
+			 * UTF-8, so we'll salvage it.  If a nick/channel/target
+			 * field has inadvertently been marked verbatim, this
+			 * could cause weirdness. */
+			tmp = g_strndup(cur, end - cur);
+			args[i] = purple_utf8_salvage(tmp);
+			g_free(tmp);
 			cur += end - cur;
 			break;
 		case 't':
@@ -721,17 +758,30 @@ void irc_parse_msg(struct irc_conn *irc,
 			cur = cur + strlen(cur);
 			break;
 		case '*':
-			args[i] = g_strdup(cur);
+			/* Ditto 'v' above; we're going to salvage this in case
+			 * it leaks past the IRC prpl */
+			args[i] = purple_utf8_salvage(cur);
 			cur = cur + strlen(cur);
 			break;
 		default:
 			purple_debug(PURPLE_DEBUG_ERROR, "irc", "invalid message format character '%c'\n", fmt[i]);
+			fmt_valid = FALSE;
 			break;
 		}
+		if (fmt_valid)
+			args_cnt = i + 1;
+	}
+	if (G_UNLIKELY(!fmt_valid)) {
+		purple_debug_error("irc", "message format was invalid");
+	} else if (G_LIKELY(args_cnt >= msgent->req_cnt)) {
+		tmp = irc_recv_convert(irc, from);
+		(msgent->cb)(irc, msgent->name, tmp, args);
+		g_free(tmp);
+	} else {
+		purple_debug_error("irc", "args count (%d) doesn't reach "
+			"expected value of %d for the '%s' command",
+			args_cnt, msgent->req_cnt, msgent->name);
 	}
-	tmp = irc_recv_convert(irc, from);
-	(msgent->cb)(irc, msgent->name, tmp, args);
-	g_free(tmp);
 	for (i = 0; i < strlen(msgent->format); i++) {
 		g_free(args[i]);
 	}
