diff -Npur pidgin-2.6.6/libpurple/protocols/jabber/iq.c pidgin-2.6.6-new/libpurple/protocols/jabber/iq.c
--- pidgin-2.6.6/libpurple/protocols/jabber/iq.c	2010-02-16 17:34:06.000000000 +0800
+++ pidgin-2.6.6-new/libpurple/protocols/jabber/iq.c	2014-03-14 15:01:43.466193496 +0800
@@ -46,6 +46,18 @@
 GHashTable *iq_handlers = NULL;
 GHashTable *signal_iq_handlers = NULL;
 
+struct _JabberIqCallbackData {
+	JabberIqCallback *callback;
+	gpointer data;
+	JabberID *to;
+};
+
+void jabber_iq_callbackdata_free(JabberIqCallbackData *jcd)
+{
+	jabber_id_free(jcd->to);
+	g_free(jcd);
+}
+
 JabberIq *jabber_iq_new(JabberStream *js, JabberIqType type)
 {
 	JabberIq *iq;
@@ -95,11 +107,6 @@ JabberIq *jabber_iq_new_query(JabberStre
 	return iq;
 }
 
-typedef struct _JabberCallbackData {
-	JabberIqCallback *callback;
-	gpointer data;
-} JabberCallbackData;
-
 void
 jabber_iq_set_callback(JabberIq *iq, JabberIqCallback *callback, gpointer data)
 {
@@ -122,15 +129,17 @@ void jabber_iq_set_id(JabberIq *iq, cons
 
 void jabber_iq_send(JabberIq *iq)
 {
-	JabberCallbackData *jcd;
+	JabberIqCallbackData *jcd;
 	g_return_if_fail(iq != NULL);
 
 	jabber_send(iq->js, iq->node);
 
 	if(iq->id && iq->callback) {
-		jcd = g_new0(JabberCallbackData, 1);
+		jcd = g_new0(JabberIqCallbackData, 1);
 		jcd->callback = iq->callback;
 		jcd->data = iq->callback_data;
+		jcd->to = jabber_id_new(xmlnode_get_attrib(iq->node, "to"));
+
 		g_hash_table_insert(iq->js->iq_callbacks, g_strdup(iq->id), jcd);
 	}
 
@@ -273,18 +282,30 @@ void jabber_iq_remove_callback_by_id(Jab
 
 void jabber_iq_parse(JabberStream *js, xmlnode *packet)
 {
-	JabberCallbackData *jcd;
+	JabberIqCallbackData *jcd;
 	xmlnode *child, *error, *x;
 	const char *xmlns;
 	const char *iq_type, *id, *from;
 	JabberIqType type = JABBER_IQ_NONE;
 	gboolean signal_return;
+	JabberID *from_id;
 
 	from = xmlnode_get_attrib(packet, "from");
 	id = xmlnode_get_attrib(packet, "id");
 	iq_type = xmlnode_get_attrib(packet, "type");
 
 	/*
+	 * Ensure the 'from' attribute is valid. No point in handling a stanza
+	 * of which we don't understand where it came from.
+	 */
+	from_id = jabber_id_new(from);
+
+	if (from && !from_id) {
+		purple_debug_error("jabber", "Received an iq with an invalid from: %s\n", from);
+		return;
+	}
+
+	/*
 	 * child will be either the first tag child or NULL if there is no child.
 	 * Historically, we used just the 'query' subchild, but newer XEPs use
 	 * differently named children. Grabbing the first child is (for the time
@@ -309,6 +330,7 @@ void jabber_iq_parse(JabberStream *js, x
 	if (type == JABBER_IQ_NONE) {
 		purple_debug_error("jabber", "IQ with invalid type ('%s') - ignoring.\n",
 						   iq_type ? iq_type : "(null)");
+		jabber_id_free(from_id);
 		return;
 	}
 
@@ -339,20 +361,38 @@ void jabber_iq_parse(JabberStream *js, x
 			purple_debug_error("jabber", "IQ of type '%s' missing id - ignoring.\n",
 			                   iq_type);
 
+		jabber_id_free(from_id);
 		return;
 	}
 
 	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc),
 			"jabber-receiving-iq", js->gc, iq_type, id, from, packet));
-	if (signal_return)
+	if (signal_return) {
+		jabber_id_free(from_id);
 		return;
+	}
 
 	/* First, lets see if a special callback got registered */
 	if(type == JABBER_IQ_RESULT || type == JABBER_IQ_ERROR) {
 		if((jcd = g_hash_table_lookup(js->iq_callbacks, id))) {
-			jcd->callback(js, from, type, id, packet, jcd->data);
-			jabber_iq_remove_callback_by_id(js, id);
-			return;
+			if(jabber_id_equal(js, jcd->to, from_id)) {
+				jcd->callback(js, from, type, id, packet, jcd->data);
+				jabber_iq_remove_callback_by_id(js, id);
+				jabber_id_free(from_id);
+				return;
+			} else {
+				char *expected_to;
+
+				if (jcd->to) {
+					expected_to = jabber_id_get_full_jid(jcd->to);
+				} else {
+					expected_to = jabber_id_get_bare_jid(js->user);
+				}
+
+				purple_debug_error("jabber", "Got a result iq with id %s from %s instead of expected %s!\n", id, from ? from : "(null)", expected_to);
+
+				g_free(expected_to);
+			}
 		}
 	}
 
@@ -369,12 +409,15 @@ void jabber_iq_parse(JabberStream *js, x
 		if (signal_ref > 0) {
 			signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc), "jabber-watched-iq",
 					js->gc, iq_type, id, from, child));
-			if (signal_return)
+			if (signal_return) {
+				jabber_id_free(from_id);
 				return;
+			}
 		}
 
 		if(jih) {
 			jih(js, from, type, id, child);
+			jabber_id_free(from_id);
 			return;
 		}
 	}
@@ -401,6 +444,8 @@ void jabber_iq_parse(JabberStream *js, x
 
 		jabber_iq_send(iq);
 	}
+
+	jabber_id_free(from_id);
 }
 
 void jabber_iq_register_handler(const char *node, const char *xmlns, JabberIqHandler *handlerfunc)
diff -Npur pidgin-2.6.6/libpurple/protocols/jabber/iq.c.orig pidgin-2.6.6-new/libpurple/protocols/jabber/iq.c.orig
--- pidgin-2.6.6/libpurple/protocols/jabber/iq.c.orig	1970-01-01 08:00:00.000000000 +0800
+++ pidgin-2.6.6-new/libpurple/protocols/jabber/iq.c.orig	2010-02-16 17:34:06.000000000 +0800
@@ -0,0 +1,495 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ *
+ */
+#include "internal.h"
+#include "core.h"
+#include "debug.h"
+#include "prefs.h"
+#include "util.h"
+
+#include "buddy.h"
+#include "disco.h"
+#include "google.h"
+#include "iq.h"
+#include "jingle/jingle.h"
+#include "oob.h"
+#include "roster.h"
+#include "si.h"
+#include "ping.h"
+#include "adhoccommands.h"
+#include "data.h"
+#include "ibb.h"
+
+#ifdef _WIN32
+#include "utsname.h"
+#endif
+
+GHashTable *iq_handlers = NULL;
+GHashTable *signal_iq_handlers = NULL;
+
+JabberIq *jabber_iq_new(JabberStream *js, JabberIqType type)
+{
+	JabberIq *iq;
+
+	iq = g_new0(JabberIq, 1);
+
+	iq->type = type;
+
+	iq->node = xmlnode_new("iq");
+	switch(iq->type) {
+		case JABBER_IQ_SET:
+			xmlnode_set_attrib(iq->node, "type", "set");
+			break;
+		case JABBER_IQ_GET:
+			xmlnode_set_attrib(iq->node, "type", "get");
+			break;
+		case JABBER_IQ_ERROR:
+			xmlnode_set_attrib(iq->node, "type", "error");
+			break;
+		case JABBER_IQ_RESULT:
+			xmlnode_set_attrib(iq->node, "type", "result");
+			break;
+		case JABBER_IQ_NONE:
+			/* this shouldn't ever happen */
+			break;
+	}
+
+	iq->js = js;
+
+	if(type == JABBER_IQ_GET || type == JABBER_IQ_SET) {
+		iq->id = jabber_get_next_id(js);
+		xmlnode_set_attrib(iq->node, "id", iq->id);
+	}
+
+	return iq;
+}
+
+JabberIq *jabber_iq_new_query(JabberStream *js, JabberIqType type,
+		const char *xmlns)
+{
+	JabberIq *iq = jabber_iq_new(js, type);
+	xmlnode *query;
+
+	query = xmlnode_new_child(iq->node, "query");
+	xmlnode_set_namespace(query, xmlns);
+
+	return iq;
+}
+
+typedef struct _JabberCallbackData {
+	JabberIqCallback *callback;
+	gpointer data;
+} JabberCallbackData;
+
+void
+jabber_iq_set_callback(JabberIq *iq, JabberIqCallback *callback, gpointer data)
+{
+	iq->callback = callback;
+	iq->callback_data = data;
+}
+
+void jabber_iq_set_id(JabberIq *iq, const char *id)
+{
+	g_free(iq->id);
+
+	if(id) {
+		xmlnode_set_attrib(iq->node, "id", id);
+		iq->id = g_strdup(id);
+	} else {
+		xmlnode_remove_attrib(iq->node, "id");
+		iq->id = NULL;
+	}
+}
+
+void jabber_iq_send(JabberIq *iq)
+{
+	JabberCallbackData *jcd;
+	g_return_if_fail(iq != NULL);
+
+	jabber_send(iq->js, iq->node);
+
+	if(iq->id && iq->callback) {
+		jcd = g_new0(JabberCallbackData, 1);
+		jcd->callback = iq->callback;
+		jcd->data = iq->callback_data;
+		g_hash_table_insert(iq->js->iq_callbacks, g_strdup(iq->id), jcd);
+	}
+
+	jabber_iq_free(iq);
+}
+
+void jabber_iq_free(JabberIq *iq)
+{
+	g_return_if_fail(iq != NULL);
+
+	g_free(iq->id);
+	xmlnode_free(iq->node);
+	g_free(iq);
+}
+
+static void jabber_iq_last_parse(JabberStream *js, const char *from,
+                                 JabberIqType type, const char *id,
+                                 xmlnode *packet)
+{
+	JabberIq *iq;
+	xmlnode *query;
+	char *idle_time;
+
+	if(type == JABBER_IQ_GET) {
+		iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, NS_LAST_ACTIVITY);
+		jabber_iq_set_id(iq, id);
+		if (from)
+			xmlnode_set_attrib(iq->node, "to", from);
+
+		query = xmlnode_get_child(iq->node, "query");
+
+		idle_time = g_strdup_printf("%ld", js->idle ? time(NULL) - js->idle : 0);
+		xmlnode_set_attrib(query, "seconds", idle_time);
+		g_free(idle_time);
+
+		jabber_iq_send(iq);
+	}
+}
+
+static void jabber_time_parse(JabberStream *js, const char *from,
+                              JabberIqType type, const char *id,
+                              xmlnode *child)
+{
+	JabberIq *iq;
+	time_t now_t;
+	struct tm *tm;
+
+	time(&now_t);
+
+	if(type == JABBER_IQ_GET) {
+		xmlnode *tzo, *utc;
+		const char *date, *tz;
+
+		iq = jabber_iq_new(js, JABBER_IQ_RESULT);
+		jabber_iq_set_id(iq, id);
+		if (from)
+			xmlnode_set_attrib(iq->node, "to", from);
+
+		child = xmlnode_new_child(iq->node, child->name);
+		xmlnode_set_namespace(child, NS_ENTITY_TIME);
+
+		/* <tzo>-06:00</tzo> */
+		tm = localtime(&now_t);
+		tz = purple_get_tzoff_str(tm, TRUE);
+		tzo = xmlnode_new_child(child, "tzo");
+		xmlnode_insert_data(tzo, tz, -1);
+
+		/* <utc>2006-12-19T17:58:35Z</utc> */
+		tm = gmtime(&now_t);
+		date = purple_utf8_strftime("%Y-%m-%dT%H:%M:%SZ", tm);
+		utc = xmlnode_new_child(child, "utc");
+		xmlnode_insert_data(utc, date, -1);
+
+		jabber_iq_send(iq);
+	} else {
+		/* TODO: Errors */
+	}
+}
+
+static void jabber_iq_version_parse(JabberStream *js, const char *from,
+                                    JabberIqType type, const char *id,
+                                    xmlnode *packet)
+{
+	JabberIq *iq;
+	xmlnode *query;
+
+	if(type == JABBER_IQ_GET) {
+		GHashTable *ui_info;
+		const char *ui_name = NULL, *ui_version = NULL;
+#if 0
+		char *os = NULL;
+		if(!purple_prefs_get_bool("/plugins/prpl/jabber/hide_os")) {
+			struct utsname osinfo;
+
+			uname(&osinfo);
+			os = g_strdup_printf("%s %s %s", osinfo.sysname, osinfo.release,
+					osinfo.machine);
+		}
+#endif
+
+		iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, "jabber:iq:version");
+		if (from)
+			xmlnode_set_attrib(iq->node, "to", from);
+		jabber_iq_set_id(iq, id);
+
+		query = xmlnode_get_child(iq->node, "query");
+
+		ui_info = purple_core_get_ui_info();
+
+		if(NULL != ui_info) {
+			ui_name = g_hash_table_lookup(ui_info, "name");
+			ui_version = g_hash_table_lookup(ui_info, "version");
+		}
+
+		if(NULL != ui_name && NULL != ui_version) {
+			char *version_complete = g_strdup_printf("%s (libpurple " VERSION ")", ui_version);
+			xmlnode_insert_data(xmlnode_new_child(query, "name"), ui_name, -1);
+			xmlnode_insert_data(xmlnode_new_child(query, "version"), version_complete, -1);
+			g_free(version_complete);
+		} else {
+			xmlnode_insert_data(xmlnode_new_child(query, "name"), "libpurple", -1);
+			xmlnode_insert_data(xmlnode_new_child(query, "version"), VERSION, -1);
+		}
+
+#if 0
+		if(os) {
+			xmlnode_insert_data(xmlnode_new_child(query, "os"), os, -1);
+			g_free(os);
+		}
+#endif
+
+		jabber_iq_send(iq);
+	}
+}
+
+void jabber_iq_remove_callback_by_id(JabberStream *js, const char *id)
+{
+	g_hash_table_remove(js->iq_callbacks, id);
+}
+
+void jabber_iq_parse(JabberStream *js, xmlnode *packet)
+{
+	JabberCallbackData *jcd;
+	xmlnode *child, *error, *x;
+	const char *xmlns;
+	const char *iq_type, *id, *from;
+	JabberIqType type = JABBER_IQ_NONE;
+	gboolean signal_return;
+
+	from = xmlnode_get_attrib(packet, "from");
+	id = xmlnode_get_attrib(packet, "id");
+	iq_type = xmlnode_get_attrib(packet, "type");
+
+	/*
+	 * child will be either the first tag child or NULL if there is no child.
+	 * Historically, we used just the 'query' subchild, but newer XEPs use
+	 * differently named children. Grabbing the first child is (for the time
+	 * being) sufficient.
+	 */
+	for (child = packet->child; child; child = child->next) {
+		if (child->type == XMLNODE_TYPE_TAG)
+			break;
+	}
+
+	if (iq_type) {
+		if (!strcmp(iq_type, "get"))
+			type = JABBER_IQ_GET;
+		else if (!strcmp(iq_type, "set"))
+			type = JABBER_IQ_SET;
+		else if (!strcmp(iq_type, "result"))
+			type = JABBER_IQ_RESULT;
+		else if (!strcmp(iq_type, "error"))
+			type = JABBER_IQ_ERROR;
+	}
+
+	if (type == JABBER_IQ_NONE) {
+		purple_debug_error("jabber", "IQ with invalid type ('%s') - ignoring.\n",
+						   iq_type ? iq_type : "(null)");
+		return;
+	}
+
+	/* All IQs must have an ID, so send an error for a set/get that doesn't */
+	if(!id || !*id) {
+
+		if(type == JABBER_IQ_SET || type == JABBER_IQ_GET) {
+			JabberIq *iq = jabber_iq_new(js, JABBER_IQ_ERROR);
+
+			xmlnode_free(iq->node);
+			iq->node = xmlnode_copy(packet);
+			if (from) {
+				xmlnode_set_attrib(iq->node, "to", from);
+				xmlnode_remove_attrib(iq->node, "from");
+			}
+
+			xmlnode_set_attrib(iq->node, "type", "error");
+			/* This id is clearly not useful, but we must put something there for a valid stanza */
+			iq->id = jabber_get_next_id(js);
+			xmlnode_set_attrib(iq->node, "id", iq->id);
+			error = xmlnode_new_child(iq->node, "error");
+			xmlnode_set_attrib(error, "type", "modify");
+			x = xmlnode_new_child(error, "bad-request");
+			xmlnode_set_namespace(x, NS_XMPP_STANZAS);
+
+			jabber_iq_send(iq);
+		} else
+			purple_debug_error("jabber", "IQ of type '%s' missing id - ignoring.\n",
+			                   iq_type);
+
+		return;
+	}
+
+	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc),
+			"jabber-receiving-iq", js->gc, iq_type, id, from, packet));
+	if (signal_return)
+		return;
+
+	/* First, lets see if a special callback got registered */
+	if(type == JABBER_IQ_RESULT || type == JABBER_IQ_ERROR) {
+		if((jcd = g_hash_table_lookup(js->iq_callbacks, id))) {
+			jcd->callback(js, from, type, id, packet, jcd->data);
+			jabber_iq_remove_callback_by_id(js, id);
+			return;
+		}
+	}
+
+	/*
+	 * Apparently not, so let's see if we have a pre-defined handler
+	 * or if an outside plugin is interested.
+	 */
+	if(child && (xmlns = xmlnode_get_namespace(child))) {
+		char *key = g_strdup_printf("%s %s", child->name, xmlns);
+		JabberIqHandler *jih = g_hash_table_lookup(iq_handlers, key);
+		int signal_ref = GPOINTER_TO_INT(g_hash_table_lookup(signal_iq_handlers, key));
+		g_free(key);
+
+		if (signal_ref > 0) {
+			signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc), "jabber-watched-iq",
+					js->gc, iq_type, id, from, child));
+			if (signal_return)
+				return;
+		}
+
+		if(jih) {
+			jih(js, from, type, id, child);
+			return;
+		}
+	}
+
+	purple_debug_misc("jabber", "Unhandled IQ with id %s\n", id);
+
+	/* If we get here, send the default error reply mandated by XMPP-CORE */
+	if(type == JABBER_IQ_SET || type == JABBER_IQ_GET) {
+		JabberIq *iq = jabber_iq_new(js, JABBER_IQ_ERROR);
+
+		xmlnode_free(iq->node);
+		iq->node = xmlnode_copy(packet);
+		if (from) {
+			xmlnode_set_attrib(iq->node, "to", from);
+			xmlnode_remove_attrib(iq->node, "from");
+		}
+
+		xmlnode_set_attrib(iq->node, "type", "error");
+		error = xmlnode_new_child(iq->node, "error");
+		xmlnode_set_attrib(error, "type", "cancel");
+		xmlnode_set_attrib(error, "code", "501");
+		x = xmlnode_new_child(error, "feature-not-implemented");
+		xmlnode_set_namespace(x, NS_XMPP_STANZAS);
+
+		jabber_iq_send(iq);
+	}
+}
+
+void jabber_iq_register_handler(const char *node, const char *xmlns, JabberIqHandler *handlerfunc)
+{
+	/*
+	 * This is valid because nodes nor namespaces cannot have spaces in them
+	 * (see http://www.w3.org/TR/2006/REC-xml-20060816/ and
+	 * http://www.w3.org/TR/REC-xml-names/)
+	 */
+	char *key = g_strdup_printf("%s %s", node, xmlns);
+	g_hash_table_replace(iq_handlers, key, handlerfunc);
+}
+
+void jabber_iq_signal_register(const gchar *node, const gchar *xmlns)
+{
+	gchar *key;
+	int ref;
+
+	g_return_if_fail(node != NULL && *node != '\0');
+	g_return_if_fail(xmlns != NULL && *xmlns != '\0');
+
+	key = g_strdup_printf("%s %s", node, xmlns);
+	ref = GPOINTER_TO_INT(g_hash_table_lookup(signal_iq_handlers, key));
+	if (ref == 0) {
+		g_hash_table_insert(signal_iq_handlers, key, GINT_TO_POINTER(1));
+	} else {
+		g_hash_table_insert(signal_iq_handlers, key, GINT_TO_POINTER(ref + 1));
+		g_free(key);
+	}
+}
+
+void jabber_iq_signal_unregister(const gchar *node, const gchar *xmlns)
+{
+	gchar *key;
+	int ref;
+
+	g_return_if_fail(node != NULL && *node != '\0');
+	g_return_if_fail(xmlns != NULL && *xmlns != '\0');
+
+	key = g_strdup_printf("%s %s", node, xmlns);
+	ref = GPOINTER_TO_INT(g_hash_table_lookup(signal_iq_handlers, key));
+
+	if (ref == 1) {
+		g_hash_table_remove(signal_iq_handlers, key);
+	} else if (ref > 1) {
+		g_hash_table_insert(signal_iq_handlers, key, GINT_TO_POINTER(ref - 1));
+	}
+
+	g_free(key);
+}
+
+void jabber_iq_init(void)
+{
+	iq_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+	signal_iq_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+
+	jabber_iq_register_handler("jingle", JINGLE, jingle_parse);
+	jabber_iq_register_handler("mailbox", NS_GOOGLE_MAIL_NOTIFY,
+			jabber_gmail_poke);
+	jabber_iq_register_handler("new-mail", NS_GOOGLE_MAIL_NOTIFY,
+			jabber_gmail_poke);
+	jabber_iq_register_handler("ping", NS_PING, jabber_ping_parse);
+	jabber_iq_register_handler("query", NS_GOOGLE_JINGLE_INFO,
+			jabber_google_handle_jingle_info);
+	jabber_iq_register_handler("query", NS_BYTESTREAMS,
+			jabber_bytestreams_parse);
+	jabber_iq_register_handler("query", NS_DISCO_INFO, jabber_disco_info_parse);
+	jabber_iq_register_handler("query", NS_DISCO_ITEMS, jabber_disco_items_parse);
+	jabber_iq_register_handler("query", NS_LAST_ACTIVITY, jabber_iq_last_parse);
+	jabber_iq_register_handler("query", NS_OOB_IQ_DATA, jabber_oob_parse);
+	jabber_iq_register_handler("query", "jabber:iq:register",
+			jabber_register_parse);
+	jabber_iq_register_handler("query", "jabber:iq:roster",
+			jabber_roster_parse);
+	jabber_iq_register_handler("query", "jabber:iq:version",
+			jabber_iq_version_parse);
+#ifdef USE_VV
+	jabber_iq_register_handler("session", NS_GOOGLE_SESSION,
+		jabber_google_session_parse);
+#endif
+	jabber_iq_register_handler("block", NS_SIMPLE_BLOCKING, jabber_blocklist_parse_push);
+	jabber_iq_register_handler("unblock", NS_SIMPLE_BLOCKING, jabber_blocklist_parse_push);
+	jabber_iq_register_handler("time", NS_ENTITY_TIME, jabber_time_parse);
+
+}
+
+void jabber_iq_uninit(void)
+{
+	g_hash_table_destroy(iq_handlers);
+	g_hash_table_destroy(signal_iq_handlers);
+	iq_handlers = signal_iq_handlers = NULL;
+}
diff -Npur pidgin-2.6.6/libpurple/protocols/jabber/iq.h pidgin-2.6.6-new/libpurple/protocols/jabber/iq.h
--- pidgin-2.6.6/libpurple/protocols/jabber/iq.h	2010-02-16 17:34:06.000000000 +0800
+++ pidgin-2.6.6-new/libpurple/protocols/jabber/iq.h	2014-03-14 15:00:28.423253554 +0800
@@ -36,6 +36,7 @@ typedef enum {
 #include "connection.h"
 
 typedef struct _JabberIq JabberIq;
+typedef struct _JabberIqCallbackData  JabberIqCallbackData;
 
 /**
  * A JabberIqHandler is called to process an incoming IQ stanza.
@@ -96,6 +97,7 @@ JabberIq *jabber_iq_new_query(JabberStre
 
 void jabber_iq_parse(JabberStream *js, xmlnode *packet);
 
+void jabber_iq_callbackdata_free(JabberIqCallbackData *jcd);
 void jabber_iq_remove_callback_by_id(JabberStream *js, const char *id);
 void jabber_iq_set_callback(JabberIq *iq, JabberIqCallback *cb, gpointer data);
 void jabber_iq_set_id(JabberIq *iq, const char *id);
diff -Npur pidgin-2.6.6/libpurple/protocols/jabber/jabber.c pidgin-2.6.6-new/libpurple/protocols/jabber/jabber.c
--- pidgin-2.6.6/libpurple/protocols/jabber/jabber.c	2010-02-16 17:34:06.000000000 +0800
+++ pidgin-2.6.6-new/libpurple/protocols/jabber/jabber.c	2014-03-14 15:00:28.423253554 +0800
@@ -853,7 +853,7 @@ jabber_stream_new(PurpleAccount *account
 	js->user_jb->subscription |= JABBER_SUB_BOTH;
 
 	js->iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal,
-			g_free, g_free);
+			g_free, (GDestroyNotify)jabber_iq_callbackdata_free);
 	js->chats = g_hash_table_new_full(g_str_hash, g_str_equal,
 			g_free, (GDestroyNotify)jabber_chat_free);
 	js->next_id = g_random_int();
diff -Npur pidgin-2.6.6/libpurple/protocols/jabber/jabber.c.orig pidgin-2.6.6-new/libpurple/protocols/jabber/jabber.c.orig
--- pidgin-2.6.6/libpurple/protocols/jabber/jabber.c.orig	1970-01-01 08:00:00.000000000 +0800
+++ pidgin-2.6.6-new/libpurple/protocols/jabber/jabber.c.orig	2010-02-16 17:34:06.000000000 +0800
@@ -0,0 +1,3563 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ *
+ */
+#include "internal.h"
+
+#include "account.h"
+#include "accountopt.h"
+#include "blist.h"
+#include "core.h"
+#include "cmds.h"
+#include "connection.h"
+#include "conversation.h"
+#include "debug.h"
+#include "dnssrv.h"
+#include "imgstore.h"
+#include "message.h"
+#include "notify.h"
+#include "pluginpref.h"
+#include "privacy.h"
+#include "proxy.h"
+#include "prpl.h"
+#include "request.h"
+#include "server.h"
+#include "status.h"
+#include "util.h"
+#include "version.h"
+#include "xmlnode.h"
+
+#include "auth.h"
+#include "buddy.h"
+#include "caps.h"
+#include "chat.h"
+#include "data.h"
+#include "disco.h"
+#include "google.h"
+#include "ibb.h"
+#include "iq.h"
+#include "jutil.h"
+#include "message.h"
+#include "parser.h"
+#include "presence.h"
+#include "jabber.h"
+#include "roster.h"
+#include "ping.h"
+#include "si.h"
+#include "xdata.h"
+#include "pep.h"
+#include "adhoccommands.h"
+
+#include "jingle/jingle.h"
+#include "jingle/rtp.h"
+
+GList *jabber_features = NULL;
+GList *jabber_identities = NULL;
+static GSList *jabber_cmds = NULL;
+
+static void jabber_unregister_account_cb(JabberStream *js);
+static void try_srv_connect(JabberStream *js);
+
+static void jabber_stream_init(JabberStream *js)
+{
+	char *open_stream;
+
+	open_stream = g_strdup_printf("<stream:stream to='%s' "
+				          "xmlns='" NS_XMPP_CLIENT "' "
+						  "xmlns:stream='" NS_XMPP_STREAMS "' "
+						  "version='1.0'>",
+						  js->user->domain);
+	/* setup the parser fresh for each stream */
+	jabber_parser_setup(js);
+	jabber_send_raw(js, open_stream, -1);
+	js->reinit = FALSE;
+	g_free(open_stream);
+}
+
+static void
+jabber_session_initialized_cb(JabberStream *js, const char *from,
+                              JabberIqType type, const char *id,
+                              xmlnode *packet, gpointer data)
+{
+	if (type == JABBER_IQ_RESULT) {
+		jabber_disco_items_server(js);
+		if(js->unregistration)
+			jabber_unregister_account_cb(js);
+	} else {
+		purple_connection_error_reason(js->gc,
+			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+			("Error initializing session"));
+	}
+}
+
+static void jabber_session_init(JabberStream *js)
+{
+	JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET);
+	xmlnode *session;
+
+	jabber_iq_set_callback(iq, jabber_session_initialized_cb, NULL);
+
+	session = xmlnode_new_child(iq->node, "session");
+	xmlnode_set_namespace(session, NS_XMPP_SESSION);
+
+	jabber_iq_send(iq);
+}
+
+static void jabber_bind_result_cb(JabberStream *js, const char *from,
+                                  JabberIqType type, const char *id,
+                                  xmlnode *packet, gpointer data)
+{
+	xmlnode *bind;
+
+	if (type == JABBER_IQ_RESULT &&
+			(bind = xmlnode_get_child_with_namespace(packet, "bind", NS_XMPP_BIND))) {
+		xmlnode *jid;
+		char *full_jid;
+		if((jid = xmlnode_get_child(bind, "jid")) && (full_jid = xmlnode_get_data(jid))) {
+			jabber_id_free(js->user);
+
+			js->user = jabber_id_new(full_jid);
+			if (js->user == NULL) {
+				purple_connection_error_reason(js->gc,
+					PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+					_("Invalid response from server"));
+				g_free(full_jid);
+				return;
+			}
+
+			js->user_jb = jabber_buddy_find(js, full_jid, TRUE);
+			js->user_jb->subscription |= JABBER_SUB_BOTH;
+
+			purple_connection_set_display_name(js->gc, full_jid);
+
+			g_free(full_jid);
+		}
+	} else {
+		PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
+		char *msg = jabber_parse_error(js, packet, &reason);
+		purple_connection_error_reason(js->gc, reason, msg);
+		g_free(msg);
+	}
+
+	jabber_session_init(js);
+}
+
+static char *jabber_prep_resource(char *input) {
+	char hostname[256], /* current hostname */
+		 *dot = NULL;
+
+	/* Empty resource == don't send any */
+	if (input == NULL || *input == '\0')
+		return NULL;
+
+	if (strstr(input, "__HOSTNAME__") == NULL)
+		return g_strdup(input);
+
+	/* Replace __HOSTNAME__ with hostname */
+	if (gethostname(hostname, sizeof(hostname) - 1)) {
+		purple_debug_warning("jabber", "gethostname: %s\n", g_strerror(errno));
+		/* according to glibc doc, the only time an error is returned
+		   is if the hostname is longer than the buffer, in which case
+		   glibc 2.2+ would still fill the buffer with partial
+		   hostname, so maybe we want to detect that and use it
+		   instead
+		*/
+		strcpy(hostname, "localhost");
+	}
+	hostname[sizeof(hostname) - 1] = '\0';
+
+	/* We want only the short hostname, not the FQDN - this will prevent the
+	 * resource string from being unreasonably long on systems which stuff the
+	 * whole FQDN in the hostname */
+	if((dot = strchr(hostname, '.')))
+			dot = '\0';
+
+	return purple_strreplace(input, "__HOSTNAME__", hostname);
+}
+
+void jabber_stream_features_parse(JabberStream *js, xmlnode *packet)
+{
+	if(xmlnode_get_child(packet, "starttls")) {
+		if(jabber_process_starttls(js, packet)) {
+			jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING_ENCRYPTION);
+			return;
+		}
+	} else if(purple_account_get_bool(js->gc->account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS) && !jabber_stream_is_ssl(js)) {
+		purple_connection_error_reason(js->gc,
+			 PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
+			_("You require encryption, but it is not available on this server."));
+		return;
+	}
+
+	if(js->registration) {
+		jabber_register_start(js);
+	} else if(xmlnode_get_child(packet, "mechanisms")) {
+		jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING);
+		jabber_auth_start(js, packet);
+	} else if(xmlnode_get_child(packet, "bind")) {
+		xmlnode *bind, *resource;
+		char *requested_resource;
+		JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET);
+		bind = xmlnode_new_child(iq->node, "bind");
+		xmlnode_set_namespace(bind, NS_XMPP_BIND);
+		requested_resource = jabber_prep_resource(js->user->resource);
+
+		if (requested_resource != NULL) {
+			resource = xmlnode_new_child(bind, "resource");
+			xmlnode_insert_data(resource, requested_resource, -1);
+			g_free(requested_resource);
+		}
+
+		jabber_iq_set_callback(iq, jabber_bind_result_cb, NULL);
+
+		jabber_iq_send(iq);
+	} else if (xmlnode_get_child_with_namespace(packet, "ver", NS_ROSTER_VERSIONING)) {
+		js->server_caps |= JABBER_CAP_ROSTER_VERSIONING;
+	} else /* if(xmlnode_get_child_with_namespace(packet, "auth")) */ {
+		/* If we get an empty stream:features packet, or we explicitly get
+		 * an auth feature with namespace http://jabber.org/features/iq-auth
+		 * we should revert back to iq:auth authentication, even though we're
+		 * connecting to an XMPP server.  */
+		jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING);
+		jabber_auth_start_old(js);
+	}
+}
+
+static void jabber_stream_handle_error(JabberStream *js, xmlnode *packet)
+{
+	PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
+	char *msg = jabber_parse_error(js, packet, &reason);
+
+	purple_connection_error_reason(js->gc, reason, msg);
+
+	g_free(msg);
+}
+
+static void tls_init(JabberStream *js);
+
+void jabber_process_packet(JabberStream *js, xmlnode **packet)
+{
+	const char *xmlns;
+
+	purple_signal_emit(purple_connection_get_prpl(js->gc), "jabber-receiving-xmlnode", js->gc, packet);
+
+	/* if the signal leaves us with a null packet, we're done */
+	if(NULL == *packet)
+		return;
+
+	xmlns = xmlnode_get_namespace(*packet);
+
+	if(!strcmp((*packet)->name, "iq")) {
+		jabber_iq_parse(js, *packet);
+	} else if(!strcmp((*packet)->name, "presence")) {
+		jabber_presence_parse(js, *packet);
+	} else if(!strcmp((*packet)->name, "message")) {
+		jabber_message_parse(js, *packet);
+	} else if(!strcmp((*packet)->name, "stream:features")) {
+		jabber_stream_features_parse(js, *packet);
+	} else if (!strcmp((*packet)->name, "features") && xmlns &&
+		   !strcmp(xmlns, NS_XMPP_STREAMS)) {
+		jabber_stream_features_parse(js, *packet);
+	} else if(!strcmp((*packet)->name, "stream:error") ||
+			 (!strcmp((*packet)->name, "error") && xmlns &&
+				!strcmp(xmlns, NS_XMPP_STREAMS)))
+	{
+		jabber_stream_handle_error(js, *packet);
+	} else if(!strcmp((*packet)->name, "challenge")) {
+		if(js->state == JABBER_STREAM_AUTHENTICATING)
+			jabber_auth_handle_challenge(js, *packet);
+	} else if(!strcmp((*packet)->name, "success")) {
+		if(js->state == JABBER_STREAM_AUTHENTICATING)
+			jabber_auth_handle_success(js, *packet);
+	} else if(!strcmp((*packet)->name, "failure")) {
+		if(js->state == JABBER_STREAM_AUTHENTICATING)
+			jabber_auth_handle_failure(js, *packet);
+	} else if(!strcmp((*packet)->name, "proceed")) {
+		if (js->state == JABBER_STREAM_INITIALIZING_ENCRYPTION && !js->gsc)
+			tls_init(js);
+		else
+			purple_debug_warning("jabber", "Ignoring spurious <proceed/>\n");
+	} else {
+		purple_debug_warning("jabber", "Unknown packet: %s\n", (*packet)->name);
+	}
+}
+
+static int jabber_do_send(JabberStream *js, const char *data, int len)
+{
+	int ret;
+
+	if (js->gsc)
+		ret = purple_ssl_write(js->gsc, data, len);
+	else
+		ret = write(js->fd, data, len);
+
+	return ret;
+}
+
+static void jabber_send_cb(gpointer data, gint source, PurpleInputCondition cond)
+{
+	JabberStream *js = data;
+	int ret, writelen;
+	writelen = purple_circ_buffer_get_max_read(js->write_buffer);
+
+	if (writelen == 0) {
+		purple_input_remove(js->writeh);
+		js->writeh = 0;
+		return;
+	}
+
+	ret = jabber_do_send(js, js->write_buffer->outptr, writelen);
+
+	if (ret < 0 && errno == EAGAIN)
+		return;
+	else if (ret <= 0) {
+		gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
+				g_strerror(errno));
+		purple_connection_error_reason(js->gc,
+			PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
+		g_free(tmp);
+		return;
+	}
+
+	purple_circ_buffer_mark_read(js->write_buffer, ret);
+}
+
+static gboolean do_jabber_send_raw(JabberStream *js, const char *data, int len)
+{
+	int ret;
+	gboolean success = TRUE;
+
+	if (len == -1)
+		len = strlen(data);
+
+	if (js->writeh == 0)
+		ret = jabber_do_send(js, data, len);
+	else {
+		ret = -1;
+		errno = EAGAIN;
+	}
+
+	if (ret < 0 && errno != EAGAIN) {
+		PurpleAccount *account = purple_connection_get_account(js->gc);
+		/*
+		 * The server may have closed the socket (on a stream error), so if
+		 * we're disconnecting, don't generate (possibly another) error that
+		 * (for some UIs) would mask the first.
+		 */
+		if (!account->disconnecting) {
+			gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
+					g_strerror(errno));
+			purple_connection_error_reason(js->gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
+			g_free(tmp);
+		}
+
+		success = FALSE;
+	} else if (ret < len) {
+		if (ret < 0)
+			ret = 0;
+		if (js->writeh == 0)
+			js->writeh = purple_input_add(
+				js->gsc ? js->gsc->fd : js->fd,
+				PURPLE_INPUT_WRITE, jabber_send_cb, js);
+		purple_circ_buffer_append(js->write_buffer,
+			data + ret, len - ret);
+	}
+
+	return success;
+}
+
+void jabber_send_raw(JabberStream *js, const char *data, int len)
+{
+	/* because printing a tab to debug every minute gets old */
+	if(strcmp(data, "\t")) {
+		const char *username;
+		char *text = NULL, *last_part = NULL, *tag_start = NULL;
+
+		/* Because debug logs with plaintext passwords make me sad */
+		if(js->state != JABBER_STREAM_CONNECTED &&
+				/* Either <auth> or <query><password>... */
+				(((tag_start = strstr(data, "<auth ")) &&
+					strstr(data, "xmlns='" NS_XMPP_SASL "'")) ||
+				((tag_start = strstr(data, "<query ")) &&
+					strstr(data, "xmlns='jabber:iq:auth'>") &&
+					(tag_start = strstr(tag_start, "<password>"))))) {
+			char *data_start, *tag_end = strchr(tag_start, '>');
+			text = g_strdup(data);
+
+			/* Better to print out some wacky debugging than crash
+			 * due to a plugin sending bad xml */
+			if (tag_end == NULL)
+				tag_end = tag_start;
+
+			data_start = text + (tag_end - data) + 1;
+
+			last_part = strchr(data_start, '<');
+			*data_start = '\0';
+		}
+
+		username = purple_connection_get_display_name(js->gc);
+		if (!username)
+			username = purple_account_get_username(purple_connection_get_account(js->gc));
+
+		purple_debug_misc("jabber", "Sending%s (%s): %s%s%s\n",
+				jabber_stream_is_ssl(js) ? " (ssl)" : "", username,
+				text ? text : data,
+				last_part ? "password removed" : "",
+				last_part ? last_part : "");
+
+		g_free(text);
+	}
+
+	purple_signal_emit(purple_connection_get_prpl(js->gc), "jabber-sending-text", js->gc, &data);
+	if (data == NULL)
+		return;
+
+	/* If we've got a security layer, we need to encode the data,
+	 * splitting it on the maximum buffer length negotiated */
+#ifdef HAVE_CYRUS_SASL
+	if (js->sasl_maxbuf>0) {
+		int pos = 0;
+
+		if (!js->gsc && js->fd<0)
+			return;
+
+		if (len == -1)
+			len = strlen(data);
+
+		while (pos < len) {
+			int towrite;
+			const char *out;
+			unsigned olen;
+
+			towrite = MIN((len - pos), js->sasl_maxbuf);
+
+			sasl_encode(js->sasl, &data[pos], towrite, &out, &olen);
+			pos += towrite;
+
+			if (!do_jabber_send_raw(js, out, olen))
+				break;
+		}
+		return;
+	}
+#endif
+
+	if (len == -1)
+		len = strlen(data);
+
+	if (js->bosh)
+		jabber_bosh_connection_send_raw(js->bosh, data);
+	else
+		do_jabber_send_raw(js, data, len);
+}
+
+int jabber_prpl_send_raw(PurpleConnection *gc, const char *buf, int len)
+{
+	JabberStream *js = (JabberStream*)gc->proto_data;
+	jabber_send_raw(js, buf, len);
+	return len;
+}
+
+void jabber_send_signal_cb(PurpleConnection *pc, xmlnode **packet,
+                           gpointer unused)
+{
+	JabberStream *js;
+	char *txt;
+	int len;
+
+	if (NULL == packet)
+		return;
+
+	if (!PURPLE_CONNECTION_IS_VALID(pc))
+		return;
+
+	js = purple_connection_get_protocol_data(pc);
+
+	if (NULL == js)
+		return;
+
+	if (js->bosh)
+		if (g_str_equal((*packet)->name, "message") ||
+				g_str_equal((*packet)->name, "iq") ||
+				g_str_equal((*packet)->name, "presence"))
+			xmlnode_set_namespace(*packet, NS_XMPP_CLIENT);
+	txt = xmlnode_to_str(*packet, &len);
+	jabber_send_raw(js, txt, len);
+	g_free(txt);
+}
+
+void jabber_send(JabberStream *js, xmlnode *packet)
+{
+	purple_signal_emit(purple_connection_get_prpl(js->gc), "jabber-sending-xmlnode", js->gc, &packet);
+}
+
+static gboolean jabber_keepalive_timeout(PurpleConnection *gc)
+{
+	JabberStream *js = gc->proto_data;
+	purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+					_("Ping timed out"));
+	js->keepalive_timeout = 0;
+	return FALSE;
+}
+
+void jabber_keepalive(PurpleConnection *gc)
+{
+	JabberStream *js = gc->proto_data;
+
+	if (js->keepalive_timeout == 0) {
+		jabber_keepalive_ping(js);
+		js->keepalive_timeout = purple_timeout_add_seconds(120,
+				(GSourceFunc)(jabber_keepalive_timeout), gc);
+	}
+}
+
+static void
+jabber_recv_cb_ssl(gpointer data, PurpleSslConnection *gsc,
+		PurpleInputCondition cond)
+{
+	PurpleConnection *gc = data;
+	JabberStream *js = gc->proto_data;
+	int len;
+	static char buf[4096];
+
+	/* TODO: It should be possible to make this check unnecessary */
+	if(!PURPLE_CONNECTION_IS_VALID(gc)) {
+		purple_ssl_close(gsc);
+		return;
+	}
+
+	while((len = purple_ssl_read(gsc, buf, sizeof(buf) - 1)) > 0) {
+		gc->last_received = time(NULL);
+		buf[len] = '\0';
+		purple_debug_info("jabber", "Recv (ssl)(%d): %s\n", len, buf);
+		jabber_parser_process(js, buf, len);
+		if(js->reinit)
+			jabber_stream_init(js);
+	}
+
+	if(len < 0 && errno == EAGAIN)
+		return;
+	else {
+		gchar *tmp;
+		if (len == 0)
+			tmp = g_strdup_printf(_("Server closed the connection"));
+		else
+			tmp = g_strdup_printf(_("Lost connection with server: %s"),
+					g_strerror(errno));
+		purple_connection_error_reason(js->gc,
+			PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
+		g_free(tmp);
+	}
+}
+
+static void
+jabber_recv_cb(gpointer data, gint source, PurpleInputCondition condition)
+{
+	PurpleConnection *gc = data;
+	JabberStream *js = gc->proto_data;
+	int len;
+	static char buf[4096];
+
+	if(!PURPLE_CONNECTION_IS_VALID(gc))
+		return;
+
+	if((len = read(js->fd, buf, sizeof(buf) - 1)) > 0) {
+		gc->last_received = time(NULL);
+#ifdef HAVE_CYRUS_SASL
+		if (js->sasl_maxbuf>0) {
+			const char *out;
+			unsigned int olen;
+			sasl_decode(js->sasl, buf, len, &out, &olen);
+			if (olen>0) {
+				purple_debug_info("jabber", "RecvSASL (%u): %s\n", olen, out);
+				jabber_parser_process(js,out,olen);
+				if(js->reinit)
+					jabber_stream_init(js);
+			}
+			return;
+		}
+#endif
+		buf[len] = '\0';
+		purple_debug_info("jabber", "Recv (%d): %s\n", len, buf);
+		jabber_parser_process(js, buf, len);
+		if(js->reinit)
+			jabber_stream_init(js);
+	} else if(len < 0 && errno == EAGAIN) {
+		return;
+	} else {
+		gchar *tmp;
+		if (len == 0)
+			tmp = g_strdup_printf(_("Server closed the connection"));
+		else
+			tmp = g_strdup_printf(_("Lost connection with server: %s"),
+					g_strerror(errno));
+		purple_connection_error_reason(js->gc,
+			PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
+		g_free(tmp);
+	}
+}
+
+static void
+jabber_login_callback_ssl(gpointer data, PurpleSslConnection *gsc,
+		PurpleInputCondition cond)
+{
+	PurpleConnection *gc = data;
+	JabberStream *js;
+
+	/* TODO: It should be possible to make this check unnecessary */
+	if(!PURPLE_CONNECTION_IS_VALID(gc)) {
+		purple_ssl_close(gsc);
+		return;
+	}
+
+	js = gc->proto_data;
+
+	if(js->state == JABBER_STREAM_CONNECTING)
+		jabber_send_raw(js, "<?xml version='1.0' ?>", -1);
+	jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING);
+	purple_ssl_input_add(gsc, jabber_recv_cb_ssl, gc);
+
+	/* Tell the app that we're doing encryption */
+	jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING_ENCRYPTION);
+}
+
+static void
+txt_resolved_cb(GList *responses, gpointer data)
+{
+	JabberStream *js = data;
+
+	js->srv_query_data = NULL;
+
+	if (responses == NULL) {
+		purple_debug_warning("jabber", "Unable to find alternative XMPP connection "
+				  "methods after failing to connect directly.\n");
+		purple_connection_error_reason(js->gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Unable to connect"));
+		return;
+	}
+
+	while (responses) {
+		PurpleTxtResponse *resp = responses->data;
+		gchar **token;
+		token = g_strsplit(purple_txt_response_get_content(resp), "=", 2);
+		if (!strcmp(token[0], "_xmpp-client-xbosh")) {
+			purple_debug_info("jabber","Found alternative connection method using %s at %s.\n", token[0], token[1]);
+			js->bosh = jabber_bosh_connection_init(js, token[1]);
+			g_strfreev(token);
+			break;
+		}
+		g_strfreev(token);
+		purple_txt_response_destroy(resp);
+		responses = g_list_delete_link(responses, responses);
+	}
+
+	if (js->bosh) {
+		jabber_bosh_connection_connect(js->bosh);
+	} else {
+		purple_debug_info("jabber","Didn't find an alternative connection method.\n");
+	}
+
+	if (responses) {
+		g_list_foreach(responses, (GFunc)purple_txt_response_destroy, NULL);
+		g_list_free(responses);
+	}
+}
+
+static void
+jabber_login_callback(gpointer data, gint source, const gchar *error)
+{
+	PurpleConnection *gc = data;
+	JabberStream *js = purple_connection_get_protocol_data(gc);
+
+	if (source < 0) {
+		if (js->srv_rec != NULL) {
+			purple_debug_error("jabber", "Unable to connect to server: %s.  Trying next SRV record or connecting directly.\n", error);
+			try_srv_connect(js);
+		} else {
+			purple_debug_info("jabber","Couldn't connect directly to %s.  Trying to find alternative connection methods, like BOSH.\n", js->user->domain);
+			js->srv_query_data = purple_txt_resolve("_xmppconnect",
+					js->user->domain, txt_resolved_cb, js);
+		}
+		return;
+	}
+
+	g_free(js->srv_rec);
+	js->srv_rec = NULL;
+
+	js->fd = source;
+
+	if(js->state == JABBER_STREAM_CONNECTING)
+		jabber_send_raw(js, "<?xml version='1.0' ?>", -1);
+
+	jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING);
+	gc->inpa = purple_input_add(js->fd, PURPLE_INPUT_READ, jabber_recv_cb, gc);
+}
+
+static void
+jabber_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
+		gpointer data)
+{
+	PurpleConnection *gc = data;
+	JabberStream *js;
+
+	/* If the connection is already disconnected, we don't need to do anything else */
+	if(!PURPLE_CONNECTION_IS_VALID(gc))
+		return;
+
+	js = gc->proto_data;
+	js->gsc = NULL;
+
+	purple_connection_ssl_error (gc, error);
+}
+
+static void tls_init(JabberStream *js)
+{
+	purple_input_remove(js->gc->inpa);
+	js->gc->inpa = 0;
+	js->gsc = purple_ssl_connect_with_host_fd(js->gc->account, js->fd,
+			jabber_login_callback_ssl, jabber_ssl_connect_failure, js->certificate_CN, js->gc);
+	/* The fd is no longer our concern */
+	js->fd = -1;
+}
+
+static gboolean jabber_login_connect(JabberStream *js, const char *domain, const char *host, int port,
+				 gboolean fatal_failure)
+{
+	/* host should be used in preference to domain to
+	 * allow SASL authentication to work with FQDN of the server,
+	 * but we use domain as fallback for when users enter IP address
+	 * in connect server */
+	g_free(js->serverFQDN);
+	if (purple_ip_address_is_valid(host))
+		js->serverFQDN = g_strdup(domain);
+	else
+		js->serverFQDN = g_strdup(host);
+
+	if (purple_proxy_connect(js->gc, purple_connection_get_account(js->gc),
+			host, port, jabber_login_callback, js->gc) == NULL) {
+		if (fatal_failure) {
+			purple_connection_error_reason(js->gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Unable to connect"));
+		}
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static void try_srv_connect(JabberStream *js)
+{
+	while (js->srv_rec != NULL && js->srv_rec_idx < js->max_srv_rec_idx) {
+		PurpleSrvResponse *tmp_resp = js->srv_rec + (js->srv_rec_idx++);
+		if (jabber_login_connect(js, tmp_resp->hostname, tmp_resp->hostname, tmp_resp->port, FALSE))
+			return;
+	}
+
+	g_free(js->srv_rec);
+	js->srv_rec = NULL;
+
+	/* Fall back to the defaults (I'm not sure if we should actually do this) */
+	jabber_login_connect(js, js->user->domain, js->user->domain,
+			purple_account_get_int(purple_connection_get_account(js->gc), "port", 5222),
+			TRUE);
+}
+
+static void srv_resolved_cb(PurpleSrvResponse *resp, int results, gpointer data)
+{
+	JabberStream *js = data;
+	js->srv_query_data = NULL;
+
+	if(results) {
+		js->srv_rec = resp;
+		js->srv_rec_idx = 0;
+		js->max_srv_rec_idx = results;
+		try_srv_connect(js);
+	} else {
+		jabber_login_connect(js, js->user->domain, js->user->domain,
+				purple_account_get_int(purple_connection_get_account(js->gc), "port", 5222),
+				TRUE);
+	}
+}
+
+static JabberStream *
+jabber_stream_new(PurpleAccount *account)
+{
+	PurpleConnection *gc = purple_account_get_connection(account);
+	JabberStream *js;
+	PurplePresence *presence;
+	gchar *user;
+	gchar *slash;
+
+	js = gc->proto_data = g_new0(JabberStream, 1);
+	js->gc = gc;
+	js->fd = -1;
+
+	user = g_strdup(purple_account_get_username(account));
+	/* jabber_id_new doesn't accept "user@domain/" as valid */
+	slash = strchr(user, '/');
+	if (slash && *(slash + 1) == '\0')
+		*slash = '\0';
+	js->user = jabber_id_new(user);
+
+	if (!js->user) {
+		purple_connection_error_reason(gc,
+			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
+			_("Invalid XMPP ID"));
+		/* Destroying the connection will free the JabberStream */
+		return NULL;
+	}
+
+	if (!js->user->domain || *(js->user->domain) == '\0') {
+		purple_connection_error_reason(gc,
+			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
+			_("Invalid XMPP ID. Domain must be set."));
+		/* Destroying the connection will free the JabberStream */
+		return NULL;
+	}
+
+	js->buddies = g_hash_table_new_full(g_str_hash, g_str_equal,
+			g_free, (GDestroyNotify)jabber_buddy_free);
+
+	js->user_jb = jabber_buddy_find(js, user, TRUE);
+	g_free(user);
+	if (!js->user_jb) {
+		/* This basically *can't* fail, but for good measure... */
+		purple_connection_error_reason(gc,
+			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
+			_("Invalid XMPP ID"));
+		/* Destroying the connection will free the JabberStream */
+		g_return_val_if_reached(NULL);
+	}
+
+	js->user_jb->subscription |= JABBER_SUB_BOTH;
+
+	js->iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal,
+			g_free, g_free);
+	js->chats = g_hash_table_new_full(g_str_hash, g_str_equal,
+			g_free, (GDestroyNotify)jabber_chat_free);
+	js->next_id = g_random_int();
+	js->write_buffer = purple_circ_buffer_new(512);
+	js->old_length = 0;
+	js->keepalive_timeout = 0;
+	/* Set the default protocol version to 1.0. Overridden in parser.c. */
+	js->protocol_version = JABBER_PROTO_1_0;
+	js->sessions = NULL;
+	js->stun_ip = NULL;
+	js->stun_port = 0;
+	js->stun_query = NULL;
+
+	/* if we are idle, set idle-ness on the stream (this could happen if we get
+		disconnected and the reconnects while being idle. I don't think it makes
+		sense to do this when registering a new account... */
+	presence = purple_account_get_presence(account);
+	if (purple_presence_is_idle(presence))
+		js->idle = purple_presence_get_idle_time(presence);
+
+	return js;
+}
+
+static void
+jabber_stream_connect(JabberStream *js)
+{
+	PurpleConnection *gc = js->gc;
+	PurpleAccount *account = purple_connection_get_account(gc);
+	const char *connect_server = purple_account_get_string(account,
+			"connect_server", "");
+	const char *bosh_url = purple_account_get_string(account,
+			"bosh_url", "");
+
+	jabber_stream_set_state(js, JABBER_STREAM_CONNECTING);
+
+	/* If both BOSH and a Connect Server are specified, we prefer BOSH. I'm not
+	 * attached to that choice, though.
+	 */
+	if (*bosh_url) {
+		js->bosh = jabber_bosh_connection_init(js, bosh_url);
+		if (js->bosh)
+			jabber_bosh_connection_connect(js->bosh);
+		else {
+			purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
+				_("Malformed BOSH URL"));
+		}
+
+		return;
+	}
+
+	js->certificate_CN = g_strdup(connect_server[0] ? connect_server : js->user->domain);
+
+	/* if they've got old-ssl mode going, we probably want to ignore SRV lookups */
+	if(purple_account_get_bool(account, "old_ssl", FALSE)) {
+		if(purple_ssl_is_supported()) {
+			js->gsc = purple_ssl_connect(account, js->certificate_CN,
+					purple_account_get_int(account, "port", 5223),
+					jabber_login_callback_ssl, jabber_ssl_connect_failure, gc);
+			if (!js->gsc) {
+				purple_connection_error_reason(gc,
+					PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
+					_("Unable to establish SSL connection"));
+			}
+		} else {
+			purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
+				_("SSL support unavailable"));
+		}
+
+		return;
+	}
+
+	/* no old-ssl, so if they've specified a connect server, we'll use that, otherwise we'll
+	 * invoke the magic of SRV lookups, to figure out host and port */
+	if(connect_server[0]) {
+		jabber_login_connect(js, js->user->domain, connect_server,
+				purple_account_get_int(account, "port", 5222), TRUE);
+	} else {
+		js->srv_query_data = purple_srv_resolve("xmpp-client",
+				"tcp", js->user->domain, srv_resolved_cb, js);
+	}
+}
+
+void
+jabber_login(PurpleAccount *account)
+{
+	PurpleConnection *gc = purple_account_get_connection(account);
+	JabberStream *js;
+	PurpleStoredImage *image;
+
+	gc->flags |= PURPLE_CONNECTION_HTML |
+		PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
+	js = jabber_stream_new(account);
+	if (js == NULL)
+		return;
+
+	/* TODO: Remove this at some point.  Added 2010-02-14 (v2.6.6) */
+	if (g_str_equal("proxy.jabber.org", purple_account_get_string(account, "ft_proxies", "")))
+		purple_account_set_string(account, "ft_proxies", JABBER_DEFAULT_FT_PROXIES);
+
+	/*
+	 * Calculate the avatar hash for our current image so we know (when we
+	 * fetch our vCard and PEP avatar) if we should send our avatar to the
+	 * server.
+	 */
+	image = purple_buddy_icons_find_account_icon(account);
+	if (image != NULL) {
+		js->initial_avatar_hash =
+			jabber_calculate_data_sha1sum(purple_imgstore_get_data(image),
+					purple_imgstore_get_size(image));
+		purple_imgstore_unref(image);
+	}
+
+	jabber_stream_connect(js);
+}
+
+
+static gboolean
+conn_close_cb(gpointer data)
+{
+	JabberStream *js = data;
+	PurpleAccount *account = purple_connection_get_account(js->gc);
+
+	jabber_parser_free(js);
+
+	purple_account_disconnect(account);
+
+	return FALSE;
+}
+
+static void
+jabber_connection_schedule_close(JabberStream *js)
+{
+	purple_timeout_add(0, conn_close_cb, js);
+}
+
+static void
+jabber_registration_result_cb(JabberStream *js, const char *from,
+                              JabberIqType type, const char *id,
+                              xmlnode *packet, gpointer data)
+{
+	PurpleAccount *account = purple_connection_get_account(js->gc);
+	char *buf;
+	char *to = data;
+
+	if (type == JABBER_IQ_RESULT) {
+		if(js->registration) {
+			buf = g_strdup_printf(_("Registration of %s@%s successful"),
+					js->user->node, js->user->domain);
+			if(account->registration_cb)
+				(account->registration_cb)(account, TRUE, account->registration_cb_user_data);
+		} else {
+			g_return_if_fail(to != NULL);
+			buf = g_strdup_printf(_("Registration to %s successful"),
+				to);
+		}
+		purple_notify_info(NULL, _("Registration Successful"),
+				_("Registration Successful"), buf);
+		g_free(buf);
+	} else {
+		char *msg = jabber_parse_error(js, packet, NULL);
+
+		if(!msg)
+			msg = g_strdup(_("Unknown Error"));
+
+		purple_notify_error(NULL, _("Registration Failed"),
+				_("Registration Failed"), msg);
+		g_free(msg);
+		if(account->registration_cb)
+			(account->registration_cb)(account, FALSE, account->registration_cb_user_data);
+	}
+	g_free(to);
+	if(js->registration)
+		jabber_connection_schedule_close(js);
+}
+
+static void
+jabber_unregistration_result_cb(JabberStream *js, const char *from,
+                                JabberIqType type, const char *id,
+                                xmlnode *packet, gpointer data)
+{
+	char *buf;
+	char *to = data;
+
+	/* This function is never called for unregistering our XMPP account from
+	 * the server, so there should always be a 'to' address. */
+	g_return_if_fail(to != NULL);
+
+	if (type == JABBER_IQ_RESULT) {
+		buf = g_strdup_printf(_("Registration from %s successfully removed"),
+							  to);
+		purple_notify_info(NULL, _("Unregistration Successful"),
+						   _("Unregistration Successful"), buf);
+		g_free(buf);
+	} else {
+		char *msg = jabber_parse_error(js, packet, NULL);
+
+		if(!msg)
+			msg = g_strdup(_("Unknown Error"));
+
+		purple_notify_error(NULL, _("Unregistration Failed"),
+							_("Unregistration Failed"), msg);
+		g_free(msg);
+	}
+	g_free(to);
+}
+
+typedef struct _JabberRegisterCBData {
+	JabberStream *js;
+	char *who;
+} JabberRegisterCBData;
+
+static void
+jabber_register_cb(JabberRegisterCBData *cbdata, PurpleRequestFields *fields)
+{
+	GList *groups, *flds;
+	xmlnode *query, *y;
+	JabberIq *iq;
+	char *username;
+
+	iq = jabber_iq_new_query(cbdata->js, JABBER_IQ_SET, "jabber:iq:register");
+	query = xmlnode_get_child(iq->node, "query");
+	if (cbdata->who)
+		xmlnode_set_attrib(iq->node, "to", cbdata->who);
+
+	for(groups = purple_request_fields_get_groups(fields); groups;
+			groups = groups->next) {
+		for(flds = purple_request_field_group_get_fields(groups->data);
+				flds; flds = flds->next) {
+			PurpleRequestField *field = flds->data;
+			const char *id = purple_request_field_get_id(field);
+			if(!strcmp(id,"unregister")) {
+				gboolean value = purple_request_field_bool_get_value(field);
+				if(value) {
+					/* unregister from service. this doesn't include any of the fields, so remove them from the stanza by recreating it
+					   (there's no "remove child" function for xmlnode) */
+					jabber_iq_free(iq);
+					iq = jabber_iq_new_query(cbdata->js, JABBER_IQ_SET, "jabber:iq:register");
+					query = xmlnode_get_child(iq->node, "query");
+					if (cbdata->who)
+						xmlnode_set_attrib(iq->node,"to",cbdata->who);
+					xmlnode_new_child(query, "remove");
+
+					jabber_iq_set_callback(iq, jabber_unregistration_result_cb, cbdata->who);
+
+					jabber_iq_send(iq);
+					g_free(cbdata);
+					return;
+				}
+			} else {
+				const char *ids[] = {"username", "password", "name", "email", "nick", "first",
+					"last", "address", "city", "state", "zip", "phone", "url", "date",
+					NULL};
+				const char *value = purple_request_field_string_get_value(field);
+				int i;
+				for (i = 0; ids[i]; i++) {
+					if (!strcmp(id, ids[i]))
+						break;
+				}
+
+				if (!ids[i])
+					continue;
+				y = xmlnode_new_child(query, ids[i]);
+				xmlnode_insert_data(y, value, -1);
+				if(cbdata->js->registration && !strcmp(id, "username")) {
+					g_free(cbdata->js->user->node);
+					cbdata->js->user->node = g_strdup(value);
+				}
+				if(cbdata->js->registration && !strcmp(id, "password"))
+					purple_account_set_password(cbdata->js->gc->account, value);
+			}
+		}
+	}
+
+	if(cbdata->js->registration) {
+		username = g_strdup_printf("%s@%s%s%s", cbdata->js->user->node, cbdata->js->user->domain,
+			cbdata->js->user->resource ? "/" : "",
+			cbdata->js->user->resource ? cbdata->js->user->resource : "");
+		purple_account_set_username(cbdata->js->gc->account, username);
+		g_free(username);
+	}
+
+	jabber_iq_set_callback(iq, jabber_registration_result_cb, cbdata->who);
+
+	jabber_iq_send(iq);
+	g_free(cbdata);
+}
+
+static void
+jabber_register_cancel_cb(JabberRegisterCBData *cbdata, PurpleRequestFields *fields)
+{
+	PurpleAccount *account = purple_connection_get_account(cbdata->js->gc);
+	if(account && cbdata->js->registration) {
+		if(account->registration_cb)
+			(account->registration_cb)(account, FALSE, account->registration_cb_user_data);
+		jabber_connection_schedule_close(cbdata->js);
+	}
+	g_free(cbdata->who);
+	g_free(cbdata);
+}
+
+static void jabber_register_x_data_cb(JabberStream *js, xmlnode *result, gpointer data)
+{
+	xmlnode *query;
+	JabberIq *iq;
+	char *to = data;
+
+	iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:register");
+	query = xmlnode_get_child(iq->node, "query");
+	if (to)
+		xmlnode_set_attrib(iq->node,"to",to);
+
+	xmlnode_insert_child(query, result);
+
+	jabber_iq_set_callback(iq, jabber_registration_result_cb, to);
+	jabber_iq_send(iq);
+}
+
+static const struct {
+	const char *name;
+	const char *label;
+} registration_fields[] = {
+	{ "email",   N_("Email") },
+	{ "nick",    N_("Nickname") },
+	{ "first",   N_("First name") },
+	{ "last",    N_("Last name") },
+	{ "address", N_("Address") },
+	{ "city",    N_("City") },
+	{ "state",   N_("State") },
+	{ "zip",     N_("Postal code") },
+	{ "phone",   N_("Phone") },
+	{ "url",     N_("URL") },
+	{ "date",    N_("Date") },
+	{ NULL, NULL }
+};
+
+void jabber_register_parse(JabberStream *js, const char *from, JabberIqType type,
+                           const char *id, xmlnode *query)
+{
+	PurpleAccount *account = purple_connection_get_account(js->gc);
+	PurpleRequestFields *fields;
+	PurpleRequestFieldGroup *group;
+	PurpleRequestField *field;
+	xmlnode *x, *y, *node;
+	char *instructions;
+	JabberRegisterCBData *cbdata;
+	gboolean registered = FALSE;
+	int i;
+
+	if (type != JABBER_IQ_RESULT)
+		return;
+
+	if(js->registration) {
+		/* get rid of the login thingy */
+		purple_connection_set_state(js->gc, PURPLE_CONNECTED);
+	}
+
+	if(xmlnode_get_child(query, "registered")) {
+		registered = TRUE;
+
+		if(js->registration) {
+			purple_notify_error(NULL, _("Already Registered"),
+								_("Already Registered"), NULL);
+			if(account->registration_cb)
+				(account->registration_cb)(account, FALSE, account->registration_cb_user_data);
+			jabber_connection_schedule_close(js);
+			return;
+		}
+	}
+
+	if((x = xmlnode_get_child_with_namespace(query, "x", "jabber:x:data"))) {
+		jabber_x_data_request(js, x, jabber_register_x_data_cb, g_strdup(from));
+		return;
+
+	} else if((x = xmlnode_get_child_with_namespace(query, "x", NS_OOB_X_DATA))) {
+		xmlnode *url;
+
+		if((url = xmlnode_get_child(x, "url"))) {
+			char *href;
+			if((href = xmlnode_get_data(url))) {
+				purple_notify_uri(NULL, href);
+				g_free(href);
+
+				if(js->registration) {
+					js->gc->wants_to_die = TRUE;
+					if(account->registration_cb) /* succeeded, but we have no login info */
+						(account->registration_cb)(account, TRUE, account->registration_cb_user_data);
+					jabber_connection_schedule_close(js);
+				}
+				return;
+			}
+		}
+	}
+
+	/* as a last resort, use the old jabber:iq:register syntax */
+
+	fields = purple_request_fields_new();
+	group = purple_request_field_group_new(NULL);
+	purple_request_fields_add_group(fields, group);
+
+	if((node = xmlnode_get_child(query, "username"))) {
+		char *data = xmlnode_get_data(node);
+		if(js->registration)
+			field = purple_request_field_string_new("username", _("Username"), data ? data : js->user->node, FALSE);
+		else
+			field = purple_request_field_string_new("username", _("Username"), data, FALSE);
+
+		purple_request_field_group_add_field(group, field);
+		g_free(data);
+	}
+	if((node = xmlnode_get_child(query, "password"))) {
+		if(js->registration)
+			field = purple_request_field_string_new("password", _("Password"),
+										purple_connection_get_password(js->gc), FALSE);
+		else {
+			char *data = xmlnode_get_data(node);
+			field = purple_request_field_string_new("password", _("Password"), data, FALSE);
+			g_free(data);
+		}
+
+		purple_request_field_string_set_masked(field, TRUE);
+		purple_request_field_group_add_field(group, field);
+	}
+
+	if((node = xmlnode_get_child(query, "name"))) {
+		if(js->registration)
+			field = purple_request_field_string_new("name", _("Name"),
+													purple_account_get_alias(js->gc->account), FALSE);
+		else {
+			char *data = xmlnode_get_data(node);
+			field = purple_request_field_string_new("name", _("Name"), data, FALSE);
+			g_free(data);
+		}
+		purple_request_field_group_add_field(group, field);
+	}
+
+	for (i = 0; registration_fields[i].name != NULL; ++i) {
+		if ((node = xmlnode_get_child(query, registration_fields[i].name))) {
+			char *data = xmlnode_get_data(node);
+			field = purple_request_field_string_new(registration_fields[i].name,
+			                                        _(registration_fields[i].label),
+			                                        data, FALSE);
+			purple_request_field_group_add_field(group, field);
+			g_free(data);
+		}
+	}
+
+	if(registered) {
+		field = purple_request_field_bool_new("unregister", _("Unregister"), FALSE);
+		purple_request_field_group_add_field(group, field);
+	}
+
+	if((y = xmlnode_get_child(query, "instructions")))
+		instructions = xmlnode_get_data(y);
+	else if(registered)
+		instructions = g_strdup(_("Please fill out the information below "
+					"to change your account registration."));
+	else
+		instructions = g_strdup(_("Please fill out the information below "
+					"to register your new account."));
+
+	cbdata = g_new0(JabberRegisterCBData, 1);
+	cbdata->js = js;
+	cbdata->who = g_strdup(from);
+
+	if(js->registration)
+		purple_request_fields(js->gc, _("Register New XMPP Account"),
+				_("Register New XMPP Account"), instructions, fields,
+				_("Register"), G_CALLBACK(jabber_register_cb),
+				_("Cancel"), G_CALLBACK(jabber_register_cancel_cb),
+				purple_connection_get_account(js->gc), NULL, NULL,
+				cbdata);
+	else {
+		char *title;
+		g_return_if_fail(from != NULL);
+		title = registered ? g_strdup_printf(_("Change Account Registration at %s"), from)
+								:g_strdup_printf(_("Register New Account at %s"), from);
+		purple_request_fields(js->gc, title,
+			  title, instructions, fields,
+			  (registered ? _("Change Registration") : _("Register")), G_CALLBACK(jabber_register_cb),
+			  _("Cancel"), G_CALLBACK(jabber_register_cancel_cb),
+			  purple_connection_get_account(js->gc), NULL, NULL,
+			  cbdata);
+		g_free(title);
+	}
+
+	g_free(instructions);
+}
+
+void jabber_register_start(JabberStream *js)
+{
+	JabberIq *iq;
+
+	iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:register");
+	jabber_iq_send(iq);
+}
+
+void jabber_register_gateway(JabberStream *js, const char *gateway) {
+	JabberIq *iq;
+
+	iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:register");
+	xmlnode_set_attrib(iq->node, "to", gateway);
+	jabber_iq_send(iq);
+}
+
+void jabber_register_account(PurpleAccount *account)
+{
+	JabberStream *js;
+
+	js = jabber_stream_new(account);
+	if (js == NULL)
+		return;
+
+	js->registration = TRUE;
+	jabber_stream_connect(js);
+}
+
+static void
+jabber_unregister_account_iq_cb(JabberStream *js, const char *from,
+                                JabberIqType type, const char *id,
+                                xmlnode *packet, gpointer data)
+{
+	PurpleAccount *account = purple_connection_get_account(js->gc);
+
+	if (type == JABBER_IQ_ERROR) {
+		char *msg = jabber_parse_error(js, packet, NULL);
+
+		purple_notify_error(js->gc, _("Error unregistering account"),
+							_("Error unregistering account"), msg);
+		g_free(msg);
+		if(js->unregistration_cb)
+			js->unregistration_cb(account, FALSE, js->unregistration_user_data);
+	} else {
+		purple_notify_info(js->gc, _("Account successfully unregistered"),
+						   _("Account successfully unregistered"), NULL);
+		if(js->unregistration_cb)
+			js->unregistration_cb(account, TRUE, js->unregistration_user_data);
+	}
+}
+
+static void jabber_unregister_account_cb(JabberStream *js) {
+	JabberIq *iq;
+	xmlnode *query;
+
+	g_return_if_fail(js->unregistration);
+
+	iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:register");
+
+	query = xmlnode_get_child_with_namespace(iq->node, "query", "jabber:iq:register");
+
+	xmlnode_new_child(query, "remove");
+	xmlnode_set_attrib(iq->node, "to", js->user->domain);
+
+	jabber_iq_set_callback(iq, jabber_unregister_account_iq_cb, NULL);
+	jabber_iq_send(iq);
+}
+
+void jabber_unregister_account(PurpleAccount *account, PurpleAccountUnregistrationCb cb, void *user_data) {
+	PurpleConnection *gc = purple_account_get_connection(account);
+	JabberStream *js;
+
+	if(gc->state != PURPLE_CONNECTED) {
+		if(gc->state != PURPLE_CONNECTING)
+			jabber_login(account);
+		js = gc->proto_data;
+		js->unregistration = TRUE;
+		js->unregistration_cb = cb;
+		js->unregistration_user_data = user_data;
+		return;
+	}
+
+	js = gc->proto_data;
+
+	if (js->unregistration) {
+		purple_debug_error("jabber", "Unregistration in process; ignoring duplicate request.\n");
+		return;
+	}
+
+	js->unregistration = TRUE;
+	js->unregistration_cb = cb;
+	js->unregistration_user_data = user_data;
+
+	jabber_unregister_account_cb(js);
+}
+
+/* TODO: As Will pointed out in IRC, after being notified by the core to
+ * shutdown, we should async. wait for the server to send us the stream
+ * termination before destorying everything. That seems like it would require
+ * changing the semantics of prpl->close(), so it's a good idea for 3.0.0.
+ */
+void jabber_close(PurpleConnection *gc)
+{
+	JabberStream *js = purple_connection_get_protocol_data(gc);
+
+	/* Close all of the open Jingle sessions on this stream */
+	jingle_terminate_sessions(js);
+
+	if (js->bosh)
+		jabber_bosh_connection_close(js->bosh);
+	else if ((js->gsc && js->gsc->fd > 0) || js->fd > 0)
+		jabber_send_raw(js, "</stream:stream>", -1);
+
+	if (js->srv_query_data)
+		purple_srv_cancel(js->srv_query_data);
+
+	if(js->gsc) {
+		purple_ssl_close(js->gsc);
+	} else if (js->fd > 0) {
+		if(js->gc->inpa)
+			purple_input_remove(js->gc->inpa);
+		close(js->fd);
+	}
+
+	if (js->bosh)
+		jabber_bosh_connection_destroy(js->bosh);
+
+	jabber_buddy_remove_all_pending_buddy_info_requests(js);
+
+	jabber_parser_free(js);
+
+	if(js->iq_callbacks)
+		g_hash_table_destroy(js->iq_callbacks);
+	if(js->buddies)
+		g_hash_table_destroy(js->buddies);
+	if(js->chats)
+		g_hash_table_destroy(js->chats);
+
+	while(js->chat_servers) {
+		g_free(js->chat_servers->data);
+		js->chat_servers = g_list_delete_link(js->chat_servers, js->chat_servers);
+	}
+
+	while(js->user_directories) {
+		g_free(js->user_directories->data);
+		js->user_directories = g_list_delete_link(js->user_directories, js->user_directories);
+	}
+
+	while(js->bs_proxies) {
+		JabberBytestreamsStreamhost *sh = js->bs_proxies->data;
+		g_free(sh->jid);
+		g_free(sh->host);
+		g_free(sh->zeroconf);
+		g_free(sh);
+		js->bs_proxies = g_list_delete_link(js->bs_proxies, js->bs_proxies);
+	}
+
+	while(js->url_datas) {
+		purple_util_fetch_url_cancel(js->url_datas->data);
+		js->url_datas = g_slist_delete_link(js->url_datas, js->url_datas);
+	}
+
+	g_free(js->stream_id);
+	if(js->user)
+		jabber_id_free(js->user);
+	g_free(js->initial_avatar_hash);
+	g_free(js->avatar_hash);
+	g_free(js->caps_hash);
+
+	purple_circ_buffer_destroy(js->write_buffer);
+	if(js->writeh)
+		purple_input_remove(js->writeh);
+	if (js->auth_mech && js->auth_mech->dispose)
+		js->auth_mech->dispose(js);
+#ifdef HAVE_CYRUS_SASL
+	if(js->sasl)
+		sasl_dispose(&js->sasl);
+	if(js->sasl_mechs)
+		g_string_free(js->sasl_mechs, TRUE);
+	g_free(js->sasl_cb);
+#endif
+	g_free(js->serverFQDN);
+	while(js->commands) {
+		JabberAdHocCommands *cmd = js->commands->data;
+		g_free(cmd->jid);
+		g_free(cmd->node);
+		g_free(cmd->name);
+		g_free(cmd);
+		js->commands = g_list_delete_link(js->commands, js->commands);
+	}
+	g_free(js->server_name);
+	g_free(js->certificate_CN);
+	g_free(js->gmail_last_time);
+	g_free(js->gmail_last_tid);
+	g_free(js->old_msg);
+	g_free(js->old_avatarhash);
+	g_free(js->old_artist);
+	g_free(js->old_title);
+	g_free(js->old_source);
+	g_free(js->old_uri);
+	g_free(js->old_track);
+	g_free(js->expected_rspauth);
+
+	if (js->vcard_timer != 0)
+		purple_timeout_remove(js->vcard_timer);
+
+	if (js->keepalive_timeout != 0)
+		purple_timeout_remove(js->keepalive_timeout);
+
+	g_free(js->srv_rec);
+	js->srv_rec = NULL;
+
+	g_free(js->stun_ip);
+	js->stun_ip = NULL;
+
+	/* cancel DNS query for STUN, if one is ongoing */
+	if (js->stun_query) {
+		purple_dnsquery_destroy(js->stun_query);
+		js->stun_query = NULL;
+	}
+
+	g_free(js);
+
+	gc->proto_data = NULL;
+}
+
+void jabber_stream_set_state(JabberStream *js, JabberStreamState state)
+{
+#define JABBER_CONNECT_STEPS ((js->gsc || js->state == JABBER_STREAM_INITIALIZING_ENCRYPTION) ? 9 : 5)
+
+	js->state = state;
+	switch(state) {
+		case JABBER_STREAM_OFFLINE:
+			break;
+		case JABBER_STREAM_CONNECTING:
+			purple_connection_update_progress(js->gc, _("Connecting"), 1,
+					JABBER_CONNECT_STEPS);
+			break;
+		case JABBER_STREAM_INITIALIZING:
+			purple_connection_update_progress(js->gc, _("Initializing Stream"),
+					js->gsc ? 5 : 2, JABBER_CONNECT_STEPS);
+			jabber_stream_init(js);
+			break;
+		case JABBER_STREAM_INITIALIZING_ENCRYPTION:
+			purple_connection_update_progress(js->gc, _("Initializing SSL/TLS"),
+											  6, JABBER_CONNECT_STEPS);
+			break;
+		case JABBER_STREAM_AUTHENTICATING:
+			purple_connection_update_progress(js->gc, _("Authenticating"),
+					js->gsc ? 7 : 3, JABBER_CONNECT_STEPS);
+			break;
+		case JABBER_STREAM_POST_AUTH:
+			purple_connection_update_progress(js->gc, _("Re-initializing Stream"),
+					(js->gsc ? 8 : 4), JABBER_CONNECT_STEPS);
+
+			break;
+		case JABBER_STREAM_CONNECTED:
+			/* Send initial presence */
+			jabber_presence_send(js, TRUE);
+			purple_connection_set_state(js->gc, PURPLE_CONNECTED);
+			break;
+	}
+
+#undef JABBER_CONNECT_STEPS
+}
+
+char *jabber_get_next_id(JabberStream *js)
+{
+	return g_strdup_printf("purple%x", js->next_id++);
+}
+
+
+void jabber_idle_set(PurpleConnection *gc, int idle)
+{
+	JabberStream *js = gc->proto_data;
+
+	js->idle = idle ? time(NULL) - idle : idle;
+
+	/* send out an updated prescence */
+	purple_debug_info("jabber", "sending updated presence for idle\n");
+	jabber_presence_send(js, FALSE);
+}
+
+void jabber_blocklist_parse_push(JabberStream *js, const char *from,
+                                 JabberIqType type, const char *id,
+                                 xmlnode *child)
+{
+	JabberIq *result;
+	xmlnode *item;
+	PurpleAccount *account;
+	gboolean is_block;
+
+	if (!jabber_is_own_account(js, from)) {
+		xmlnode *error, *x;
+		result = jabber_iq_new(js, JABBER_IQ_ERROR);
+		xmlnode_set_attrib(result->node, "id", id);
+		if (from)
+			xmlnode_set_attrib(result->node, "to", from);
+
+		error = xmlnode_new_child(result->node, "error");
+		xmlnode_set_attrib(error, "type", "cancel");
+		x = xmlnode_new_child(error, "not-allowed");
+		xmlnode_set_namespace(x, NS_XMPP_STANZAS);
+
+		jabber_iq_send(result);
+		return;
+	}
+
+	account = purple_connection_get_account(js->gc);
+	is_block = g_str_equal(child->name, "block");
+
+	item = xmlnode_get_child(child, "item");
+	if (!is_block && item == NULL) {
+		/* Unblock everyone */
+		purple_debug_info("jabber", "Received unblock push. Unblocking everyone.\n");
+
+		while (account->deny != NULL) {
+			purple_privacy_deny_remove(account, account->deny->data, TRUE);
+		}
+	} else if (item == NULL) {
+		/* An empty <block/> is bogus */
+		xmlnode *error, *x;
+		result = jabber_iq_new(js, JABBER_IQ_ERROR);
+		xmlnode_set_attrib(result->node, "id", id);
+
+		error = xmlnode_new_child(result->node, "error");
+		xmlnode_set_attrib(error, "type", "modify");
+		x = xmlnode_new_child(error, "bad-request");
+		xmlnode_set_namespace(x, NS_XMPP_STANZAS);
+
+		jabber_iq_send(result);
+		return;
+	} else {
+		for ( ; item; item = xmlnode_get_next_twin(item)) {
+			const char *jid = xmlnode_get_attrib(item, "jid");
+			if (jid == NULL || *jid == '\0')
+				continue;
+
+			if (is_block)
+				purple_privacy_deny_add(account, jid, TRUE);
+			else
+				purple_privacy_deny_remove(account, jid, TRUE);
+		}
+	}
+
+	result = jabber_iq_new(js, JABBER_IQ_RESULT);
+	xmlnode_set_attrib(result->node, "id", id);
+	jabber_iq_send(result);
+}
+
+static void jabber_blocklist_parse(JabberStream *js, const char *from,
+                                   JabberIqType type, const char *id,
+                                   xmlnode *packet, gpointer data)
+{
+	xmlnode *blocklist, *item;
+	PurpleAccount *account;
+
+	blocklist = xmlnode_get_child_with_namespace(packet,
+			"blocklist", NS_SIMPLE_BLOCKING);
+	account = purple_connection_get_account(js->gc);
+
+	if (type == JABBER_IQ_ERROR || blocklist == NULL)
+		return;
+
+	/* This is the only privacy method supported by XEP-0191 */
+	if (account->perm_deny != PURPLE_PRIVACY_DENY_USERS)
+		account->perm_deny = PURPLE_PRIVACY_DENY_USERS;
+
+	/*
+	 * TODO: When account->deny is something more than a hash table, this can
+	 * be re-written to find the set intersection and difference.
+	 */
+	while (account->deny)
+		purple_privacy_deny_remove(account, account->deny->data, TRUE);
+
+	item = xmlnode_get_child(blocklist, "item");
+	while (item != NULL) {
+		const char *jid = xmlnode_get_attrib(item, "jid");
+		purple_privacy_deny_add(account, jid, TRUE);
+		item = xmlnode_get_next_twin(item);
+	}
+}
+
+void jabber_request_block_list(JabberStream *js)
+{
+	JabberIq *iq;
+	xmlnode *blocklist;
+
+	iq = jabber_iq_new(js, JABBER_IQ_GET);
+
+	blocklist = xmlnode_new_child(iq->node, "blocklist");
+	xmlnode_set_namespace(blocklist, NS_SIMPLE_BLOCKING);
+
+	jabber_iq_set_callback(iq, jabber_blocklist_parse, NULL);
+
+	jabber_iq_send(iq);
+}
+
+void jabber_add_deny(PurpleConnection *gc, const char *who)
+{
+	JabberStream *js;
+	JabberIq *iq;
+	xmlnode *block, *item;
+
+	g_return_if_fail(who != NULL && *who != '\0');
+
+	js = purple_connection_get_protocol_data(gc);
+	if (js == NULL)
+		return;
+
+	if (js->server_caps & JABBER_CAP_GOOGLE_ROSTER)
+	{
+		jabber_google_roster_add_deny(js, who);
+		return;
+	}
+
+	if (!(js->server_caps & JABBER_CAP_BLOCKING))
+	{
+		purple_notify_error(NULL, _("Server doesn't support blocking"),
+							_("Server doesn't support blocking"), NULL);
+		return;
+	}
+
+	iq = jabber_iq_new(js, JABBER_IQ_SET);
+
+	block = xmlnode_new_child(iq->node, "block");
+	xmlnode_set_namespace(block, NS_SIMPLE_BLOCKING);
+
+	item = xmlnode_new_child(block, "item");
+	xmlnode_set_attrib(item, "jid", who);
+
+	jabber_iq_send(iq);
+}
+
+void jabber_rem_deny(PurpleConnection *gc, const char *who)
+{
+	JabberStream *js;
+	JabberIq *iq;
+	xmlnode *unblock, *item;
+
+	g_return_if_fail(who != NULL && *who != '\0');
+
+	js = purple_connection_get_protocol_data(gc);
+	if (js == NULL)
+		return;
+
+	if (js->server_caps & JABBER_CAP_GOOGLE_ROSTER)
+	{
+		jabber_google_roster_rem_deny(js, who);
+		return;
+	}
+
+	if (!(js->server_caps & JABBER_CAP_BLOCKING))
+		return;
+
+	iq = jabber_iq_new(js, JABBER_IQ_SET);
+
+	unblock = xmlnode_new_child(iq->node, "unblock");
+	xmlnode_set_namespace(unblock, NS_SIMPLE_BLOCKING);
+
+	item = xmlnode_new_child(unblock, "item");
+	xmlnode_set_attrib(item, "jid", who);
+
+	jabber_iq_send(iq);
+}
+
+void jabber_add_feature(const char *namespace, JabberFeatureEnabled cb) {
+	JabberFeature *feat;
+
+	g_return_if_fail(namespace != NULL);
+
+	feat = g_new0(JabberFeature,1);
+	feat->namespace = g_strdup(namespace);
+	feat->is_enabled = cb;
+
+	/* try to remove just in case it already exists in the list */
+	jabber_remove_feature(namespace);
+
+	jabber_features = g_list_append(jabber_features, feat);
+}
+
+void jabber_remove_feature(const char *namespace) {
+	GList *feature;
+	for(feature = jabber_features; feature; feature = feature->next) {
+		JabberFeature *feat = (JabberFeature*)feature->data;
+		if(!strcmp(feat->namespace, namespace)) {
+			g_free(feat->namespace);
+			g_free(feature->data);
+			jabber_features = g_list_delete_link(jabber_features, feature);
+			break;
+		}
+	}
+}
+
+static void jabber_features_destroy(void)
+{
+	while (jabber_features) {
+		JabberFeature *feature = jabber_features->data;
+		g_free(feature->namespace);
+		g_free(feature);
+		jabber_features = g_list_delete_link(jabber_features, jabber_features);
+	}
+}
+
+void jabber_add_identity(const gchar *category, const gchar *type, const gchar *lang, const gchar *name) {
+	GList *identity;
+	JabberIdentity *ident;
+	/* both required according to XEP-0030 */
+	g_return_if_fail(category != NULL);
+	g_return_if_fail(type != NULL);
+
+	for(identity = jabber_identities; identity; identity = identity->next) {
+		JabberIdentity *ident = (JabberIdentity*)identity->data;
+		if (!strcmp(ident->category, category) &&
+		    !strcmp(ident->type, type) &&
+		    ((!ident->lang && !lang) || (ident->lang && lang && !strcmp(ident->lang, lang)))) {
+			return;
+		}
+	}
+
+	ident = g_new0(JabberIdentity, 1);
+	ident->category = g_strdup(category);
+	ident->type = g_strdup(type);
+	ident->lang = g_strdup(lang);
+	ident->name = g_strdup(name);
+	jabber_identities = g_list_prepend(jabber_identities, ident);
+}
+
+static void jabber_identities_destroy(void)
+{
+	while (jabber_identities) {
+		JabberIdentity *id = jabber_identities->data;
+		g_free(id->category);
+		g_free(id->type);
+		g_free(id->lang);
+		g_free(id->name);
+		g_free(id);
+		jabber_identities = g_list_delete_link(jabber_identities, jabber_identities);
+	}
+}
+
+gboolean jabber_stream_is_ssl(JabberStream *js)
+{
+	return (js->bosh && jabber_bosh_connection_is_ssl(js->bosh)) ||
+	       (!js->bosh && js->gsc);
+}
+
+const char *jabber_list_icon(PurpleAccount *a, PurpleBuddy *b)
+{
+	return "jabber";
+}
+
+const char* jabber_list_emblem(PurpleBuddy *b)
+{
+	JabberStream *js;
+	JabberBuddy *jb = NULL;
+	PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(b));
+
+	if(!gc)
+		return NULL;
+
+	js = gc->proto_data;
+	if(js)
+		jb = jabber_buddy_find(js, purple_buddy_get_name(b), FALSE);
+
+	if(!PURPLE_BUDDY_IS_ONLINE(b)) {
+		if(jb && (jb->subscription & JABBER_SUB_PENDING ||
+					!(jb->subscription & JABBER_SUB_TO)))
+			return "not-authorized";
+	}
+
+	if (jb) {
+		JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, NULL);
+		if (jbr) {
+			const gchar *client_type = 
+				jabber_resource_get_identity_category_type(jbr, "client");
+		
+			if (client_type) {
+				if (strcmp(client_type, "phone") == 0) {
+					return "mobile";
+				} else if (strcmp(client_type, "web") == 0) {
+					return "external";
+				} else if (strcmp(client_type, "handheld") == 0) {
+					return "hiptop";
+				} else if (strcmp(client_type, "bot") == 0) {
+					return "bot";
+				}
+				/* the default value "pc" falls through and has no emblem */
+			}
+		}
+	}
+		
+	return NULL;
+}
+
+char *jabber_status_text(PurpleBuddy *b)
+{
+	char *ret = NULL;
+	JabberBuddy *jb = NULL;
+	PurpleAccount *account = purple_buddy_get_account(b);
+	PurpleConnection *gc = purple_account_get_connection(account);
+
+	if (gc && gc->proto_data)
+		jb = jabber_buddy_find(gc->proto_data, purple_buddy_get_name(b), FALSE);
+
+	if(jb && !PURPLE_BUDDY_IS_ONLINE(b) && (jb->subscription & JABBER_SUB_PENDING || !(jb->subscription & JABBER_SUB_TO))) {
+		ret = g_strdup(_("Not Authorized"));
+	} else if(jb && !PURPLE_BUDDY_IS_ONLINE(b) && jb->error_msg) {
+		ret = g_strdup(jb->error_msg);
+	} else {
+		PurplePresence *presence = purple_buddy_get_presence(b);
+		PurpleStatus *status = purple_presence_get_active_status(presence);
+		const char *message;
+
+		if((message = purple_status_get_attr_string(status, "message"))) {
+			ret = g_markup_escape_text(message, -1);
+		} else if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_TUNE)) {
+			PurpleStatus *status = purple_presence_get_status(presence, "tune");
+			const char *title = purple_status_get_attr_string(status, PURPLE_TUNE_TITLE);
+			const char *artist = purple_status_get_attr_string(status, PURPLE_TUNE_ARTIST);
+			const char *album = purple_status_get_attr_string(status, PURPLE_TUNE_ALBUM);
+			ret = purple_util_format_song_info(title, artist, album, NULL);
+		}
+	}
+
+	return ret;
+}
+
+static void
+jabber_tooltip_add_resource_text(JabberBuddyResource *jbr,
+	PurpleNotifyUserInfo *user_info, gboolean multiple_resources)
+{
+	char *text = NULL;
+	char *res = NULL;
+	char *label, *value;
+	const char *state;
+
+	if(jbr->status) {
+		text = g_markup_escape_text(jbr->status, -1);
+	}
+
+	if(jbr->name)
+		res = g_strdup_printf(" (%s)", jbr->name);
+
+	state = jabber_buddy_state_get_name(jbr->state);
+	if (text != NULL && !purple_utf8_strcasecmp(state, text)) {
+		g_free(text);
+		text = NULL;
+	}
+
+	label = g_strdup_printf("%s%s", _("Status"), (res ? res : ""));
+	value = g_strdup_printf("%s%s%s", state, (text ? ": " : ""), (text ? text : ""));
+
+	purple_notify_user_info_add_pair(user_info, label, value);
+	g_free(label);
+	g_free(value);
+	g_free(text);
+
+	/* if the resource is idle, show that */
+	/* only show it if there is more than one resource available for
+	the buddy, since the "general" idleness will be shown anyway,
+	this way we can see see the idleness of lower-priority resources */
+	if (jbr->idle && multiple_resources) {
+		gchar *idle_str =
+			purple_str_seconds_to_string(time(NULL) - jbr->idle);
+		label = g_strdup_printf("%s%s", _("Idle"), (res ? res : ""));
+		purple_notify_user_info_add_pair(user_info, label, idle_str);
+		g_free(idle_str);
+		g_free(label);
+	}
+	g_free(res);
+}
+
+void jabber_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full)
+{
+	JabberBuddy *jb;
+	PurpleAccount *account;
+	PurpleConnection *gc;
+
+	g_return_if_fail(b != NULL);
+
+	account = purple_buddy_get_account(b);
+	g_return_if_fail(account != NULL);
+
+	gc = purple_account_get_connection(account);
+	g_return_if_fail(gc != NULL);
+	g_return_if_fail(gc->proto_data != NULL);
+
+	jb = jabber_buddy_find(gc->proto_data, purple_buddy_get_name(b), FALSE);
+
+	if(jb) {
+		JabberBuddyResource *jbr = NULL;
+		PurplePresence *presence = purple_buddy_get_presence(b);
+		const char *sub;
+		GList *l;
+		const char *mood;
+		gboolean multiple_resources =
+			jb->resources && g_list_next(jb->resources);
+		JabberBuddyResource *top_jbr = jabber_buddy_find_resource(jb, NULL);
+
+		/* resource-specific info for the top resource */
+		if (top_jbr) {
+			jabber_tooltip_add_resource_text(top_jbr, user_info,
+				multiple_resources);
+		}
+
+		for(l=jb->resources; l; l = l->next) {
+			jbr = l->data;
+			/* the remaining resources */
+			if (jbr != top_jbr) {
+				jabber_tooltip_add_resource_text(jbr, user_info,
+					multiple_resources);
+			}
+		}
+
+		if (full) {
+			PurpleStatus *status;
+
+			status = purple_presence_get_active_status(presence);
+			mood = purple_status_get_attr_string(status, "mood");
+			if(mood != NULL) {
+				const char *moodtext;
+				moodtext = purple_status_get_attr_string(status, "moodtext");
+				if(moodtext != NULL) {
+					char *moodplustext = g_strdup_printf("%s (%s)", mood, moodtext);
+
+					purple_notify_user_info_add_pair(user_info, _("Mood"), moodplustext);
+					g_free(moodplustext);
+				} else
+					purple_notify_user_info_add_pair(user_info, _("Mood"), mood);
+			}
+			if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_TUNE)) {
+				PurpleStatus *tune = purple_presence_get_status(presence, "tune");
+				const char *title = purple_status_get_attr_string(tune, PURPLE_TUNE_TITLE);
+				const char *artist = purple_status_get_attr_string(tune, PURPLE_TUNE_ARTIST);
+				const char *album = purple_status_get_attr_string(tune, PURPLE_TUNE_ALBUM);
+				char *playing = purple_util_format_song_info(title, artist, album, NULL);
+				if (playing) {
+					purple_notify_user_info_add_pair(user_info, _("Now Listening"), playing);
+					g_free(playing);
+				}
+			}
+
+			if(jb->subscription & JABBER_SUB_FROM) {
+				if(jb->subscription & JABBER_SUB_TO)
+					sub = _("Both");
+				else if(jb->subscription & JABBER_SUB_PENDING)
+					sub = _("From (To pending)");
+				else
+					sub = _("From");
+			} else {
+				if(jb->subscription & JABBER_SUB_TO)
+					sub = _("To");
+				else if(jb->subscription & JABBER_SUB_PENDING)
+					sub = _("None (To pending)");
+				else
+					sub = _("None");
+			}
+
+			purple_notify_user_info_add_pair(user_info, _("Subscription"), sub);
+
+		}
+
+		if(!PURPLE_BUDDY_IS_ONLINE(b) && jb->error_msg) {
+			purple_notify_user_info_add_pair(user_info, _("Error"), jb->error_msg);
+		}
+	}
+}
+
+GList *jabber_status_types(PurpleAccount *account)
+{
+	PurpleStatusType *type;
+	GList *types = NULL;
+	PurpleValue *priority_value;
+	PurpleValue *buzz_enabled;
+
+	priority_value = purple_value_new(PURPLE_TYPE_INT);
+	purple_value_set_int(priority_value, 1);
+	buzz_enabled = purple_value_new(PURPLE_TYPE_BOOLEAN);
+	purple_value_set_boolean(buzz_enabled, TRUE);
+	type = purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE,
+			jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_ONLINE),
+			NULL, TRUE, TRUE, FALSE,
+			"priority", _("Priority"), priority_value,
+			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
+			"mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
+			"moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
+			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
+			"buzz", _("Allow Buzz"), buzz_enabled,
+			NULL);
+	types = g_list_append(types, type);
+
+	priority_value = purple_value_new(PURPLE_TYPE_INT);
+	purple_value_set_int(priority_value, 1);
+	buzz_enabled = purple_value_new(PURPLE_TYPE_BOOLEAN);
+	purple_value_set_boolean(buzz_enabled, TRUE);
+	type = purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE,
+			jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_CHAT),
+			_("Chatty"), TRUE, TRUE, FALSE,
+			"priority", _("Priority"), priority_value,
+			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
+			"mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
+			"moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
+			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
+			"buzz", _("Allow Buzz"), buzz_enabled,
+			NULL);
+	types = g_list_append(types, type);
+
+	priority_value = purple_value_new(PURPLE_TYPE_INT);
+	purple_value_set_int(priority_value, 0);
+	buzz_enabled = purple_value_new(PURPLE_TYPE_BOOLEAN);
+	purple_value_set_boolean(buzz_enabled, TRUE);
+	type = purple_status_type_new_with_attrs(PURPLE_STATUS_AWAY,
+			jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_AWAY),
+			NULL, TRUE, TRUE, FALSE,
+			"priority", _("Priority"), priority_value,
+			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
+			"mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
+			"moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
+			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
+			"buzz", _("Allow Buzz"), buzz_enabled,
+			NULL);
+	types = g_list_append(types, type);
+
+	priority_value = purple_value_new(PURPLE_TYPE_INT);
+	purple_value_set_int(priority_value, 0);
+	buzz_enabled = purple_value_new(PURPLE_TYPE_BOOLEAN);
+	purple_value_set_boolean(buzz_enabled, TRUE);
+	type = purple_status_type_new_with_attrs(PURPLE_STATUS_EXTENDED_AWAY,
+			jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_XA),
+			NULL, TRUE, TRUE, FALSE,
+			"priority", _("Priority"), priority_value,
+			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
+			"mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
+			"moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
+			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
+			"buzz", _("Allow Buzz"), buzz_enabled,
+			NULL);
+	types = g_list_append(types, type);
+
+	priority_value = purple_value_new(PURPLE_TYPE_INT);
+	purple_value_set_int(priority_value, 0);
+	type = purple_status_type_new_with_attrs(PURPLE_STATUS_UNAVAILABLE,
+			jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_DND),
+			_("Do Not Disturb"), TRUE, TRUE, FALSE,
+			"priority", _("Priority"), priority_value,
+			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
+			"mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
+			"moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
+			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
+			NULL);
+	types = g_list_append(types, type);
+
+	/*
+	if(js->protocol_version == JABBER_PROTO_0_9)
+		m = g_list_append(m, _("Invisible"));
+	*/
+
+	type = purple_status_type_new_with_attrs(PURPLE_STATUS_OFFLINE,
+			jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_UNAVAILABLE),
+			NULL, TRUE, TRUE, FALSE,
+			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
+			NULL);
+	types = g_list_append(types, type);
+
+	type = purple_status_type_new_with_attrs(PURPLE_STATUS_TUNE,
+			"tune", NULL, FALSE, TRUE, TRUE,
+			PURPLE_TUNE_ARTIST, _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING),
+			PURPLE_TUNE_TITLE, _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING),
+			PURPLE_TUNE_ALBUM, _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING),
+			PURPLE_TUNE_GENRE, _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING),
+			PURPLE_TUNE_COMMENT, _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING),
+			PURPLE_TUNE_TRACK, _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING),
+			PURPLE_TUNE_TIME, _("Tune Time"), purple_value_new(PURPLE_TYPE_INT),
+			PURPLE_TUNE_YEAR, _("Tune Year"), purple_value_new(PURPLE_TYPE_INT),
+			PURPLE_TUNE_URL, _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING),
+			NULL);
+	types = g_list_append(types, type);
+
+	return types;
+}
+
+static void
+jabber_password_change_result_cb(JabberStream *js, const char *from,
+                                 JabberIqType type, const char *id,
+                                 xmlnode *packet, gpointer data)
+{
+	if (type == JABBER_IQ_RESULT) {
+		purple_notify_info(js->gc, _("Password Changed"), _("Password Changed"),
+				_("Your password has been changed."));
+
+		purple_account_set_password(js->gc->account, (char *)data);
+	} else {
+		char *msg = jabber_parse_error(js, packet, NULL);
+
+		purple_notify_error(js->gc, _("Error changing password"),
+				_("Error changing password"), msg);
+		g_free(msg);
+	}
+
+	g_free(data);
+}
+
+static void jabber_password_change_cb(JabberStream *js,
+		PurpleRequestFields *fields)
+{
+	const char *p1, *p2;
+	JabberIq *iq;
+	xmlnode *query, *y;
+
+	p1 = purple_request_fields_get_string(fields, "password1");
+	p2 = purple_request_fields_get_string(fields, "password2");
+
+	if(strcmp(p1, p2)) {
+		purple_notify_error(js->gc, NULL, _("New passwords do not match."), NULL);
+		return;
+	}
+
+	iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:register");
+
+	xmlnode_set_attrib(iq->node, "to", js->user->domain);
+
+	query = xmlnode_get_child(iq->node, "query");
+
+	y = xmlnode_new_child(query, "username");
+	xmlnode_insert_data(y, js->user->node, -1);
+	y = xmlnode_new_child(query, "password");
+	xmlnode_insert_data(y, p1, -1);
+
+	jabber_iq_set_callback(iq, jabber_password_change_result_cb, g_strdup(p1));
+
+	jabber_iq_send(iq);
+}
+
+static void jabber_password_change(PurplePluginAction *action)
+{
+
+	PurpleConnection *gc = (PurpleConnection *) action->context;
+	JabberStream *js = gc->proto_data;
+	PurpleRequestFields *fields;
+	PurpleRequestFieldGroup *group;
+	PurpleRequestField *field;
+
+	fields = purple_request_fields_new();
+	group = purple_request_field_group_new(NULL);
+	purple_request_fields_add_group(fields, group);
+
+	field = purple_request_field_string_new("password1", _("Password"),
+			"", FALSE);
+	purple_request_field_string_set_masked(field, TRUE);
+	purple_request_field_set_required(field, TRUE);
+	purple_request_field_group_add_field(group, field);
+
+	field = purple_request_field_string_new("password2", _("Password (again)"),
+			"", FALSE);
+	purple_request_field_string_set_masked(field, TRUE);
+	purple_request_field_set_required(field, TRUE);
+	purple_request_field_group_add_field(group, field);
+
+	purple_request_fields(js->gc, _("Change XMPP Password"),
+			_("Change XMPP Password"), _("Please enter your new password"),
+			fields, _("OK"), G_CALLBACK(jabber_password_change_cb),
+			_("Cancel"), NULL,
+			purple_connection_get_account(gc), NULL, NULL,
+			js);
+}
+
+GList *jabber_actions(PurplePlugin *plugin, gpointer context)
+{
+	PurpleConnection *gc = (PurpleConnection *) context;
+	JabberStream *js = gc->proto_data;
+	GList *m = NULL;
+	PurplePluginAction *act;
+
+	act = purple_plugin_action_new(_("Set User Info..."),
+	                             jabber_setup_set_info);
+	m = g_list_append(m, act);
+
+	/* if (js->protocol_options & CHANGE_PASSWORD) { */
+		act = purple_plugin_action_new(_("Change Password..."),
+		                             jabber_password_change);
+		m = g_list_append(m, act);
+	/* } */
+
+	act = purple_plugin_action_new(_("Search for Users..."),
+	                             jabber_user_search_begin);
+	m = g_list_append(m, act);
+
+	purple_debug_info("jabber", "jabber_actions: have pep: %s\n", js->pep?"YES":"NO");
+
+	if(js->pep)
+		jabber_pep_init_actions(&m);
+
+	if(js->commands)
+		jabber_adhoc_init_server_commands(js, &m);
+
+	return m;
+}
+
+PurpleChat *jabber_find_blist_chat(PurpleAccount *account, const char *name)
+{
+	PurpleBlistNode *gnode, *cnode;
+	JabberID *jid;
+
+	if(!(jid = jabber_id_new(name)))
+		return NULL;
+
+	for(gnode = purple_blist_get_root(); gnode;
+			gnode = purple_blist_node_get_sibling_next(gnode)) {
+		for(cnode = purple_blist_node_get_first_child(gnode);
+				cnode;
+				cnode = purple_blist_node_get_sibling_next(cnode)) {
+			PurpleChat *chat = (PurpleChat*)cnode;
+			const char *room, *server;
+			GHashTable *components;
+			if(!PURPLE_BLIST_NODE_IS_CHAT(cnode))
+				continue;
+
+			if (purple_chat_get_account(chat) != account)
+				continue;
+
+			components = purple_chat_get_components(chat);
+			if(!(room = g_hash_table_lookup(components, "room")))
+				continue;
+			if(!(server = g_hash_table_lookup(components, "server")))
+				continue;
+
+			if(jid->node && jid->domain &&
+					!g_utf8_collate(room, jid->node) && !g_utf8_collate(server, jid->domain)) {
+				jabber_id_free(jid);
+				return chat;
+			}
+		}
+	}
+	jabber_id_free(jid);
+	return NULL;
+}
+
+void jabber_convo_closed(PurpleConnection *gc, const char *who)
+{
+	JabberStream *js = gc->proto_data;
+	JabberID *jid;
+	JabberBuddy *jb;
+	JabberBuddyResource *jbr;
+
+	if(!(jid = jabber_id_new(who)))
+		return;
+
+	if((jb = jabber_buddy_find(js, who, TRUE)) &&
+			(jbr = jabber_buddy_find_resource(jb, jid->resource))) {
+		if(jbr->thread_id) {
+			g_free(jbr->thread_id);
+			jbr->thread_id = NULL;
+		}
+	}
+
+	jabber_id_free(jid);
+}
+
+
+char *jabber_parse_error(JabberStream *js,
+                         xmlnode *packet,
+                         PurpleConnectionError *reason)
+{
+	xmlnode *error;
+	const char *code = NULL, *text = NULL;
+	const char *xmlns = xmlnode_get_namespace(packet);
+	char *cdata = NULL;
+
+#define SET_REASON(x) \
+	if(reason != NULL) { *reason = x; }
+
+	if((error = xmlnode_get_child(packet, "error"))) {
+		cdata = xmlnode_get_data(error);
+		code = xmlnode_get_attrib(error, "code");
+
+		/* Stanza errors */
+		if(xmlnode_get_child(error, "bad-request")) {
+			text = _("Bad Request");
+		} else if(xmlnode_get_child(error, "conflict")) {
+			text = _("Conflict");
+		} else if(xmlnode_get_child(error, "feature-not-implemented")) {
+			text = _("Feature Not Implemented");
+		} else if(xmlnode_get_child(error, "forbidden")) {
+			text = _("Forbidden");
+		} else if(xmlnode_get_child(error, "gone")) {
+			text = _("Gone");
+		} else if(xmlnode_get_child(error, "internal-server-error")) {
+			text = _("Internal Server Error");
+		} else if(xmlnode_get_child(error, "item-not-found")) {
+			text = _("Item Not Found");
+		} else if(xmlnode_get_child(error, "jid-malformed")) {
+			text = _("Malformed XMPP ID");
+		} else if(xmlnode_get_child(error, "not-acceptable")) {
+			text = _("Not Acceptable");
+		} else if(xmlnode_get_child(error, "not-allowed")) {
+			text = _("Not Allowed");
+		} else if(xmlnode_get_child(error, "not-authorized")) {
+			text = _("Not Authorized");
+		} else if(xmlnode_get_child(error, "payment-required")) {
+			text = _("Payment Required");
+		} else if(xmlnode_get_child(error, "recipient-unavailable")) {
+			text = _("Recipient Unavailable");
+		} else if(xmlnode_get_child(error, "redirect")) {
+			/* XXX */
+		} else if(xmlnode_get_child(error, "registration-required")) {
+			text = _("Registration Required");
+		} else if(xmlnode_get_child(error, "remote-server-not-found")) {
+			text = _("Remote Server Not Found");
+		} else if(xmlnode_get_child(error, "remote-server-timeout")) {
+			text = _("Remote Server Timeout");
+		} else if(xmlnode_get_child(error, "resource-constraint")) {
+			text = _("Server Overloaded");
+		} else if(xmlnode_get_child(error, "service-unavailable")) {
+			text = _("Service Unavailable");
+		} else if(xmlnode_get_child(error, "subscription-required")) {
+			text = _("Subscription Required");
+		} else if(xmlnode_get_child(error, "unexpected-request")) {
+			text = _("Unexpected Request");
+		} else if(xmlnode_get_child(error, "undefined-condition")) {
+			text = _("Unknown Error");
+		}
+	} else if(xmlns && !strcmp(xmlns, NS_XMPP_SASL)) {
+		/* Most common reason can be the default */
+		SET_REASON(PURPLE_CONNECTION_ERROR_NETWORK_ERROR);
+		if(xmlnode_get_child(packet, "aborted")) {
+			text = _("Authorization Aborted");
+		} else if(xmlnode_get_child(packet, "incorrect-encoding")) {
+			text = _("Incorrect encoding in authorization");
+		} else if(xmlnode_get_child(packet, "invalid-authzid")) {
+			text = _("Invalid authzid");
+		} else if(xmlnode_get_child(packet, "invalid-mechanism")) {
+			text = _("Invalid Authorization Mechanism");
+		} else if(xmlnode_get_child(packet, "mechanism-too-weak")) {
+			SET_REASON(PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE);
+			text = _("Authorization mechanism too weak");
+		} else if(xmlnode_get_child(packet, "not-authorized")) {
+			SET_REASON(PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED);
+			/* Clear the pasword if it isn't being saved */
+			if (!purple_account_get_remember_password(js->gc->account))
+				purple_account_set_password(js->gc->account, NULL);
+			text = _("Not Authorized");
+		} else if(xmlnode_get_child(packet, "temporary-auth-failure")) {
+			text = _("Temporary Authentication Failure");
+		} else {
+			SET_REASON(PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED);
+			text = _("Authentication Failure");
+		}
+	} else if(!strcmp(packet->name, "stream:error") ||
+			 (!strcmp(packet->name, "error") && xmlns &&
+				!strcmp(xmlns, NS_XMPP_STREAMS))) {
+		/* Most common reason as default: */
+		SET_REASON(PURPLE_CONNECTION_ERROR_NETWORK_ERROR);
+		if(xmlnode_get_child(packet, "bad-format")) {
+			text = _("Bad Format");
+		} else if(xmlnode_get_child(packet, "bad-namespace-prefix")) {
+			text = _("Bad Namespace Prefix");
+		} else if(xmlnode_get_child(packet, "conflict")) {
+			SET_REASON(PURPLE_CONNECTION_ERROR_NAME_IN_USE);
+			text = _("Resource Conflict");
+		} else if(xmlnode_get_child(packet, "connection-timeout")) {
+			text = _("Connection Timeout");
+		} else if(xmlnode_get_child(packet, "host-gone")) {
+			text = _("Host Gone");
+		} else if(xmlnode_get_child(packet, "host-unknown")) {
+			text = _("Host Unknown");
+		} else if(xmlnode_get_child(packet, "improper-addressing")) {
+			text = _("Improper Addressing");
+		} else if(xmlnode_get_child(packet, "internal-server-error")) {
+			text = _("Internal Server Error");
+		} else if(xmlnode_get_child(packet, "invalid-id")) {
+			text = _("Invalid ID");
+		} else if(xmlnode_get_child(packet, "invalid-namespace")) {
+			text = _("Invalid Namespace");
+		} else if(xmlnode_get_child(packet, "invalid-xml")) {
+			text = _("Invalid XML");
+		} else if(xmlnode_get_child(packet, "nonmatching-hosts")) {
+			text = _("Non-matching Hosts");
+		} else if(xmlnode_get_child(packet, "not-authorized")) {
+			text = _("Not Authorized");
+		} else if(xmlnode_get_child(packet, "policy-violation")) {
+			text = _("Policy Violation");
+		} else if(xmlnode_get_child(packet, "remote-connection-failed")) {
+			text = _("Remote Connection Failed");
+		} else if(xmlnode_get_child(packet, "resource-constraint")) {
+			text = _("Resource Constraint");
+		} else if(xmlnode_get_child(packet, "restricted-xml")) {
+			text = _("Restricted XML");
+		} else if(xmlnode_get_child(packet, "see-other-host")) {
+			text = _("See Other Host");
+		} else if(xmlnode_get_child(packet, "system-shutdown")) {
+			text = _("System Shutdown");
+		} else if(xmlnode_get_child(packet, "undefined-condition")) {
+			text = _("Undefined Condition");
+		} else if(xmlnode_get_child(packet, "unsupported-encoding")) {
+			text = _("Unsupported Encoding");
+		} else if(xmlnode_get_child(packet, "unsupported-stanza-type")) {
+			text = _("Unsupported Stanza Type");
+		} else if(xmlnode_get_child(packet, "unsupported-version")) {
+			text = _("Unsupported Version");
+		} else if(xmlnode_get_child(packet, "xml-not-well-formed")) {
+			text = _("XML Not Well Formed");
+		} else {
+			text = _("Stream Error");
+		}
+	}
+
+#undef SET_REASON
+
+	if(text || cdata) {
+		char *ret = g_strdup_printf("%s%s%s", code ? code : "",
+				code ? ": " : "", text ? text : cdata);
+		g_free(cdata);
+		return ret;
+	} else {
+		return NULL;
+	}
+}
+
+static PurpleCmdRet jabber_cmd_chat_config(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	JabberChat *chat = jabber_chat_find_by_conv(conv);
+
+	if (!chat)
+		return PURPLE_CMD_RET_FAILED;
+
+	jabber_chat_request_room_configure(chat);
+	return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet jabber_cmd_chat_register(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	JabberChat *chat = jabber_chat_find_by_conv(conv);
+
+	if (!chat)
+		return PURPLE_CMD_RET_FAILED;
+
+	jabber_chat_register(chat);
+	return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet jabber_cmd_chat_topic(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	JabberChat *chat = jabber_chat_find_by_conv(conv);
+
+	if (!chat)
+		return PURPLE_CMD_RET_FAILED;
+
+	if (args && args[0] && *args[0])
+		jabber_chat_change_topic(chat, args[0]);
+	else {
+		const char *cur = purple_conv_chat_get_topic(PURPLE_CONV_CHAT(conv));
+		char *buf, *tmp, *tmp2;
+
+		if (cur) {
+			tmp = g_markup_escape_text(cur, -1);
+			tmp2 = purple_markup_linkify(tmp);
+			buf = g_strdup_printf(_("current topic is: %s"), tmp2);
+			g_free(tmp);
+			g_free(tmp2);
+		} else
+			buf = g_strdup(_("No topic is set"));
+		purple_conv_chat_write(PURPLE_CONV_CHAT(conv), "", buf,
+				PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG, time(NULL));
+		g_free(buf);
+	}
+
+	return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet jabber_cmd_chat_nick(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	JabberChat *chat = jabber_chat_find_by_conv(conv);
+
+	if(!chat || !args || !args[0])
+		return PURPLE_CMD_RET_FAILED;
+
+	if (!jabber_resourceprep_validate(args[0])) {
+		*error = g_strdup(_("Invalid nickname"));
+		return PURPLE_CMD_RET_FAILED;
+	}
+
+	if (jabber_chat_change_nick(chat, args[0]))
+		return PURPLE_CMD_RET_OK;
+	else
+		return PURPLE_CMD_RET_FAILED;
+}
+
+static PurpleCmdRet jabber_cmd_chat_part(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	JabberChat *chat = jabber_chat_find_by_conv(conv);
+
+	if (!chat)
+		return PURPLE_CMD_RET_FAILED;
+
+	jabber_chat_part(chat, args ? args[0] : NULL);
+	return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet jabber_cmd_chat_ban(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	JabberChat *chat = jabber_chat_find_by_conv(conv);
+
+	if(!chat || !args || !args[0])
+		return PURPLE_CMD_RET_FAILED;
+
+	if(!jabber_chat_ban_user(chat, args[0], args[1])) {
+		*error = g_strdup_printf(_("Unable to ban user %s"), args[0]);
+		return PURPLE_CMD_RET_FAILED;
+	}
+
+	return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet jabber_cmd_chat_affiliate(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	JabberChat *chat = jabber_chat_find_by_conv(conv);
+
+	if (!chat || !args || !args[0])
+		return PURPLE_CMD_RET_FAILED;
+
+	if (strcmp(args[0], "owner") != 0 &&
+	    strcmp(args[0], "admin") != 0 &&
+	    strcmp(args[0], "member") != 0 &&
+	    strcmp(args[0], "outcast") != 0 &&
+	    strcmp(args[0], "none") != 0) {
+		*error = g_strdup_printf(_("Unknown affiliation: \"%s\""), args[0]);
+		return PURPLE_CMD_RET_FAILED;
+	}
+
+	if (args[1]) {
+		int i;
+		char **nicks = g_strsplit(args[1], " ", -1);
+
+		for (i = 0; nicks[i]; ++i)
+			if (!jabber_chat_affiliate_user(chat, nicks[i], args[0])) {
+				*error = g_strdup_printf(_("Unable to affiliate user %s as \"%s\""), nicks[i], args[0]);
+				g_strfreev(nicks);
+				return PURPLE_CMD_RET_FAILED;
+			}
+
+		g_strfreev(nicks);
+	} else {
+		jabber_chat_affiliation_list(chat, args[0]);
+	}
+
+	return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet jabber_cmd_chat_role(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	JabberChat *chat = jabber_chat_find_by_conv(conv);
+
+	if (!chat || !args || !args[0])
+		return PURPLE_CMD_RET_FAILED;
+
+	if (strcmp(args[0], "moderator") != 0 &&
+	    strcmp(args[0], "participant") != 0 &&
+	    strcmp(args[0], "visitor") != 0 &&
+	    strcmp(args[0], "none") != 0) {
+		*error = g_strdup_printf(_("Unknown role: \"%s\""), args[0]);
+		return PURPLE_CMD_RET_FAILED;
+	}
+
+	if (args[1]) {
+		int i;
+		char **nicks = g_strsplit(args[1], " ", -1);
+
+		for (i = 0; nicks[i]; i++)
+			if (!jabber_chat_role_user(chat, nicks[i], args[0])) {
+				*error = g_strdup_printf(_("Unable to set role \"%s\" for user: %s"),
+										 args[0], nicks[i]);
+				return PURPLE_CMD_RET_FAILED;
+			}
+
+		g_strfreev(nicks);
+	} else {
+		jabber_chat_role_list(chat, args[0]);
+	}
+	return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet jabber_cmd_chat_invite(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	if(!args || !args[0])
+		return PURPLE_CMD_RET_FAILED;
+
+	jabber_chat_invite(purple_conversation_get_gc(conv),
+			purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)), args[1] ? args[1] : "",
+			args[0]);
+
+	return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet jabber_cmd_chat_join(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	JabberChat *chat = jabber_chat_find_by_conv(conv);
+	GHashTable *components;
+
+	if(!chat || !args || !args[0])
+		return PURPLE_CMD_RET_FAILED;
+
+	components = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
+
+	g_hash_table_replace(components, "room", args[0]);
+	g_hash_table_replace(components, "server", chat->server);
+	g_hash_table_replace(components, "handle", chat->handle);
+	if(args[1])
+		g_hash_table_replace(components, "password", args[1]);
+
+	jabber_chat_join(purple_conversation_get_gc(conv), components);
+
+	g_hash_table_destroy(components);
+	return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet jabber_cmd_chat_kick(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	JabberChat *chat = jabber_chat_find_by_conv(conv);
+
+	if(!chat || !args || !args[0])
+		return PURPLE_CMD_RET_FAILED;
+
+	if(!jabber_chat_kick_user(chat, args[0], args[1])) {
+		*error = g_strdup_printf(_("Unable to kick user %s"), args[0]);
+		return PURPLE_CMD_RET_FAILED;
+	}
+
+	return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet jabber_cmd_chat_msg(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	JabberChat *chat = jabber_chat_find_by_conv(conv);
+	char *who;
+
+	if (!chat)
+		return PURPLE_CMD_RET_FAILED;
+
+	who = g_strdup_printf("%s@%s/%s", chat->room, chat->server, args[0]);
+
+	jabber_message_send_im(purple_conversation_get_gc(conv), who, args[1], 0);
+
+	g_free(who);
+	return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet jabber_cmd_ping(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	PurpleAccount *account;
+	PurpleConnection *pc;
+
+	if(!args || !args[0])
+		return PURPLE_CMD_RET_FAILED;
+
+	account = purple_conversation_get_account(conv);
+	pc = purple_account_get_connection(account);
+
+	if(!jabber_ping_jid(purple_connection_get_protocol_data(pc), args[0])) {
+		*error = g_strdup_printf(_("Unable to ping user %s"), args[0]);
+		return PURPLE_CMD_RET_FAILED;
+	}
+
+	return PURPLE_CMD_RET_OK;
+}
+
+static gboolean _jabber_send_buzz(JabberStream *js, const char *username, char **error) {
+
+	JabberBuddy *jb;
+	JabberBuddyResource *jbr;
+	PurpleConnection *gc = js->gc;
+	PurpleBuddy *buddy =
+		purple_find_buddy(purple_connection_get_account(gc), username);
+	const gchar *alias =
+		buddy ? purple_buddy_get_contact_alias(buddy) : username;
+
+	if(!username)
+		return FALSE;
+
+	jb = jabber_buddy_find(js, username, FALSE);
+	if(!jb) {
+		*error = g_strdup_printf(_("Unable to buzz, because there is nothing "
+			"known about %s."), alias);
+		return FALSE;
+	}
+
+	jbr = jabber_buddy_find_resource(jb, NULL);
+	if (!jbr) {
+		*error = g_strdup_printf(_("Unable to buzz, because %s might be offline."),
+			alias);
+		return FALSE;
+	}
+
+	if (jabber_resource_has_capability(jbr, NS_ATTENTION)) {
+		xmlnode *buzz, *msg = xmlnode_new("message");
+		gchar *to;
+
+		to = g_strdup_printf("%s/%s", username, jbr->name);
+		xmlnode_set_attrib(msg, "to", to);
+		g_free(to);
+
+		/* avoid offline storage */
+		xmlnode_set_attrib(msg, "type", "headline");
+
+		buzz = xmlnode_new_child(msg, "attention");
+		xmlnode_set_namespace(buzz, NS_ATTENTION);
+
+		jabber_send(js, msg);
+		xmlnode_free(msg);
+
+		return TRUE;
+	} else {
+		*error = g_strdup_printf(_("Unable to buzz, because %s does "
+			"not support it or does not wish to receive buzzes now."), alias);
+		return FALSE;
+	}
+}
+
+static PurpleCmdRet jabber_cmd_buzz(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	JabberStream *js = conv->account->gc->proto_data;
+	const gchar *who;
+
+	if (!args || !args[0]) {
+		/* use the buddy from conversation, if it's a one-to-one conversation */
+		if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) {
+			who = purple_conversation_get_name(conv);
+		} else {
+			return PURPLE_CMD_RET_FAILED;
+		}
+	} else {
+		who = args[0];
+	}
+
+	if (_jabber_send_buzz(js, who, error)) {
+		const gchar *alias;
+		gchar *str;
+		PurpleBuddy *buddy =
+			purple_find_buddy(purple_connection_get_account(conv->account->gc),
+				who);
+
+		if (buddy != NULL)
+			alias = purple_buddy_get_contact_alias(buddy);
+		else
+			alias = who;
+
+		str = g_strdup_printf(_("Buzzing %s..."), alias);
+		purple_conversation_write(conv, NULL, str,
+			PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NOTIFY, time(NULL));
+		g_free(str);
+
+		return PURPLE_CMD_RET_OK;
+	} else {
+		return PURPLE_CMD_RET_FAILED;
+	}
+}
+
+GList *jabber_attention_types(PurpleAccount *account)
+{
+	static GList *types = NULL;
+
+	if (!types) {
+		types = g_list_append(types, purple_attention_type_new("Buzz", _("Buzz"),
+				_("%s has buzzed you!"), _("Buzzing %s...")));
+	}
+
+	return types;
+}
+
+gboolean jabber_send_attention(PurpleConnection *gc, const char *username, guint code)
+{
+	JabberStream *js = gc->proto_data;
+	gchar *error = NULL;
+
+	if (!_jabber_send_buzz(js, username, &error)) {
+		purple_debug_error("jabber", "jabber_send_attention: jabber_cmd_buzz failed with error: %s\n", error ? error : "(NULL)");
+		g_free(error);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+
+gboolean jabber_offline_message(const PurpleBuddy *buddy)
+{
+	return TRUE;
+}
+
+#ifdef USE_VV
+gboolean
+jabber_audio_enabled(JabberStream *js, const char *namespace)
+{
+	PurpleMediaManager *manager = purple_media_manager_get();
+	PurpleMediaCaps caps = purple_media_manager_get_ui_caps(manager);
+
+	return (caps & (PURPLE_MEDIA_CAPS_AUDIO | PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION));
+}
+
+gboolean
+jabber_video_enabled(JabberStream *js, const char *namespace)
+{
+	PurpleMediaManager *manager = purple_media_manager_get();
+	PurpleMediaCaps caps = purple_media_manager_get_ui_caps(manager);
+
+	return (caps & (PURPLE_MEDIA_CAPS_VIDEO | PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION));
+}
+
+typedef struct {
+	PurpleAccount *account;
+	gchar *who;
+	PurpleMediaSessionType type;
+
+} JabberMediaRequest;
+
+static void
+jabber_media_cancel_cb(JabberMediaRequest *request,
+		PurpleRequestFields *fields)
+{
+	g_free(request->who);
+	g_free(request);
+}
+
+static void
+jabber_media_ok_cb(JabberMediaRequest *request, PurpleRequestFields *fields)
+{
+	PurpleRequestField *field =
+			purple_request_fields_get_field(fields, "resource");
+	int selected_id = purple_request_field_choice_get_value(field);
+	GList *labels = purple_request_field_choice_get_labels(field);
+	gchar *who = g_strdup_printf("%s/%s", request->who,
+			(gchar*)g_list_nth_data(labels, selected_id));
+	jabber_initiate_media(request->account, who, request->type);
+
+	g_free(who);
+	g_free(request->who);
+	g_free(request);
+}
+#endif
+
+gboolean
+jabber_initiate_media(PurpleAccount *account, const char *who,
+		      PurpleMediaSessionType type)
+{
+#ifdef USE_VV
+	JabberStream *js = (JabberStream *)
+			purple_account_get_connection(account)->proto_data;
+	JabberBuddy *jb;
+	JabberBuddyResource *jbr = NULL;
+	char *resource;
+
+	if (!js) {
+		purple_debug_error("jabber",
+				"jabber_initiate_media: NULL stream\n");
+		return FALSE;
+	}
+
+
+	if((resource = jabber_get_resource(who)) != NULL) {
+		/* they've specified a resource, no need to ask or
+		 * default or anything, just do it */
+
+		jb = jabber_buddy_find(js, who, FALSE);
+		jbr = jabber_buddy_find_resource(jb, resource);
+		g_free(resource);
+
+		if (type & PURPLE_MEDIA_AUDIO &&
+				!jabber_resource_has_capability(jbr,
+				JINGLE_APP_RTP_SUPPORT_AUDIO) &&
+				jabber_resource_has_capability(jbr, NS_GOOGLE_VOICE))
+			return jabber_google_session_initiate(js, who, type);
+		else
+			return jingle_rtp_initiate_media(js, who, type);
+	}
+
+	jb = jabber_buddy_find(js, who, FALSE);
+
+	if(!jb || !jb->resources) {
+		/* no resources online, we're trying to initiate with someone
+		 * whose presence we're not subscribed to, or
+		 * someone who is offline.  Let's inform the user */
+		char *msg;
+
+		if(!jb) {
+			msg = g_strdup_printf(_("Unable to initiate media with %s: invalid JID"), who);
+		} else if(jb->subscription & JABBER_SUB_TO) {
+			msg = g_strdup_printf(_("Unable to initiate media with %s: user is not online"), who);
+		} else {
+			msg = g_strdup_printf(_("Unable to initiate media with %s: not subscribed to user presence"), who);
+		}
+
+		purple_notify_error(account, _("Media Initiation Failed"),
+				_("Media Initiation Failed"), msg);
+		g_free(msg);
+		return FALSE;
+	} else if(!jb->resources->next) {
+		/* only 1 resource online (probably our most common case)
+		 * so no need to ask who to initiate with */
+		gchar *name;
+		gboolean result;
+		jbr = jb->resources->data;
+		name = g_strdup_printf("%s/%s", who, jbr->name);
+		result = jabber_initiate_media(account, name, type);
+		g_free(name);
+		return result;
+	} else {
+		/* we've got multiple resources,
+		 * we need to pick one to initiate with */
+		GList *l;
+		char *msg;
+		PurpleRequestFields *fields;
+		PurpleRequestField *field = purple_request_field_choice_new(
+				"resource", _("Resource"), 0);
+		PurpleRequestFieldGroup *group;
+		JabberMediaRequest *request;
+
+		for(l = jb->resources; l; l = l->next)
+		{
+			JabberBuddyResource *ljbr = l->data;
+			PurpleMediaCaps caps;
+			gchar *name;
+			name = g_strdup_printf("%s/%s", who, ljbr->name);
+			caps = jabber_get_media_caps(account, name);
+			g_free(name);
+
+			if ((type & PURPLE_MEDIA_AUDIO) &&
+					(type & PURPLE_MEDIA_VIDEO)) {
+				if (caps & PURPLE_MEDIA_CAPS_AUDIO_VIDEO) {
+					jbr = ljbr;
+					purple_request_field_choice_add(
+							field, jbr->name);
+				}
+			} else if (type & (PURPLE_MEDIA_AUDIO) &&
+					(caps & PURPLE_MEDIA_CAPS_AUDIO)) {
+				jbr = ljbr;
+				purple_request_field_choice_add(
+						field, jbr->name);
+			}else if (type & (PURPLE_MEDIA_VIDEO) &&
+					(caps & PURPLE_MEDIA_CAPS_VIDEO)) {
+				jbr = ljbr;
+				purple_request_field_choice_add(
+						field, jbr->name);
+			}
+		}
+
+		if (jbr == NULL) {
+			purple_debug_error("jabber",
+					"No resources available\n");
+			return FALSE;
+		}
+
+		if (g_list_length(purple_request_field_choice_get_labels(
+				field)) <= 1) {
+			gchar *name;
+			gboolean result;
+			purple_request_field_destroy(field);
+			name = g_strdup_printf("%s/%s", who, jbr->name);
+			result = jabber_initiate_media(account, name, type);
+			g_free(name);
+			return result;
+		}
+
+		msg = g_strdup_printf(_("Please select the resource of %s with which you would like to start a media session."), who);
+		fields = purple_request_fields_new();
+		group =	purple_request_field_group_new(NULL);
+		request = g_new0(JabberMediaRequest, 1);
+		request->account = account;
+		request->who = g_strdup(who);
+		request->type = type;
+
+		purple_request_field_group_add_field(group, field);
+		purple_request_fields_add_group(fields, group);
+		purple_request_fields(account, _("Select a Resource"), msg,
+				NULL, fields, _("Initiate Media"),
+				G_CALLBACK(jabber_media_ok_cb), _("Cancel"),
+				G_CALLBACK(jabber_media_cancel_cb),
+				account, who, NULL, request);
+
+		g_free(msg);
+		return TRUE;
+	}
+#endif
+	return FALSE;
+}
+
+PurpleMediaCaps jabber_get_media_caps(PurpleAccount *account, const char *who)
+{
+#ifdef USE_VV
+	JabberStream *js = (JabberStream *)
+			purple_account_get_connection(account)->proto_data;
+	JabberBuddy *jb;
+	JabberBuddyResource *jbr;
+	PurpleMediaCaps total = PURPLE_MEDIA_CAPS_NONE;
+	gchar *resource;
+	GList *specific = NULL, *l;
+
+	if (!js) {
+		purple_debug_info("jabber",
+				"jabber_can_do_media: NULL stream\n");
+		return FALSE;
+	}
+
+	jb = jabber_buddy_find(js, who, FALSE);
+
+	if (!jb || !jb->resources) {
+		/* no resources online, we're trying to get caps for someone
+		 * whose presence we're not subscribed to, or
+		 * someone who is offline. */
+		return total;
+
+	} else if ((resource = jabber_get_resource(who)) != NULL) {
+		/* they've specified a resource, no need to ask or
+		 * default or anything, just do it */
+		jbr = jabber_buddy_find_resource(jb, resource);
+		g_free(resource);
+
+		if (!jbr) {
+			purple_debug_error("jabber", "jabber_get_media_caps:"
+					" Can't find resource %s\n", who);
+			return total;
+		}
+
+		l = specific = g_list_prepend(specific, jbr);
+
+	} else {
+		/* we've got multiple resources, combine their caps */
+		l = jb->resources;
+	}
+
+	for (; l; l = l->next) {
+		PurpleMediaCaps caps = PURPLE_MEDIA_CAPS_NONE;
+		jbr = l->data;
+
+		if (jabber_resource_has_capability(jbr,
+				JINGLE_APP_RTP_SUPPORT_AUDIO))
+			caps |= PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION |
+					PURPLE_MEDIA_CAPS_AUDIO;
+		if (jabber_resource_has_capability(jbr,
+				JINGLE_APP_RTP_SUPPORT_VIDEO))
+			caps |= PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION |
+					PURPLE_MEDIA_CAPS_VIDEO;
+		if (caps & PURPLE_MEDIA_CAPS_AUDIO && caps &
+				PURPLE_MEDIA_CAPS_VIDEO)
+			caps |= PURPLE_MEDIA_CAPS_AUDIO_VIDEO;
+		if (caps != PURPLE_MEDIA_CAPS_NONE) {
+			if (!jabber_resource_has_capability(jbr,
+					JINGLE_TRANSPORT_ICEUDP) &&
+					!jabber_resource_has_capability(jbr,
+					JINGLE_TRANSPORT_RAWUDP)) {
+				purple_debug_info("jingle-rtp", "Buddy doesn't "
+						"support the same transport types\n");
+				caps = PURPLE_MEDIA_CAPS_NONE;
+			} else
+				caps |= PURPLE_MEDIA_CAPS_MODIFY_SESSION |
+						PURPLE_MEDIA_CAPS_CHANGE_DIRECTION;
+		}
+		if (jabber_resource_has_capability(jbr, NS_GOOGLE_VOICE)) {
+			caps |= PURPLE_MEDIA_CAPS_AUDIO;
+			if (jabber_resource_has_capability(jbr, NS_GOOGLE_VIDEO))
+				caps |= PURPLE_MEDIA_CAPS_AUDIO_VIDEO;
+		}
+
+		total |= caps;
+	}
+
+	if (specific) {
+		g_list_free(specific);
+	}
+
+	return total;
+#else
+	return PURPLE_MEDIA_CAPS_NONE;
+#endif
+}
+
+gboolean jabber_can_receive_file(PurpleConnection *gc, const char *who)
+{
+	JabberStream *js = gc->proto_data;
+
+	if (js) {
+		JabberBuddy *jb = jabber_buddy_find(js, who, FALSE);
+		GList *iter;
+		gboolean has_resources_without_caps = FALSE;
+
+		/* if we didn't find a JabberBuddy, we don't have presence for this
+		 buddy, let's assume they can receive files, disco should tell us
+		 when actually trying */
+		if (jb == NULL)
+			return TRUE;
+
+		/* find out if there is any resources without caps */
+		for (iter = jb->resources; iter ; iter = g_list_next(iter)) {
+			JabberBuddyResource *jbr = (JabberBuddyResource *) iter->data;
+
+			if (!jabber_resource_know_capabilities(jbr)) {
+				has_resources_without_caps = TRUE;
+			}
+		}
+
+		if (has_resources_without_caps) {
+			/* there is at least one resource which we don't have caps for, 
+			 let's assume they can receive files... */
+			return TRUE;
+		} else {
+			/* we have caps for all the resources, see if at least one has
+			 right caps */
+			for (iter = jb->resources; iter ; iter = g_list_next(iter)) {
+				JabberBuddyResource *jbr = (JabberBuddyResource *) iter->data;
+
+				if (jabber_resource_has_capability(jbr,
+						"http://jabber.org/protocol/si/profile/file-transfer")
+			    	&& (jabber_resource_has_capability(jbr,
+			    			NS_BYTESTREAMS)
+			        	|| jabber_resource_has_capability(jbr, NS_IBB))) {
+					return TRUE;
+				}
+			}
+			return FALSE;
+		}
+	} else {
+		return TRUE;
+	}
+}
+
+void jabber_register_commands(void)
+{
+	PurpleCmdId id;
+	id = purple_cmd_register("config", "", PURPLE_CMD_P_PRPL,
+	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
+	                  "prpl-jabber", jabber_cmd_chat_config,
+	                  _("config:  Configure a chat room."), NULL);
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("configure", "", PURPLE_CMD_P_PRPL,
+	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
+	                  "prpl-jabber", jabber_cmd_chat_config,
+	                  _("configure:  Configure a chat room."), NULL);
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("nick", "s", PURPLE_CMD_P_PRPL,
+	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
+	                  "prpl-jabber", jabber_cmd_chat_nick,
+	                  _("nick &lt;new nickname&gt;:  Change your nickname."),
+	                  NULL);
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("part", "s", PURPLE_CMD_P_PRPL,
+	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+	                  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
+	                  jabber_cmd_chat_part, _("part [message]:  Leave the room."),
+	                  NULL);
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("register", "", PURPLE_CMD_P_PRPL,
+	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
+	                  "prpl-jabber", jabber_cmd_chat_register,
+	                  _("register:  Register with a chat room."), NULL);
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	/* XXX: there needs to be a core /topic cmd, methinks */
+	id = purple_cmd_register("topic", "s", PURPLE_CMD_P_PRPL,
+	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+	                  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
+	                  jabber_cmd_chat_topic,
+	                  _("topic [new topic]:  View or change the topic."),
+	                  NULL);
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("ban", "ws", PURPLE_CMD_P_PRPL,
+	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+	                  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
+	                  jabber_cmd_chat_ban,
+	                  _("ban &lt;user&gt; [reason]:  Ban a user from the room."),
+	                  NULL);
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("affiliate", "ws", PURPLE_CMD_P_PRPL,
+	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+	                  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
+	                  jabber_cmd_chat_affiliate,
+	                  _("affiliate &lt;owner|admin|member|outcast|none&gt; [nick1] [nick2] ...: Get the users with an affiliation or set users' affiliation with the room."),
+	                  NULL);
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("role", "ws", PURPLE_CMD_P_PRPL,
+	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+	                  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
+	                  jabber_cmd_chat_role,
+	                  _("role &lt;moderator|participant|visitor|none&gt; [nick1] [nick2] ...: Get the users with a role or set users' role with the room."),
+	                  NULL);
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("invite", "ws", PURPLE_CMD_P_PRPL,
+	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+	                  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
+	                  jabber_cmd_chat_invite,
+	                  _("invite &lt;user&gt; [message]:  Invite a user to the room."),
+	                  NULL);
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("join", "ws", PURPLE_CMD_P_PRPL,
+	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+	                  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
+	                  jabber_cmd_chat_join,
+	                  _("join: &lt;room&gt; [password]:  Join a chat on this server."),
+	                  NULL);
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("kick", "ws", PURPLE_CMD_P_PRPL,
+	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+	                  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
+	                  jabber_cmd_chat_kick,
+	                  _("kick &lt;user&gt; [reason]:  Kick a user from the room."),
+	                  NULL);
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("msg", "ws", PURPLE_CMD_P_PRPL,
+	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
+	                  "prpl-jabber", jabber_cmd_chat_msg,
+	                  _("msg &lt;user&gt; &lt;message&gt;:  Send a private message to another user."),
+	                  NULL);
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("ping", "w", PURPLE_CMD_P_PRPL,
+					  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM |
+					  PURPLE_CMD_FLAG_PRPL_ONLY,
+					  "prpl-jabber", jabber_cmd_ping,
+					  _("ping &lt;jid&gt;:	Ping a user/component/server."),
+					  NULL);
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("buzz", "w", PURPLE_CMD_P_PRPL,
+					  PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PRPL_ONLY |
+					  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
+					  "prpl-jabber", jabber_cmd_buzz,
+					  _("buzz: Buzz a user to get their attention"), NULL);
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+}
+
+void jabber_unregister_commands(void)
+{
+	while (jabber_cmds != NULL) {
+		purple_cmd_unregister(GPOINTER_TO_UINT(jabber_cmds->data));
+		jabber_cmds = g_slist_delete_link(jabber_cmds, jabber_cmds);
+	}
+}
+
+/* IPC functions */
+
+/**
+ * IPC function for determining if a contact supports a certain feature.
+ *
+ * @param account   The PurpleAccount
+ * @param jid       The full JID of the contact.
+ * @param feature   The feature's namespace.
+ *
+ * @return TRUE if supports feature; else FALSE.
+ */
+static gboolean
+jabber_ipc_contact_has_feature(PurpleAccount *account, const gchar *jid,
+                               const gchar *feature)
+{
+	PurpleConnection *gc = purple_account_get_connection(account);
+	JabberStream *js;
+	JabberBuddy *jb;
+	JabberBuddyResource *jbr;
+	gchar *resource;
+
+	if (!purple_account_is_connected(account))
+		return FALSE;
+	js = gc->proto_data;
+
+	if (!(resource = jabber_get_resource(jid)) ||
+	    !(jb = jabber_buddy_find(js, jid, FALSE)) ||
+	    !(jbr = jabber_buddy_find_resource(jb, resource))) {
+		g_free(resource);
+		return FALSE;
+	}
+
+	g_free(resource);
+
+	return jabber_resource_has_capability(jbr, feature);
+}
+
+static void
+jabber_ipc_add_feature(const gchar *feature)
+{
+	if (!feature)
+		return;
+	jabber_add_feature(feature, 0);
+
+	/* send presence with new caps info for all connected accounts */
+	jabber_caps_broadcast_change();
+}
+
+void
+jabber_init_plugin(PurplePlugin *plugin)
+{
+	GHashTable *ui_info = purple_core_get_ui_info();
+	const gchar *ui_type;
+	const gchar *type = "pc"; /* default client type, if unknown or
+								unspecified */
+	const gchar *ui_name = NULL;
+
+	ui_type = ui_info ? g_hash_table_lookup(ui_info, "client_type") : NULL;
+	if (ui_type) {
+		if (strcmp(ui_type, "pc") == 0 ||
+			strcmp(ui_type, "console") == 0 ||
+			strcmp(ui_type, "phone") == 0 ||
+			strcmp(ui_type, "handheld") == 0 ||
+			strcmp(ui_type, "web") == 0 ||
+			strcmp(ui_type, "bot") == 0) {
+			type = ui_type;
+		}
+	}
+
+	if (ui_info)
+		ui_name = g_hash_table_lookup(ui_info, "name");
+	if (ui_name == NULL)
+		ui_name = PACKAGE;
+
+	jabber_add_identity("client", type, NULL, ui_name);
+
+	/* initialize jabber_features list */
+	jabber_add_feature(NS_LAST_ACTIVITY, 0);
+	jabber_add_feature(NS_OOB_IQ_DATA, 0);
+	jabber_add_feature(NS_ENTITY_TIME, 0);
+	jabber_add_feature("jabber:iq:version", 0);
+	jabber_add_feature("jabber:x:conference", 0);
+	jabber_add_feature(NS_BYTESTREAMS, 0);
+	jabber_add_feature("http://jabber.org/protocol/caps", 0);
+	jabber_add_feature("http://jabber.org/protocol/chatstates", 0);
+	jabber_add_feature(NS_DISCO_INFO, 0);
+	jabber_add_feature(NS_DISCO_ITEMS, 0);
+	jabber_add_feature(NS_IBB, 0);
+	jabber_add_feature("http://jabber.org/protocol/muc", 0);
+	jabber_add_feature("http://jabber.org/protocol/muc#user", 0);
+	jabber_add_feature("http://jabber.org/protocol/si", 0);
+	jabber_add_feature("http://jabber.org/protocol/si/profile/file-transfer", 0);
+	jabber_add_feature(NS_XHTML_IM, 0);
+	jabber_add_feature(NS_PING, 0);
+
+	/* Buzz/Attention */
+	jabber_add_feature(NS_ATTENTION, jabber_buzz_isenabled);
+
+	/* Bits Of Binary */
+	jabber_add_feature(NS_BOB, 0);
+
+	/* Jingle features! */
+	jabber_add_feature(JINGLE, 0);
+
+#ifdef USE_VV
+	jabber_add_feature(NS_GOOGLE_PROTOCOL_SESSION, jabber_audio_enabled);
+	jabber_add_feature(NS_GOOGLE_VOICE, jabber_audio_enabled);
+	jabber_add_feature(NS_GOOGLE_VIDEO, jabber_video_enabled);
+	jabber_add_feature(NS_GOOGLE_CAMERA, jabber_video_enabled);
+	jabber_add_feature(JINGLE_APP_RTP, 0);
+	jabber_add_feature(JINGLE_APP_RTP_SUPPORT_AUDIO, jabber_audio_enabled);
+	jabber_add_feature(JINGLE_APP_RTP_SUPPORT_VIDEO, jabber_video_enabled);
+	jabber_add_feature(JINGLE_TRANSPORT_RAWUDP, 0);
+	jabber_add_feature(JINGLE_TRANSPORT_ICEUDP, 0);
+#endif
+
+	jabber_auth_init();
+
+	/* IPC functions */
+	purple_plugin_ipc_register(plugin, "contact_has_feature", PURPLE_CALLBACK(jabber_ipc_contact_has_feature),
+							 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER,
+							 purple_value_new(PURPLE_TYPE_BOOLEAN), 3,
+							 purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_ACCOUNT),
+							 purple_value_new(PURPLE_TYPE_STRING),
+							 purple_value_new(PURPLE_TYPE_STRING));
+
+	purple_plugin_ipc_register(plugin, "add_feature", PURPLE_CALLBACK(jabber_ipc_add_feature),
+							 purple_marshal_VOID__POINTER,
+							 NULL, 1,
+							 purple_value_new(PURPLE_TYPE_STRING));
+
+	/* Modifying these? Look at libxmpp.c:load_plugin for the signal versions */
+	purple_plugin_ipc_register(plugin, "register_namespace_watcher",
+	                           PURPLE_CALLBACK(jabber_iq_signal_register),
+	                           purple_marshal_VOID__POINTER_POINTER,
+	                           NULL, 2,
+	                           purple_value_new(PURPLE_TYPE_STRING),  /* node */
+	                           purple_value_new(PURPLE_TYPE_STRING)); /* namespace */
+
+	purple_plugin_ipc_register(plugin, "unregister_namespace_watcher",
+	                           PURPLE_CALLBACK(jabber_iq_signal_unregister),
+	                           purple_marshal_VOID__POINTER_POINTER,
+	                           NULL, 2,
+	                           purple_value_new(PURPLE_TYPE_STRING),  /* node */
+	                           purple_value_new(PURPLE_TYPE_STRING)); /* namespace */
+}
+
+void
+jabber_uninit_plugin(PurplePlugin *plugin)
+{
+	purple_plugin_ipc_unregister_all(plugin);
+
+	jabber_auth_uninit();
+	jabber_features_destroy();
+	jabber_identities_destroy();
+}
diff -Npur pidgin-2.6.6/libpurple/protocols/jabber/jutil.c pidgin-2.6.6-new/libpurple/protocols/jabber/jutil.c
--- pidgin-2.6.6/libpurple/protocols/jabber/jutil.c	2010-02-16 17:34:06.000000000 +0800
+++ pidgin-2.6.6-new/libpurple/protocols/jabber/jutil.c	2014-03-14 15:00:28.423253554 +0800
@@ -509,6 +509,34 @@ jabber_id_free(JabberID *jid)
 	}
 }
 
+
+gboolean
+jabber_id_equal(JabberStream *js, const JabberID *jid1, const JabberID *jid2)
+{
+	const JabberID *j1, *j2;
+	JabberID *bare_user_jid;
+	gboolean equal;
+
+	/* If an outgoing stanza has no 'to', or an incoming has no 'from',
+	 * then those are "the server acting as my account". This function will
+	 * handle that correctly.
+	 */
+	if (!jid1 && !jid2)
+		return TRUE;
+
+	bare_user_jid = jabber_id_to_bare_jid(js->user);
+	j1 = jid1 ? jid1 : bare_user_jid;
+	j2 = jid2 ? jid2 : bare_user_jid;
+
+	equal = purple_strequal(j1->node, j2->node) &&
+			purple_strequal(j1->domain, j2->domain) &&
+			purple_strequal(j1->resource, j2->resource);
+
+	jabber_id_free(bare_user_jid);
+
+	return equal;
+}
+
 char *jabber_get_domain(const char *in)
 {
 	JabberID *jid = jabber_id_new(in);
@@ -537,6 +565,17 @@ char *jabber_get_resource(const char *in
 	return out;
 }
 
+JabberID *
+jabber_id_to_bare_jid(const JabberID *jid)
+{
+	JabberID *result = g_new0(JabberID, 1);
+
+	result->node = g_strdup(jid->node);
+	result->domain = g_strdup(jid->domain);
+
+	return result;
+}
+
 char *
 jabber_get_bare_jid(const char *in)
 {
@@ -562,6 +601,19 @@ jabber_id_get_bare_jid(const JabberID *j
 	                   NULL);
 }
 
+char *
+jabber_id_get_full_jid(const JabberID *jid)
+{
+	g_return_val_if_fail(jid != NULL, NULL);
+
+	return g_strconcat(jid->node ? jid->node : "",
+	                   jid->node ? "@" : "",
+	                   jid->domain,
+	                   jid->resource ? "/" : "",
+	                   jid->resource ? jid->resource : "",
+	                   NULL);
+}
+
 gboolean
 jabber_jid_is_domain(const char *jid)
 {
diff -Npur pidgin-2.6.6/libpurple/protocols/jabber/jutil.c.orig pidgin-2.6.6-new/libpurple/protocols/jabber/jutil.c.orig
--- pidgin-2.6.6/libpurple/protocols/jabber/jutil.c.orig	1970-01-01 08:00:00.000000000 +0800
+++ pidgin-2.6.6-new/libpurple/protocols/jabber/jutil.c.orig	2010-02-16 17:34:06.000000000 +0800
@@ -0,0 +1,679 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ *
+ */
+#include "internal.h"
+#include "account.h"
+#include "cipher.h"
+#include "conversation.h"
+#include "debug.h"
+#include "server.h"
+#include "util.h"
+#include "xmlnode.h"
+
+#include "chat.h"
+#include "presence.h"
+#include "jutil.h"
+
+#ifdef USE_IDN
+#include <idna.h>
+#include <stringprep.h>
+static char idn_buffer[1024];
+#endif
+
+#ifdef USE_IDN
+static gboolean jabber_nodeprep(char *str, size_t buflen)
+{
+	return stringprep_xmpp_nodeprep(str, buflen) == STRINGPREP_OK;
+}
+
+static gboolean jabber_resourceprep(char *str, size_t buflen)
+{
+	return stringprep_xmpp_resourceprep(str, buflen) == STRINGPREP_OK;
+}
+
+static JabberID*
+jabber_idn_validate(const char *str, const char *at, const char *slash,
+                    const char *null)
+{
+	const char *node = NULL;
+	const char *domain = NULL;
+	const char *resource = NULL;
+	int node_len = 0;
+	int domain_len = 0;
+	int resource_len = 0;
+	char *out;
+	JabberID *jid;
+
+	/* Ensure no parts are > 1023 bytes */
+	if (at) {
+		node = str;
+		node_len = at - str;
+
+		domain = at + 1;
+		if (slash) {
+			domain_len = slash - (at + 1);
+			resource = slash + 1;
+			resource_len = null - (slash + 1);
+		} else {
+			domain_len = null - (at + 1);
+		}
+	} else {
+		domain = str;
+
+		if (slash) {
+			domain_len = slash - str;
+			resource = slash;
+			resource_len = null - (slash + 1);
+		} else {
+			domain_len = null - (str + 1);
+		}
+	}
+
+	if (node && node_len > 1023)
+		return NULL;
+	if (domain_len > 1023)
+		return NULL;
+	if (resource && resource_len > 1023)
+		return NULL;
+
+	jid = g_new0(JabberID, 1);
+
+	if (node) {
+		strncpy(idn_buffer, node, node_len);
+		idn_buffer[node_len] = '\0';
+
+		if (!jabber_nodeprep(idn_buffer, sizeof(idn_buffer))) {
+			jabber_id_free(jid);
+			jid = NULL;
+			goto out;
+		}
+
+		jid->node = g_strdup(idn_buffer);
+	}
+
+	/* domain *must* be here */
+	strncpy(idn_buffer, domain, domain_len);
+	idn_buffer[domain_len] = '\0';
+	if (domain[0] == '[') { /* IPv6 address */
+		gboolean valid = FALSE;
+
+		if (idn_buffer[domain_len - 1] == ']') {
+			idn_buffer[domain_len - 1] = '\0';
+			valid = purple_ipv6_address_is_valid(idn_buffer + 1);
+		}
+
+		if (!valid) {
+			jabber_id_free(jid);
+			jid = NULL;
+			goto out;
+		}
+	} else {
+		/* Apply nameprep */
+		if (stringprep_nameprep(idn_buffer, sizeof(idn_buffer)) != STRINGPREP_OK) {
+			jabber_id_free(jid);
+			jid = NULL;
+			goto out;
+		}
+
+		/* And now ToASCII */
+		if (idna_to_ascii_8z(idn_buffer, &out, IDNA_USE_STD3_ASCII_RULES) != IDNA_SUCCESS) {
+			jabber_id_free(jid);
+			jid = NULL;
+			goto out;
+		}
+
+		/* This *MUST* be freed using 'free', not 'g_free' */
+		free(out);
+		jid->domain = g_strdup(idn_buffer);
+	}
+
+	if (resource) {
+		strncpy(idn_buffer, resource, resource_len);
+		idn_buffer[resource_len] = '\0';
+
+		if (!jabber_resourceprep(idn_buffer, sizeof(idn_buffer))) {
+			jabber_id_free(jid);
+			jid = NULL;
+			goto out;
+		} else
+			jid->resource = g_strdup(idn_buffer);
+	}
+
+out:
+	return jid;
+}
+
+#endif /* USE_IDN */
+
+gboolean jabber_nodeprep_validate(const char *str)
+{
+#ifdef USE_IDN
+	gboolean result;
+#else
+	const char *c;
+#endif
+
+	if(!str)
+		return TRUE;
+
+	if(strlen(str) > 1023)
+		return FALSE;
+
+#ifdef USE_IDN
+	strncpy(idn_buffer, str, sizeof(idn_buffer) - 1);
+	idn_buffer[sizeof(idn_buffer) - 1] = '\0';
+	result = jabber_nodeprep(idn_buffer, sizeof(idn_buffer));
+	return result;
+#else /* USE_IDN */
+	c = str;
+	while(c && *c) {
+		gunichar ch = g_utf8_get_char(c);
+		if(ch == '\"' || ch == '&' || ch == '\'' || ch == '/' || ch == ':' ||
+				ch == '<' || ch == '>' || ch == '@' || !g_unichar_isgraph(ch)) {
+			return FALSE;
+		}
+		c = g_utf8_next_char(c);
+	}
+
+	return TRUE;
+#endif /* USE_IDN */
+}
+
+gboolean jabber_domain_validate(const char *str)
+{
+	const char *c;
+	size_t len;
+
+	if(!str)
+		return TRUE;
+
+	len = strlen(str);
+	if (len > 1023)
+		return FALSE;
+
+	c = str;
+
+	if (*c == '[') {
+		/* Check if str is a valid IPv6 identifier */
+		gboolean valid = FALSE;
+
+		if (*(c + len - 1) != ']')
+			return FALSE;
+
+		/* Ugly, but in-place */
+		*(gchar *)(c + len - 1) = '\0';
+		valid = purple_ipv6_address_is_valid(c + 1);
+		*(gchar *)(c + len - 1) = ']';
+
+		return valid;
+	}
+
+	while(c && *c) {
+		gunichar ch = g_utf8_get_char(c);
+		/* The list of characters allowed in domain names is pretty small */
+		if ((ch <= 0x7F && !( (ch >= 'a' && ch <= 'z')
+				|| (ch >= '0' && ch <= '9')
+				|| (ch >= 'A' && ch <= 'Z')
+				|| ch == '.'
+				|| ch == '-' )) || (ch >= 0x80 && !g_unichar_isgraph(ch)))
+			return FALSE;
+
+		c = g_utf8_next_char(c);
+	}
+
+	return TRUE;
+}
+
+gboolean jabber_resourceprep_validate(const char *str)
+{
+#ifdef USE_IDN
+	gboolean result;
+#else
+	const char *c;
+#endif
+
+	if(!str)
+		return TRUE;
+
+	if(strlen(str) > 1023)
+		return FALSE;
+
+#ifdef USE_IDN
+	strncpy(idn_buffer, str, sizeof(idn_buffer) - 1);
+	idn_buffer[sizeof(idn_buffer) - 1] = '\0';
+	result = jabber_resourceprep(idn_buffer, sizeof(idn_buffer));
+	return result;
+#else /* USE_IDN */
+	c = str;
+	while(c && *c) {
+		gunichar ch = g_utf8_get_char(c);
+		if(!g_unichar_isgraph(ch) && ch != ' ')
+			return FALSE;
+
+		c = g_utf8_next_char(c);
+	}
+
+	return TRUE;
+#endif /* USE_IDN */
+}
+
+char *jabber_saslprep(const char *in)
+{
+#ifdef USE_IDN
+	char *out;
+
+	g_return_val_if_fail(in != NULL, NULL);
+	g_return_val_if_fail(strlen(in) <= sizeof(idn_buffer) - 1, NULL);
+
+	strncpy(idn_buffer, in, sizeof(idn_buffer) - 1);
+	idn_buffer[sizeof(idn_buffer) - 1] = '\0';
+
+	if (STRINGPREP_OK != stringprep(idn_buffer, sizeof(idn_buffer), 0,
+	                                stringprep_saslprep)) {
+		memset(idn_buffer, 0, sizeof(idn_buffer));
+		return NULL;
+	}
+
+	out = g_strdup(idn_buffer);
+	memset(idn_buffer, 0, sizeof(idn_buffer));
+	return out;
+#else /* USE_IDN */
+	/* TODO: Something better than disallowing all non-ASCII characters */
+	/* TODO: Is this even correct? */
+	const guchar *c;
+
+	c = (const guchar *)in;
+	while (*c) {
+		if (*c > 0x7f ||
+				(*c < 0x20 && *c != '\t' && *c != '\n' && *c != '\r'))
+			return NULL;
+	}
+
+	return g_strdup(in);
+#endif /* USE_IDN */
+}
+
+static JabberID*
+jabber_id_new_internal(const char *str, gboolean allow_terminating_slash)
+{
+	const char *at = NULL;
+	const char *slash = NULL;
+	const char *c;
+	gboolean needs_validation = FALSE;
+#if 0
+	gboolean node_is_required = FALSE;
+#endif
+#ifndef USE_IDN
+	char *node = NULL;
+	char *domain;
+#endif
+	JabberID *jid;
+
+	if (!str)
+		return NULL;
+
+	for (c = str; *c != '\0'; c++)
+	{
+		switch (*c) {
+			case '@':
+				if (!slash) {
+					if (at) {
+						/* Multiple @'s in the node/domain portion, not a valid JID! */
+						return NULL;
+					}
+					if (c == str) {
+						/* JIDs cannot start with @ */
+						return NULL;
+					}
+					if (c[1] == '\0') {
+						/* JIDs cannot end with @ */
+						return NULL;
+					}
+					at = c;
+				}
+				break;
+
+			case '/':
+				if (!slash) {
+					if (c == str) {
+						/* JIDs cannot start with / */
+						return NULL;
+					}
+					if (c[1] == '\0' && !allow_terminating_slash) {
+						/* JIDs cannot end with / */
+						return NULL;
+					}
+					slash = c;
+				}
+				break;
+
+			default:
+				/* characters allowed everywhere */
+				if ((*c >= 'a' && *c <= 'z')
+						|| (*c >= '0' && *c <= '9')
+						|| (*c >= 'A' && *c <= 'Z')
+						|| *c == '.' || *c == '-')
+					/* We're good */
+					break;
+
+#if 0
+				if (slash != NULL) {
+					/* characters allowed only in the resource */
+					if (implement_me)
+						/* We're good */
+						break;
+				}
+
+				/* characters allowed only in the node */
+				if (implement_me) {
+					/*
+					 * Ok, this character is valid, but only if it's a part
+					 * of the node and not the domain.  But we don't know
+					 * if "c" is a part of the node or the domain until after
+					 * we've found the @.  So set a flag for now and check
+					 * that we found an @ later.
+					 */
+					node_is_required = TRUE;
+					break;
+				}
+#endif
+
+				/*
+				 * Hmm, this character is a bit more exotic.  Better fall
+				 * back to using the more expensive UTF-8 compliant
+				 * stringprep functions.
+				 */
+				needs_validation = TRUE;
+				break;
+		}
+	}
+
+#if 0
+	if (node_is_required && at == NULL)
+		/* Found invalid characters in the domain */
+		return NULL;
+#endif
+
+	if (!needs_validation) {
+		/* JID is made of only ASCII characters--just lowercase and return */
+		jid = g_new0(JabberID, 1);
+
+		if (at) {
+			jid->node = g_ascii_strdown(str, at - str);
+			if (slash) {
+				jid->domain = g_ascii_strdown(at + 1, slash - (at + 1));
+				if (*(slash + 1))
+					jid->resource = g_strdup(slash + 1);
+			} else {
+				jid->domain = g_ascii_strdown(at + 1, -1);
+			}
+		} else {
+			if (slash) {
+				jid->domain = g_ascii_strdown(str, slash - str);
+				if (*(slash + 1))
+					jid->resource = g_strdup(slash + 1);
+			} else {
+				jid->domain = g_ascii_strdown(str, -1);
+			}
+		}
+		return jid;
+	}
+
+	/*
+	 * If we get here, there are some non-ASCII chars in the string, so
+	 * we'll need to validate it, normalize, and finally do a full jabber
+	 * nodeprep on the jid.
+	 */
+
+	if (!g_utf8_validate(str, -1, NULL))
+		return NULL;
+
+#ifdef USE_IDN
+	return jabber_idn_validate(str, at, slash, c /* points to the null */);
+#else /* USE_IDN */
+
+	jid = g_new0(JabberID, 1);
+
+	/* normalization */
+	if(at) {
+		node = g_utf8_casefold(str, at-str);
+		if(slash) {
+			domain = g_utf8_casefold(at+1, slash-(at+1));
+			if (*(slash + 1))
+				jid->resource = g_utf8_normalize(slash+1, -1, G_NORMALIZE_NFKC);
+		} else {
+			domain = g_utf8_casefold(at+1, -1);
+		}
+	} else {
+		if(slash) {
+			domain = g_utf8_casefold(str, slash-str);
+			if (*(slash + 1))
+				jid->resource = g_utf8_normalize(slash+1, -1, G_NORMALIZE_NFKC);
+		} else {
+			domain = g_utf8_casefold(str, -1);
+		}
+	}
+
+	if (node) {
+		jid->node = g_utf8_normalize(node, -1, G_NORMALIZE_NFKC);
+		g_free(node);
+	}
+
+	if (domain) {
+		jid->domain = g_utf8_normalize(domain, -1, G_NORMALIZE_NFKC);
+		g_free(domain);
+	}
+
+	/* and finally the jabber nodeprep */
+	if(!jabber_nodeprep_validate(jid->node) ||
+			!jabber_domain_validate(jid->domain) ||
+			!jabber_resourceprep_validate(jid->resource)) {
+		jabber_id_free(jid);
+		return NULL;
+	}
+
+	return jid;
+#endif /* USE_IDN */
+}
+
+void
+jabber_id_free(JabberID *jid)
+{
+	if(jid) {
+		if(jid->node)
+			g_free(jid->node);
+		if(jid->domain)
+			g_free(jid->domain);
+		if(jid->resource)
+			g_free(jid->resource);
+		g_free(jid);
+	}
+}
+
+char *jabber_get_domain(const char *in)
+{
+	JabberID *jid = jabber_id_new(in);
+	char *out;
+
+	if (!jid)
+		return NULL;
+
+	out = g_strdup(jid->domain);
+	jabber_id_free(jid);
+
+	return out;
+}
+
+char *jabber_get_resource(const char *in)
+{
+	JabberID *jid = jabber_id_new(in);
+	char *out;
+
+	if(!jid)
+		return NULL;
+
+	out = g_strdup(jid->resource);
+	jabber_id_free(jid);
+
+	return out;
+}
+
+char *
+jabber_get_bare_jid(const char *in)
+{
+	JabberID *jid = jabber_id_new(in);
+	char *out;
+
+	if (!jid)
+		return NULL;
+	out = jabber_id_get_bare_jid(jid);
+	jabber_id_free(jid);
+
+	return out;
+}
+
+char *
+jabber_id_get_bare_jid(const JabberID *jid)
+{
+	g_return_val_if_fail(jid != NULL, NULL);
+
+	return g_strconcat(jid->node ? jid->node : "",
+	                   jid->node ? "@" : "",
+	                   jid->domain,
+	                   NULL);
+}
+
+gboolean
+jabber_jid_is_domain(const char *jid)
+{
+	const char *c;
+
+	for (c = jid; *c; ++c) {
+		if (*c == '@' || *c == '/')
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+
+JabberID *
+jabber_id_new(const char *str)
+{
+	return jabber_id_new_internal(str, FALSE);
+}
+
+const char *jabber_normalize(const PurpleAccount *account, const char *in)
+{
+	PurpleConnection *gc = account ? account->gc : NULL;
+	JabberStream *js = gc ? gc->proto_data : NULL;
+	static char buf[3072]; /* maximum legal length of a jabber jid */
+	JabberID *jid;
+
+	jid = jabber_id_new_internal(in, TRUE);
+	if(!jid)
+		return NULL;
+
+	if(js && jid->node && jid->resource &&
+			jabber_chat_find(js, jid->node, jid->domain))
+		g_snprintf(buf, sizeof(buf), "%s@%s/%s", jid->node, jid->domain,
+				jid->resource);
+	else
+		g_snprintf(buf, sizeof(buf), "%s%s%s", jid->node ? jid->node : "",
+				jid->node ? "@" : "", jid->domain);
+
+	jabber_id_free(jid);
+
+	return buf;
+}
+
+gboolean
+jabber_is_own_server(JabberStream *js, const char *str)
+{
+	JabberID *jid;
+	gboolean equal;
+
+	if (str == NULL)
+		return FALSE;
+
+	g_return_val_if_fail(*str != '\0', FALSE);
+
+	jid = jabber_id_new(str);
+	if (!jid)
+		return FALSE;
+
+	equal = (jid->node == NULL &&
+	         g_str_equal(jid->domain, js->user->domain) &&
+	         jid->resource == NULL);
+	jabber_id_free(jid);
+	return equal;
+}
+
+gboolean
+jabber_is_own_account(JabberStream *js, const char *str)
+{
+	JabberID *jid;
+	gboolean equal;
+
+	if (str == NULL)
+		return TRUE;
+
+	g_return_val_if_fail(*str != '\0', FALSE);
+
+	jid = jabber_id_new(str);
+	if (!jid)
+		return FALSE;
+
+	equal = (purple_strequal(jid->node, js->user->node) &&
+	         g_str_equal(jid->domain, js->user->domain) &&
+	         (jid->resource == NULL ||
+	             g_str_equal(jid->resource, js->user->resource)));
+	jabber_id_free(jid);
+	return equal;
+}
+
+/* The same as purple_util_get_image_checksum, but guaranteed to remain SHA1 */
+char *
+jabber_calculate_data_sha1sum(gconstpointer data, size_t len)
+{
+	PurpleCipherContext *context;
+	static gchar digest[41];
+
+	context = purple_cipher_context_new_by_name("sha1", NULL);
+	if (context == NULL)
+	{
+		purple_debug_error("jabber", "Could not find sha1 cipher\n");
+		g_return_val_if_reached(NULL);
+	}
+
+	/* Hash the data */
+	purple_cipher_context_append(context, data, len);
+	if (!purple_cipher_context_digest_to_str(context, sizeof(digest), digest, NULL))
+	{
+		purple_debug_error("jabber", "Failed to get SHA-1 digest.\n");
+		g_return_val_if_reached(NULL);
+	}
+	purple_cipher_context_destroy(context);
+
+	return g_strdup(digest);
+}
+
diff -Npur pidgin-2.6.6/libpurple/protocols/jabber/jutil.h pidgin-2.6.6-new/libpurple/protocols/jabber/jutil.h
--- pidgin-2.6.6/libpurple/protocols/jabber/jutil.h	2010-02-16 17:34:06.000000000 +0800
+++ pidgin-2.6.6-new/libpurple/protocols/jabber/jutil.h	2014-03-14 15:00:28.424253553 +0800
@@ -33,12 +33,23 @@ typedef struct _JabberID {
 #include "jabber.h"
 
 JabberID* jabber_id_new(const char *str);
+
+/**
+ * Compare two JIDs for equality.
+ *
+ * Warning: If either JID is NULL then this function uses the user's
+ * bare JID, instead!
+ */
+gboolean jabber_id_equal(JabberStream *js, const JabberID *jid1, const JabberID *jid2);
+
 void jabber_id_free(JabberID *jid);
 
 char *jabber_get_domain(const char *jid);
 char *jabber_get_resource(const char *jid);
 char *jabber_get_bare_jid(const char *jid);
 char *jabber_id_get_bare_jid(const JabberID *jid);
+char *jabber_id_get_full_jid(const JabberID *jid);
+JabberID *jabber_id_to_bare_jid(const JabberID *jid);
 
 gboolean jabber_jid_is_domain(const char *jid);
 
diff -Npur pidgin-2.6.6/libpurple/protocols/jabber/jutil.h.orig pidgin-2.6.6-new/libpurple/protocols/jabber/jutil.h.orig
--- pidgin-2.6.6/libpurple/protocols/jabber/jutil.h.orig	1970-01-01 08:00:00.000000000 +0800
+++ pidgin-2.6.6-new/libpurple/protocols/jabber/jutil.h.orig	2010-02-16 17:34:06.000000000 +0800
@@ -0,0 +1,67 @@
+/**
+ * @file jutil.h utility functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef PURPLE_JABBER_JUTIL_H_
+#define PURPLE_JABBER_JUTIL_H_
+
+typedef struct _JabberID {
+	char *node;
+	char *domain;
+	char *resource;
+} JabberID;
+
+#include "jabber.h"
+
+JabberID* jabber_id_new(const char *str);
+void jabber_id_free(JabberID *jid);
+
+char *jabber_get_domain(const char *jid);
+char *jabber_get_resource(const char *jid);
+char *jabber_get_bare_jid(const char *jid);
+char *jabber_id_get_bare_jid(const JabberID *jid);
+
+gboolean jabber_jid_is_domain(const char *jid);
+
+const char *jabber_normalize(const PurpleAccount *account, const char *in);
+
+/* Returns true if JID is the bare JID of our server. */
+gboolean jabber_is_own_server(JabberStream *js, const char *jid);
+
+/* Returns true if JID is the bare JID of our account. */
+gboolean jabber_is_own_account(JabberStream *js, const char *jid);
+
+gboolean jabber_nodeprep_validate(const char *);
+gboolean jabber_domain_validate(const char *);
+gboolean jabber_resourceprep_validate(const char *);
+
+/**
+ * Apply the SASLprep profile of stringprep to the string passed in.
+ *
+ * @returns A newly allocated string containing the normalized version
+ *          of the input, or NULL if an error occurred (the string could
+ *          not be normalized)
+ */
+char *jabber_saslprep(const char *);
+
+char *jabber_calculate_data_sha1sum(gconstpointer data, size_t len);
+#endif /* PURPLE_JABBER_JUTIL_H_ */
