changeset 25765:a259d2711416

merge of '0da3644551b2442b2db4cf9d828865d167df8072' and 'efc71f836438eb97ec8d2c8e0874326b6fc64774'
author Paul Aurich <paul@darkrain42.org>
date Sun, 23 Nov 2008 03:52:40 +0000
parents 6adbaf3d25e3 (current diff) 805aadbb1a85 (diff)
children d853f56dad8b
files libpurple/protocols/jabber/caps.c
diffstat 14 files changed, 414 insertions(+), 369 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/protocols/jabber/buddy.c	Sun Nov 23 03:52:33 2008 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Sun Nov 23 03:52:40 2008 +0000
@@ -176,9 +176,8 @@
 		g_free(cmd);
 		jbr->commands = g_list_delete_link(jbr->commands, jbr->commands);
 	}
-	
-	jabber_caps_free_clientinfo(jbr->caps);
 
+	/* jbr->caps is owned by the caps code */
 	g_free(jbr->name);
 	g_free(jbr->status);
 	g_free(jbr->thread_id);
--- a/libpurple/protocols/jabber/buddy.h	Sun Nov 23 03:52:33 2008 +0000
+++ b/libpurple/protocols/jabber/buddy.h	Sun Nov 23 03:52:40 2008 +0000
@@ -81,7 +81,7 @@
 		char *name;
 		char *os;
 	} client;
-	JabberCapsClientInfo *caps;
+	const JabberCapsClientInfo *caps;
 	GList *commands;
 } JabberBuddyResource;
 
--- a/libpurple/protocols/jabber/caps.c	Sun Nov 23 03:52:33 2008 +0000
+++ b/libpurple/protocols/jabber/caps.c	Sun Nov 23 03:52:40 2008 +0000
@@ -21,17 +21,35 @@
 
 #include "internal.h"
 
-#include <glib.h>
+#include "debug.h"
 #include "caps.h"
 #include "cipher.h"
-#include <string.h>
 #include "iq.h"
 #include "presence.h"
 #include "util.h"
 
 #define JABBER_CAPS_FILENAME "xmpp-caps.xml"
 
-GHashTable *capstable = NULL; /* JabberCapsKey -> JabberCapsValue */
+typedef struct _JabberDataFormField {
+	gchar *var;
+	GList *values;
+} JabberDataFormField;
+
+typedef struct _JabberCapsKey {
+	char *node;
+	char *ver;
+	char *hash;
+} JabberCapsKey;
+
+static GHashTable *capstable = NULL; /* JabberCapsKey -> JabberCapsClientInfo */
+
+/**
+ *	Processes a query-node and returns a JabberCapsClientInfo object with all relevant info.
+ *	
+ *	@param 	query 	A query object.
+ *	@return 		A JabberCapsClientInfo object.
+ */
+static JabberCapsClientInfo *jabber_caps_parse_client_info(xmlnode *query);
 
 #if 0
 typedef struct _JabberCapsValue {
@@ -40,7 +58,6 @@
 	GHashTable *ext; /* char * -> JabberCapsValueExt */
 } JabberCapsValue;
 #endif
-typedef struct _JabberCapsClientInfo JabberCapsValue;
 
 static guint jabber_caps_hash(gconstpointer key) {
 	const JabberCapsKey *name = key;
@@ -54,7 +71,9 @@
 	const JabberCapsKey *name1 = v1;
 	const JabberCapsKey *name2 = v2;
 	
-	return strcmp(name1->node,name2->node) == 0 && strcmp(name1->ver,name2->ver) == 0 && strcmp(name1->hash,name2->hash) == 0;
+	return strcmp(name1->node, name2->node) == 0 &&
+	       strcmp(name1->ver, name2->ver) == 0 &&
+	       strcmp(name1->hash, name2->hash) == 0;
 }
 
 void jabber_caps_destroy_key(gpointer key) {
@@ -65,28 +84,34 @@
 	g_free(keystruct);
 }
 
-static void jabber_caps_destroy_value(gpointer value) {
-	JabberCapsValue *valuestruct = value;
-	while(valuestruct->identities) {
-		JabberCapsIdentity *id = valuestruct->identities->data;
+static void
+jabber_caps_client_info_destroy(gpointer data) {
+	JabberCapsClientInfo *info = data;
+	while(info->identities) {
+		JabberIdentity *id = info->identities->data;
 		g_free(id->category);
 		g_free(id->type);
 		g_free(id->name);
+		g_free(id->lang);
 		g_free(id);
-		
-		valuestruct->identities = g_list_delete_link(valuestruct->identities,valuestruct->identities);
+		info->identities = g_list_delete_link(info->identities, info->identities);
 	}
-	while(valuestruct->features) {
-		g_free(valuestruct->features->data);
-		valuestruct->features = g_list_delete_link(valuestruct->features,valuestruct->features);
+
+	while(info->features) {
+		g_free(info->features->data);
+		info->features = g_list_delete_link(info->features, info->features);
 	}
 
-	while(valuestruct->forms) {
-		g_free(valuestruct->forms->data);
-		valuestruct->forms = g_list_delete_link(valuestruct->forms,valuestruct->forms);
+	while(info->forms) {
+		g_free(info->forms->data);
+		info->forms = g_list_delete_link(info->forms, info->forms);
 	}
-	//g_hash_table_destroy(valuestruct->ext);
-	g_free(valuestruct);
+
+#if 0
+	g_hash_table_destroy(valuestruct->ext);
+#endif
+
+	g_free(info);
 }
 
 #if 0
@@ -111,11 +136,18 @@
 
 static void jabber_caps_load(void);
 
-void jabber_caps_init(void) {
-	capstable = g_hash_table_new_full(jabber_caps_hash, jabber_caps_compare, jabber_caps_destroy_key, jabber_caps_destroy_value);
+void jabber_caps_init(void)
+{
+	capstable = g_hash_table_new_full(jabber_caps_hash, jabber_caps_compare, jabber_caps_destroy_key, jabber_caps_client_info_destroy);
 	jabber_caps_load();
 }
 
+void jabber_caps_uninit(void)
+{
+	g_hash_table_destroy(capstable);
+	capstable = NULL;
+}
+
 static void jabber_caps_load(void) {
 	xmlnode *capsdata = purple_util_read_xml_from_file(JABBER_CAPS_FILENAME, "XMPP capabilities cache");
 	xmlnode *client;
@@ -133,7 +165,7 @@
 			continue;
 		if(!strcmp(client->name, "client")) {
 			JabberCapsKey *key = g_new0(JabberCapsKey, 1);
-			JabberCapsValue *value = g_new0(JabberCapsValue, 1);
+			JabberCapsClientInfo *value = g_new0(JabberCapsClientInfo, 1);
 			xmlnode *child;
 			key->node = g_strdup(xmlnode_get_attrib(client,"node"));
 			key->ver  = g_strdup(xmlnode_get_attrib(client,"ver"));
@@ -150,11 +182,17 @@
 					const char *category = xmlnode_get_attrib(child, "category");
 					const char *type = xmlnode_get_attrib(child, "type");
 					const char *name = xmlnode_get_attrib(child, "name");
-					
-					JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1);
+					const char *lang = xmlnode_get_attrib(child, "lang");
+					JabberIdentity *id;
+
+					if (!category || !type)
+						continue;
+
+					id = g_new0(JabberIdentity, 1);
 					id->category = g_strdup(category);
 					id->type = g_strdup(type);
 					id->name = g_strdup(name);
+					id->lang = g_strdup(lang);
 					
 					value->identities = g_list_append(value->identities,id);
 				} else if(!strcmp(child->name,"x")) {
@@ -196,7 +234,7 @@
 
 static void jabber_caps_store_client(gpointer key, gpointer value, gpointer user_data) {
 	JabberCapsKey *clientinfo = key;
-	JabberCapsValue *props = value;
+	JabberCapsClientInfo *props = value;
 	xmlnode *root = user_data;
 	xmlnode *client = xmlnode_new_child(root, "client");
 	GList *iter;
@@ -205,12 +243,14 @@
 	xmlnode_set_attrib(client, "ver", clientinfo->ver);
 	xmlnode_set_attrib(client, "hash", clientinfo->hash);
 	for(iter = props->identities; iter; iter = g_list_next(iter)) {
-		JabberCapsIdentity *id = iter->data;
+		JabberIdentity *id = iter->data;
 		xmlnode *identity = xmlnode_new_child(client, "identity");
 		xmlnode_set_attrib(identity, "category", id->category);
 		xmlnode_set_attrib(identity, "type", id->type);
 		if (id->name)
 			xmlnode_set_attrib(identity, "name", id->name);
+		if (id->lang)
+			xmlnode_set_attrib(identity, "lang", id->lang);
 	}
 
 	for(iter = props->features; iter; iter = g_list_next(iter)) {
@@ -301,28 +341,6 @@
 }
 #endif
 
-void jabber_caps_free_clientinfo(JabberCapsClientInfo *clientinfo) {
-	if(!clientinfo)
-		return;
-	while(clientinfo->identities) {
-		JabberCapsIdentity *id = clientinfo->identities->data;
-		g_free(id->category);
-		g_free(id->type);
-		g_free(id->name);
-		g_free(id);
-		
-		clientinfo->identities = g_list_delete_link(clientinfo->identities,clientinfo->identities);
-	}
-	while(clientinfo->features) {
-		char *feat = clientinfo->features->data;
-		g_free(feat);
-		
-		clientinfo->features = g_list_delete_link(clientinfo->features,clientinfo->features);
-	}
-	
-	g_free(clientinfo);
-}
-
 typedef struct _jabber_caps_cbplususerdata {
 	jabber_caps_get_info_cb cb;
 	gpointer user_data;
@@ -331,13 +349,17 @@
 	char *node;
 	char *ver;
 	char *hash;
+#if 0
 	unsigned extOutstanding;
+#endif
 } jabber_caps_cbplususerdata;
 
+#if 0
 typedef struct jabber_ext_userdata {
 	jabber_caps_cbplususerdata *userdata;
 	char *node;
 } jabber_ext_userdata;
+#endif
 
 #if 0
 static void jabber_caps_get_info_check_completion(jabber_caps_cbplususerdata *userdata) {
@@ -418,122 +440,99 @@
 }
 #endif
 
-static void jabber_caps_client_iqcb(JabberStream *js, xmlnode *packet, gpointer data) {
-	/* collect data and fetch all exts */
+static void
+jabber_caps_client_iqcb(JabberStream *js, xmlnode *packet, gpointer data)
+{
 	xmlnode *query = xmlnode_get_child_with_namespace(packet, "query",
 		"http://jabber.org/protocol/disco#info");
 	jabber_caps_cbplususerdata *userdata = data;
+	JabberCapsClientInfo *info, *value;
+	gchar *hash;
+	const char *type = xmlnode_get_attrib(packet, "type");
+	JabberCapsKey key;
 
-	/* TODO: Better error checking! */
-	if (!strcmp(xmlnode_get_attrib(packet, "type"), "error"))return;
-	if (query) {
-		// check hash
-		JabberCapsClientInfo *info = jabber_caps_parse_client_info(query);
-		gchar *hash = 0;
-		JabberCapsValue *value;
-		JabberCapsKey *key;
-		xmlnode *child;
+	if (!query || !strcmp(type, "error")) {
+		userdata->cb(NULL, userdata->user_data);
+
+		g_free(userdata->who);
+		g_free(userdata->node);
+		g_free(userdata->ver);
+		g_free(userdata->hash);
+		g_free(userdata);
+		return;
+	}
 
-		if (!strcmp(userdata->hash, "sha-1")) {
-			hash = jabber_caps_calculate_hash(info, "sha1");
-		} else if (!strcmp(userdata->hash, "md5")) {
-			hash = jabber_caps_calculate_hash(info, "md5");
-		} else {
-			// clean up
-			return;	
-		}
+	/* check hash */
+	info = jabber_caps_parse_client_info(query);
+
+	if (!strcmp(userdata->hash, "sha-1")) {
+		hash = jabber_caps_calculate_hash(info, "sha1");
+	} else if (!strcmp(userdata->hash, "md5")) {
+		hash = jabber_caps_calculate_hash(info, "md5");
+	} else {
+		purple_debug_warning("jabber", "unknown caps hash algorithm: %s\n", userdata->hash);
 
-		printf("\n\tfrom:            %s", xmlnode_get_attrib(packet, "from"));
-		printf("\n\tnode:            %s", xmlnode_get_attrib(query, "node"));
-		printf("\n\tcalculated key:  %s", hash);
-		printf("\n\thash:            %s", userdata->hash);
-		printf("\n");
-		
-		if (strcmp(hash, userdata->ver)) {
-			g_free(info);
-			g_free(hash);
-			printf("\n! ! ! invalid hash ! ! !");
-			return;
-		}
-		
-		g_free(hash);
-		
-		value = g_new0(JabberCapsValue, 1);
-		key = g_new0(JabberCapsKey, 1);
+		userdata->cb(NULL, userdata->user_data);
+
+		jabber_caps_client_info_destroy(info);
+		g_free(userdata->who);
+		g_free(userdata->node);
+		g_free(userdata->ver);
+		g_free(userdata->hash);
+		g_free(userdata);		
+		return;	
+	}
 
-#if 0
-		value->ext = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, jabber_caps_ext_destroy_value);
-#endif
-		key->node = g_strdup(userdata->node);
-		key->ver = g_strdup(userdata->ver);
-		key->hash = g_strdup(userdata->hash);
-		
-		/* check whether it's stil not in the table */
-		if (g_hash_table_lookup(capstable, key)) {
-			jabber_caps_destroy_key(key);
-			g_free(value);
-			return;
-		}
-		
-		
-		for(child = query->child; child; child = child->next) {
-			if(child->type != XMLNODE_TYPE_TAG)
-				continue;
-			if(!strcmp(child->name,"feature")) {
-				const char *var = xmlnode_get_attrib(child, "var");
-				if(!var)
-					continue;
-				value->features = g_list_append(value->features, g_strdup(var));
-			} else if(!strcmp(child->name,"identity")) {
-				const char *category = xmlnode_get_attrib(child, "category");
-				const char *type = xmlnode_get_attrib(child, "type");
-				const char *name = xmlnode_get_attrib(child, "name");
-				
-				JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1);
-				id->category = g_strdup(category);
-				id->type = g_strdup(type);
-				id->name = g_strdup(name);
+	if (!hash || strcmp(hash, userdata->ver)) {
+		purple_debug_warning("jabber", "caps hash from %s did not match\n", xmlnode_get_attrib(packet, "from"));
+		userdata->cb(NULL, userdata->user_data);
+
+		jabber_caps_client_info_destroy(info);
+		g_free(userdata->who);
+		g_free(userdata->node);
+		g_free(userdata->ver);
+		g_free(userdata->hash);
+		g_free(userdata);		
+		g_free(hash);
+		return;
+	}
+
+	key.node = userdata->node;
+	key.ver = userdata->ver;
+	key.hash = userdata->hash;
 
-				value->identities = g_list_append(value->identities,id);
-			} else if(!strcmp(child->name, "x")) {
-				value->forms = g_list_append(value->forms, xmlnode_copy(child));	
-			}
-		}
-
-		g_hash_table_replace(capstable, key, value);
-		jabber_caps_store();
-
+	/* check whether it's not in the table */
+	if ((value = g_hash_table_lookup(capstable, &key))) {
+		JabberCapsClientInfo *tmp = info;
+		info = value;
+		jabber_caps_client_info_destroy(tmp);
+	} else {
+		JabberCapsKey *n_key = g_new(JabberCapsKey, 1);
+		n_key->node = userdata->node;
+		n_key->ver = userdata->ver;
+		n_key->hash = userdata->hash;
+		userdata->node = userdata->ver = userdata->hash = NULL;
 
-#if 0
-		/* fetch all exts */
-		for(iter = userdata->ext; iter; iter = g_list_next(iter)) {
-			JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_GET,
-				"http://jabber.org/protocol/disco#info");
-			xmlnode *query = xmlnode_get_child_with_namespace(iq->node,
-				"query", "http://jabber.org/protocol/disco#info");
-			char *node = g_strdup_printf("%s#%s", userdata->node, (const char*)iter->data);
-			jabber_ext_userdata *ext_data = g_new0(jabber_ext_userdata, 1);
-			ext_data->node = node;
-			ext_data->userdata = userdata;
+		g_hash_table_insert(capstable, n_key, info);
+		jabber_caps_store();
+	}
+
+	userdata->cb(info, userdata->user_data);
 
-			xmlnode_set_attrib(query, "node", node);
-			xmlnode_set_attrib(iq->node, "to", userdata->who);
-
-			jabber_iq_set_callback(iq, jabber_caps_ext_iqcb, ext_data);
-			jabber_iq_send(iq);
-		}
-
-	} else
-		/* Don't wait for the ext discoveries; they aren't going to happen */
-		userdata->extOutstanding = 0;
-
-	jabber_caps_get_info_check_completion(userdata);
-#endif
-	}
+	/* capstable will free info */
+	g_free(userdata->who);
+	g_free(userdata->node);
+	g_free(userdata->ver);
+	g_free(userdata->hash);	
+	g_free(userdata);
+	g_free(hash);
 }
 
-void jabber_caps_get_info(JabberStream *js, const char *who, const char *node, const char *ver, const char *hash, jabber_caps_get_info_cb cb, gpointer user_data) {
-	JabberCapsValue *client;
+void jabber_caps_get_info(JabberStream *js, const char *who, const char *node,
+		const char *ver, const char *hash, jabber_caps_get_info_cb cb,
+		gpointer user_data)
+{
+	JabberCapsClientInfo *client;
 	JabberCapsKey *key = g_new0(JabberCapsKey, 1);
 	jabber_caps_cbplususerdata *userdata = g_new0(jabber_caps_cbplususerdata, 1);
 	userdata->cb = cb;
@@ -548,8 +547,6 @@
 	key->hash = g_strdup(hash);
 	
 	client = g_hash_table_lookup(capstable, key);
-
-	g_hash_table_replace(jabber_contact_info, g_strdup(who), key);
 	
 	if(!client) {
 		JabberIq *iq = jabber_iq_new_query(js,JABBER_IQ_GET,"http://jabber.org/protocol/disco#info");
@@ -607,10 +604,18 @@
 	
 	ac = a;
 	bc = b;
-	
+
 	if ((cat_cmp = strcmp(ac->category, bc->category)) == 0) {
 		if ((typ_cmp = strcmp(ac->type, bc->type)) == 0) {
-			return strcmp(ac->lang, bc->lang);
+			if (!ac->lang && !bc->lang) {
+				return 0;
+			} else if (ac->lang && !bc->lang) {
+				return 1;
+			} else if (!ac->lang && bc->lang) {
+				return -1;
+			} else {
+				return strcmp(ac->lang, bc->lang);
+			}
 		} else {
 			return typ_cmp;
 		}
@@ -631,16 +636,6 @@
 }
 #endif
 
-static gint jabber_caps_string_compare(gconstpointer a, gconstpointer b) {
-	const gchar *ac;
-	const gchar *bc;
-	
-	ac = a;
-	bc = b;
-	
-	return strcmp(ac, bc);
-}
-
 static gchar *jabber_caps_get_formtype(const xmlnode *x) {
 	xmlnode *formtypefield;
 	formtypefield = xmlnode_get_child(x, "field");
@@ -665,12 +660,13 @@
 	return result;
 }
 
-JabberCapsClientInfo *jabber_caps_parse_client_info(xmlnode *query) {
+static JabberCapsClientInfo *jabber_caps_parse_client_info(xmlnode *query)
+{
 	xmlnode *child;
 	JabberCapsClientInfo *info;
 
-	if (!query) return 0;
-	if (strcmp(query->xmlns,"http://jabber.org/protocol/disco#info")) return 0;
+	if (!query || strcmp(query->xmlns, "http://jabber.org/protocol/disco#info"))
+		return 0;
 	
 	info = g_new0(JabberCapsClientInfo, 1);
 	
@@ -680,12 +676,18 @@
 			const char *category = xmlnode_get_attrib(child, "category");
 			const char *type = xmlnode_get_attrib(child, "type");
 			const char *name = xmlnode_get_attrib(child, "name");
+			const char *lang = xmlnode_get_attrib(child, "lang");
+			JabberIdentity *id;
 
-			JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1);
+			if (!category || !type)
+				continue;
+
+			id = g_new0(JabberIdentity, 1);
 			id->category = g_strdup(category);
 			id->type = g_strdup(type);
 			id->name = g_strdup(name);
-			
+			id->lang = g_strdup(lang);
+
 			info->identities = g_list_append(info->identities, id);
 		} else if (!strcmp(child->name, "feature")) {
 			/* parse feature */
@@ -694,7 +696,7 @@
 				continue;
 			info->features = g_list_append(info->features, g_strdup(var));
 		} else if (!strcmp(child->name, "x")) {
-			if (!strcmp(child->xmlns, "jabber:x:data")) {
+			if (child->xmlns && !strcmp(child->xmlns, "jabber:x:data")) {
 				/* x-data form */
 				xmlnode *dataform = xmlnode_copy(child);
 				info->forms = g_list_append(info->forms, dataform);
@@ -704,158 +706,154 @@
 	return info;
 }
 
-static gint jabber_caps_xdata_field_compare(gconstpointer a, gconstpointer b) {
-	const JabberDataFormField *ac;
-	const JabberDataFormField *bc;
-	
-	ac = a;
-	bc = b;
-	
+static gint jabber_caps_xdata_field_compare(gconstpointer a, gconstpointer b)
+{
+	const JabberDataFormField *ac = a;
+	const JabberDataFormField *bc = b;
+
 	return strcmp(ac->var, bc->var);
 }
 
-static GList *jabber_caps_xdata_get_fields(const xmlnode *x) {
-	GList *fields = 0;
+static GList* jabber_caps_xdata_get_fields(const xmlnode *x)
+{
+	GList *fields = NULL;
 	xmlnode *field;
-	xmlnode *value;
-	JabberDataFormField *xdatafield;
-	
-	if(!x) return 0;
-	
-	for(field = xmlnode_get_child(x, "field"); field != 0; field = xmlnode_get_next_twin(field)) {
-		xdatafield = g_new0(JabberDataFormField, 1);
+
+	if (!x)
+		return NULL;
+
+	for (field = xmlnode_get_child(x, "field"); field; field = xmlnode_get_next_twin(field)) {
+		xmlnode *value;
+		JabberDataFormField *xdatafield = g_new0(JabberDataFormField, 1);
 		xdatafield->var = g_strdup(xmlnode_get_attrib(field, "var"));
-		for(value = xmlnode_get_child(field, "value"); value != 0; value = xmlnode_get_next_twin(value)) {
+
+		for (value = xmlnode_get_child(field, "value"); value; value = xmlnode_get_next_twin(value)) {
 			gchar *val = xmlnode_get_data(value);
 			xdatafield->values = g_list_append(xdatafield->values, val);
 		}
-		xdatafield->values = g_list_sort(xdatafield->values, jabber_caps_string_compare);
+
+		xdatafield->values = g_list_sort(xdatafield->values, (GCompareFunc)strcmp);
 		fields = g_list_append(fields, xdatafield);
-	} 
+	}
+
 	fields = g_list_sort(fields, jabber_caps_xdata_field_compare);
 	return fields;
 }
 
-static gchar *jabber_caps_verification_append(gchar *verification_string, gchar *string) {
-	gchar *verification;
-	verification = g_strconcat(verification_string, string, "<", NULL);
-	g_free(verification_string);
-	return verification;
+static GString*
+jabber_caps_verification_append(GString *verification, const gchar *string)
+{
+	verification = g_string_append(verification, string);
+	return g_string_append_c(verification, '<');
 }
 
-gchar *jabber_caps_calculate_hash(JabberCapsClientInfo *info, const char *hash) {
-	GList *identities;
-	GList *features;
-	GList *xdata;
-	gchar *verification = 0;
-	gchar *feature_string;
-	gchar *free_verification;
-	gchar *identity_string;
+gchar *jabber_caps_calculate_hash(JabberCapsClientInfo *info, const char *hash)
+{
+	GList *node;
+	GString *verification;
 	PurpleCipherContext *context;
 	guint8 checksum[20];
 	gsize checksum_size = 20;
-	
-	if (!info) return 0;
-	
+
+	if (!info || !(context = purple_cipher_context_new_by_name(hash, NULL)))
+		return NULL;
+
 	/* sort identities, features and x-data forms */
 	info->identities = g_list_sort(info->identities, jabber_caps_jabber_identity_compare);
-	info->features = g_list_sort(info->features, jabber_caps_string_compare);
+	info->features = g_list_sort(info->features, (GCompareFunc)strcmp);
 	info->forms = g_list_sort(info->forms, jabber_caps_jabber_xdata_compare);
-	
+
+	verification = g_string_new("");
+
 	/* concat identities to the verification string */
-	for(identities = info->identities; identities; identities = identities->next) {
-		JabberIdentity *ident = (JabberIdentity*)identities->data;
-		identity_string = g_strdup_printf("%s/%s//%s<", ident->category, ident->type, ident->name);
-		free_verification = verification;
-		if(verification == 0) verification = g_strdup(identity_string);
-		 	else verification = g_strconcat(verification, identity_string, NULL);
-		g_free(identity_string);
-		g_free(free_verification);
+	for (node = info->identities; node; node = node->next) {
+		JabberIdentity *id = (JabberIdentity*)node->data;
+
+		g_string_append_printf(verification, "%s/%s/%s/%s<", id->category,
+		        id->type, id->lang ? id->lang : "", id->name);
 	}
-	
+
 	/* concat features to the verification string */
-	for(features = info->features; features; features = features->next) {
-		feature_string = g_strdup_printf("%s", (gchar*)features->data);
-		verification = jabber_caps_verification_append(verification, feature_string);
-		g_free(feature_string);
+	for (node = info->features; node; node = node->next) {
+		verification = jabber_caps_verification_append(verification, node->data);
 	}
-	
+
 	/* concat x-data forms to the verification string */
-	for(xdata = info->forms; xdata; xdata = xdata->next) {
-		gchar *formtype = 0;
-		GList *fields;
+	for(node = info->forms; node; node = node->next) {
+		xmlnode *data = (xmlnode *)node->data;
+		gchar *formtype = jabber_caps_get_formtype(data);
+		GList *fields = jabber_caps_xdata_get_fields(data);
+
 		/* append FORM_TYPE's field value to the verification string */
-		formtype = jabber_caps_get_formtype((xmlnode*)xdata->data);
 		verification = jabber_caps_verification_append(verification, formtype);
 		g_free(formtype);
-		
-		for(fields = jabber_caps_xdata_get_fields((xmlnode*)(xdata->data)); fields != 0; fields = fields->next) {
+
+		while (fields) {
 			GList *value;
 			JabberDataFormField *field = (JabberDataFormField*)fields->data; 
-			if(strcmp(field->var, "FORM_TYPE")) {
-				/* Append the value of the "var" attribute, followed by the '<' character. */
+
+			if (strcmp(field->var, "FORM_TYPE")) {
+				/* Append the "var" attribute */
 				verification = jabber_caps_verification_append(verification, field->var);
-				/* For each <value/> element, append the XML character data, followed by the '<' character. */
-				for(value = field->values; value != 0; value = value->next) {
+				/* Append <value/> element's cdata */
+				for(value = field->values; value; value = value->next) {
 					verification = jabber_caps_verification_append(verification, value->data);
+					g_free(value->data);
 				}
 			}
-			for(value = field->values; value != 0; value = value->next) {
-				g_free(value->data);
-			}
+
 			g_free(field->var);
 			g_list_free(field->values);
+
+			fields = g_list_delete_link(fields, fields);
 		}
-		g_list_free(fields);
-	}
-	
-	/* generate hash */
-	context = purple_cipher_context_new_by_name(hash, NULL);
-	if (context == NULL) {
-		//purple_debug_error("jabber", "Could not find cipher\n");
-		return 0;
 	}
-	purple_cipher_context_append(context, (guchar*)verification, strlen(verification));
-	
-	if (!purple_cipher_context_digest(context, strlen(verification), checksum, &checksum_size)) {
-		//purple_debug_error("util", "Failed to get digest.\n");
+
+	/* generate hash */
+	purple_cipher_context_append(context, (guchar*)verification->str, verification->len);
+
+	if (!purple_cipher_context_digest(context, verification->len, checksum, &checksum_size)) {
+		/* purple_debug_error("util", "Failed to get digest.\n"); */
+		g_string_free(verification, TRUE);
+		purple_cipher_context_destroy(context);
+		return NULL;
 	}
+
+	g_string_free(verification, TRUE);
 	purple_cipher_context_destroy(context);
-	
-	/* apply Base64 on hash */
-	
-	g_free(verification);
-	verification = purple_base64_encode(checksum, checksum_size);
-	
-	return verification;
+
+	return purple_base64_encode(checksum, checksum_size);
 }
 
 void jabber_caps_calculate_own_hash(JabberStream *js) {
-	JabberCapsClientInfo *info;
+	JabberCapsClientInfo info;
 	GList *iter = 0;
 	GList *features = 0;
 
-	if (jabber_identities == 0 && jabber_features == 0) return;
+	if (!jabber_identities && !jabber_features) {
+		/* This really shouldn't ever happen */
+		purple_debug_warning("jabber", "No features or identities, cannot calculate own caps hash.\n");
+		g_free(js->caps_hash);
+		js->caps_hash = NULL;
+		return;
+	}
 
-	/* sort features */
+	/* build the currently-supported list of features */
 	if (jabber_features) {
 		for (iter = jabber_features; iter; iter = iter->next) {
 			JabberFeature *feat = iter->data;
-			if(feat->is_enabled == NULL || feat->is_enabled(js, feat->namespace) == TRUE) {
+			if(!feat->is_enabled || feat->is_enabled(js, feat->namespace)) {
 				features = g_list_append(features, feat->namespace);
 			}
 		}
 	}
 
-	info = g_new0(JabberCapsClientInfo, 1);
-	info->features = features;
-	info->identities = jabber_identities;
-	info->forms = 0;
-	
-	if (js->caps_hash)
-		g_free(js->caps_hash);
-	js->caps_hash = jabber_caps_calculate_hash(info, "sha1");
-	g_free(info);
+	info.features = features;
+	info.identities = jabber_identities;
+	info.forms = NULL;
+
+	g_free(js->caps_hash);
+	js->caps_hash = jabber_caps_calculate_hash(&info, "sha1");
 	g_list_free(features);
 }
 
--- a/libpurple/protocols/jabber/caps.h	Sun Nov 23 03:52:33 2008 +0000
+++ b/libpurple/protocols/jabber/caps.h	Sun Nov 23 03:52:40 2008 +0000
@@ -26,33 +26,22 @@
 
 #include "jabber.h"
 
-/* Implementation of XEP-0115 */
-extern GHashTable *capstable;
-
-typedef struct _JabberIdentity JabberCapsIdentity;
+/* Implementation of XEP-0115 - Entity Capabilities */
 
 struct _JabberCapsClientInfo {
-	GList *identities; /* JabberCapsIdentity */
+	GList *identities; /* JabberIdentity */
 	GList *features; /* char * */
 	GList *forms; /* xmlnode * */
 };
 
+#if 0
 typedef struct _JabberCapsClientInfo JabberCapsValueExt;
-
-typedef struct _JabberDataFormField {
-	gchar *var;
-	GList *values;
-} JabberDataFormField;
-
-typedef struct _JabberCapsKey {
-	char *node;
-	char *ver;
-	char *hash;
-} JabberCapsKey;
+#endif
 
 typedef void (*jabber_caps_get_info_cb)(JabberCapsClientInfo *info, gpointer user_data);
 
 void jabber_caps_init(void);
+void jabber_caps_uninit(void);
 
 void jabber_caps_destroy_key(gpointer value);
 
@@ -60,15 +49,6 @@
  *	Main entity capabilites function to get the capabilities of a contact.
  */
 void jabber_caps_get_info(JabberStream *js, const char *who, const char *node, const char *ver, const char *hash, jabber_caps_get_info_cb cb, gpointer user_data);
-void jabber_caps_free_clientinfo(JabberCapsClientInfo *clientinfo);
-
-/**
- *	Processes a query-node and returns a JabberCapsClientInfo object with all relevant info.
- *	
- *	@param 	query 	A query object.
- *	@return 		A JabberCapsClientInfo object.
- */
-JabberCapsClientInfo *jabber_caps_parse_client_info(xmlnode *query);
 
 /**
  *	Takes a JabberCapsClientInfo pointer and returns the caps hash according to
--- a/libpurple/protocols/jabber/data.c	Sun Nov 23 03:52:33 2008 +0000
+++ b/libpurple/protocols/jabber/data.c	Sun Nov 23 03:52:40 2008 +0000
@@ -244,4 +244,5 @@
 	g_hash_table_destroy(local_data_by_alt);
 	g_hash_table_destroy(local_data_by_cid);
 	g_hash_table_destroy(remote_data_by_cid);
+	local_data_by_alt = local_data_by_cid = remote_data_by_cid = NULL;
 }
--- a/libpurple/protocols/jabber/disco.c	Sun Nov 23 03:52:33 2008 +0000
+++ b/libpurple/protocols/jabber/disco.c	Sun Nov 23 03:52:40 2008 +0000
@@ -99,7 +99,7 @@
 		const char *node = NULL;
 		char *node_uri = NULL;
 		
-		// create custom caps node URI
+		/* create custom caps node URI */
 		node_uri = g_strconcat(CAPS0115_NODE, "#", jabber_caps_get_own_hash(js), NULL);
 
 		if((in_query = xmlnode_get_child(packet, "query"))) {
@@ -126,11 +126,14 @@
 				identity = xmlnode_new_child(query, "identity");
 				xmlnode_set_attrib(identity, "category", ident->category);
 				xmlnode_set_attrib(identity, "type", ident->type);
-				if (ident->name != 0) xmlnode_set_attrib(identity, "name", ident->name);
+				if (ident->lang)
+					xmlnode_set_attrib(identity, "xml:lang", ident->lang);
+				if (ident->name)
+					xmlnode_set_attrib(identity, "name", ident->name);
 			}
 			for(features = jabber_features; features; features = features->next) {
 				JabberFeature *feat = (JabberFeature*)features->data;
-				if(feat->is_enabled == NULL || feat->is_enabled(js, feat->namespace) == TRUE) {
+				if (!feat->is_enabled || feat->is_enabled(js, feat->namespace)) {
 					feature = xmlnode_new_child(query, "feature");
 					xmlnode_set_attrib(feature, "var", feat->namespace);
 				}	
--- a/libpurple/protocols/jabber/jabber.c	Sun Nov 23 03:52:33 2008 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Sun Nov 23 03:52:40 2008 +0000
@@ -67,8 +67,6 @@
 GList *jabber_features = NULL;
 GList *jabber_identities = NULL;
 
-GHashTable *jabber_contact_info = NULL;
-
 static void jabber_unregister_account_cb(JabberStream *js);
 static void try_srv_connect(JabberStream *js);
 
@@ -1520,7 +1518,17 @@
 	}
 }
 
-void jabber_add_identity(const gchar *category, const gchar *type, const gchar *name) {
+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_remove_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 */
@@ -1529,18 +1537,34 @@
 	
 	for(identity = jabber_identities; identity; identity = identity->next) {
 		JabberIdentity *ident = (JabberIdentity*)identity->data;
-		if(!strcmp(ident->category, category)) {
-			if (!strcmp(ident->type, type)) return;
+		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_append(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_remove_link(jabber_identities, jabber_identities);
+	}
+}
+
 const char *jabber_list_icon(PurpleAccount *a, PurpleBuddy *b)
 {
 	return "jabber";
@@ -2541,38 +2565,57 @@
 					  _("buzz: Buzz a user to get their attention"), NULL);
 }
 
-/* IPC functions*/
-
-/*
- * IPC function for checking wheather a client at a full JID supports a certain feature.
- * 
- * @param fulljid 	The full JID of the client.
- * @param featrure 	The feature's namespace.
- * 
+/* 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(gchar *fulljid, gchar *feature)
+jabber_ipc_contact_has_feature(PurpleAccount *account, const gchar *jid,
+                               const gchar *feature)
 {
-	JabberCapsKey *caps_info = NULL;
-	JabberCapsValueExt *capabilities = NULL;
-	
-	caps_info = g_hash_table_lookup(jabber_contact_info, fulljid);
-	
-	if (!caps_info) return FALSE;
-	capabilities = g_hash_table_lookup(capstable, caps_info);
-	
-	if (g_list_find_custom(capabilities->features, feature, (GCompareFunc)strcmp) == NULL) return FALSE ;
-	return TRUE;
+	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;
+
+	resource = jabber_get_resource(jid);
+	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);
+
+	if (!jbr->caps) {
+		/* TODO: fetch them? */
+		return FALSE;
+	}
+
+	return NULL != g_list_find_custom(jbr->caps->features, feature, (GCompareFunc)strcmp);
 }
 
 static void
-jabber_ipc_add_feature(gchar *feature) 
+jabber_ipc_add_feature(const gchar *feature)
 {
-	if (feature == 0) return;
+	if (!feature)
+		return;
 	jabber_add_feature(feature, 0);
-	
-	// send presence with new caps info for all connected accounts
+
+	/* send presence with new caps info for all connected accounts */
 	jabber_caps_broadcast_change();
 }
 
@@ -2581,7 +2624,7 @@
 {
 	my_protocol = plugin;
 
-	jabber_add_identity("client", "pc", PACKAGE);
+	jabber_add_identity("client", "pc", NULL, PACKAGE);
 
 	/* initialize jabber_features list */
 	jabber_add_feature("jabber:iq:last", 0);
@@ -2603,12 +2646,11 @@
 	jabber_add_feature("http://jabber.org/protocol/xhtml-im", 0);
 	jabber_add_feature("urn:xmpp:ping", 0);
 	
-	jabber_contact_info = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, jabber_caps_destroy_key);
-	
 	/* IPC functions */
 	purple_plugin_ipc_register(plugin, "contact_has_feature", PURPLE_CALLBACK(jabber_ipc_contact_has_feature),
-							 purple_marshal_BOOLEAN__POINTER_POINTER,
-							 purple_value_new(PURPLE_TYPE_BOOLEAN), 2,
+							 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),
@@ -2616,3 +2658,10 @@
 							 NULL, 1,
 							 purple_value_new(PURPLE_TYPE_STRING));
 }
+
+void
+jabber_uninit_plugin(void)
+{
+	jabber_features_destroy();
+	jabber_identities_destroy();
+}
--- a/libpurple/protocols/jabber/jabber.h	Sun Nov 23 03:52:33 2008 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Sun Nov 23 03:52:40 2008 +0000
@@ -279,8 +279,6 @@
 extern GList *jabber_features;
 extern GList *jabber_identities;
 
-extern GHashTable *jabber_contact_info; /* char * -> JabberCapsKey */
-
 void jabber_stream_features_parse(JabberStream *js, xmlnode *packet);
 void jabber_process_packet(JabberStream *js, xmlnode **packet);
 void jabber_send(JabberStream *js, xmlnode *data);
@@ -308,10 +306,11 @@
 /** Adds an identitiy to this jabber library instance. For list of valid values vistit the 
  *	webiste of the XMPP Registrar ( http://www.xmpp.org/registrar/disco-categories.html#client ).
  *  @param category the category of the identity.
- *	@param type the type of the identity.
- *	@param name the name of the identity.
+ *  @param type the type of the identity.
+ *  @param language the language localization of the name. Can be NULL.
+ *  @param name the name of the identity.
  */
-void jabber_add_identity(const gchar *category, const gchar *type, const gchar *name); 
+void jabber_add_identity(const gchar *category, const gchar *type, const gchar *lang, const gchar *name);
 
 /** PRPL functions */
 const char *jabber_list_icon(PurpleAccount *a, PurpleBuddy *b);
@@ -334,6 +333,8 @@
 int jabber_prpl_send_raw(PurpleConnection *gc, const char *buf, int len);
 GList *jabber_actions(PurplePlugin *plugin, gpointer context);
 void jabber_register_commands(void);
+
 void jabber_init_plugin(PurplePlugin *plugin);
+void jabber_uninit_plugin(void);
 
 #endif /* _PURPLE_JABBER_H_ */
--- a/libpurple/protocols/jabber/libxmpp.c	Sun Nov 23 03:52:33 2008 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Sun Nov 23 03:52:40 2008 +0000
@@ -148,9 +148,17 @@
 	purple_signal_unregister(plugin, "jabber-sending-xmlnode");
 	
 	purple_signal_unregister(plugin, "jabber-sending-text");
-	
+
+	/* reverse order of init_plugin */
 	jabber_data_uninit();
-	
+	/* PEP things should be uninit via jabber_pep_uninit, not here */
+	jabber_pep_uninit();
+	jabber_caps_uninit();
+	jabber_iq_uninit();
+
+	/* Stay on target...stay on target... Almost there... */
+	jabber_uninit_plugin();
+
 	return TRUE;
 }
 
@@ -272,11 +280,12 @@
 #endif
 #endif
 	jabber_register_commands();
-	
+
+	/* reverse order of unload_plugin */
 	jabber_iq_init();
+	jabber_caps_init();
+	/* PEP things should be init via jabber_pep_init, not here */
 	jabber_pep_init();
-	jabber_caps_init();
-	jabber_tune_init();
 	jabber_data_init();
 
 	#warning implement adding and retrieving own features via IPC API
--- a/libpurple/protocols/jabber/parser.c	Sun Nov 23 03:52:33 2008 +0000
+++ b/libpurple/protocols/jabber/parser.c	Sun Nov 23 03:52:40 2008 +0000
@@ -114,7 +114,8 @@
 		xmlnode *packet = js->current;
 		js->current = NULL;
 		jabber_process_packet(js, &packet);
-		xmlnode_free(packet);
+		if (packet != NULL)
+			xmlnode_free(packet);
 	}
 }
 
--- a/libpurple/protocols/jabber/pep.c	Sun Nov 23 03:52:33 2008 +0000
+++ b/libpurple/protocols/jabber/pep.c	Sun Nov 23 03:52:40 2008 +0000
@@ -26,6 +26,7 @@
 #include <string.h>
 #include "usermood.h"
 #include "usernick.h"
+#include "usertune.h"
 
 static GHashTable *pep_handlers = NULL;
 
@@ -35,10 +36,17 @@
 		
 		/* register PEP handlers */
 		jabber_mood_init();
+		jabber_tune_init();
 		jabber_nick_init();
 	}
 }
 
+void jabber_pep_uninit(void) {
+	/* any PEP handlers that need to clean things up go here */
+	g_hash_table_destroy(pep_handlers);
+	pep_handlers = NULL;
+}
+
 void jabber_pep_init_actions(GList **m) {
 	/* register the PEP-specific actions */
 	jabber_mood_init_action(m);
--- a/libpurple/protocols/jabber/pep.h	Sun Nov 23 03:52:33 2008 +0000
+++ b/libpurple/protocols/jabber/pep.h	Sun Nov 23 03:52:40 2008 +0000
@@ -27,6 +27,7 @@
 #include "buddy.h"
 
 void jabber_pep_init(void);
+void jabber_pep_uninit(void);
 
 void jabber_pep_init_actions(GList **m);
 
--- a/libpurple/protocols/jabber/presence.c	Sun Nov 23 03:52:33 2008 +0000
+++ b/libpurple/protocols/jabber/presence.c	Sun Nov 23 03:52:40 2008 +0000
@@ -382,38 +382,31 @@
 	char *from;
 } JabberPresenceCapabilities;
 
-static void jabber_presence_set_capabilities(JabberCapsClientInfo *info, gpointer user_data) {
-	JabberPresenceCapabilities *userdata = user_data;
-	JabberID *jid;
+static void jabber_presence_set_capabilities(JabberCapsClientInfo *info, JabberPresenceCapabilities *userdata)
+{
 	JabberBuddyResource *jbr;
-	GList *iter;
+	char *resource = g_utf8_strrchr(userdata->from, -1, '/');
+	resource += 1;
 
-	jid = jabber_id_new(userdata->from);
-	jbr = jabber_buddy_find_resource(userdata->jb, jid->resource);
-	jabber_id_free(jid);
-
-	if(!jbr) {
+	jbr = jabber_buddy_find_resource(userdata->jb, resource);
+	if (!jbr) {
 		g_free(userdata->from);
 		g_free(userdata);
 		return;
 	}
 
-	if(jbr->caps)
-		jabber_caps_free_clientinfo(jbr->caps);
+	/* old value in jbr->caps is owned by caps code */
 	jbr->caps = info;
 
 	if (info) {
-		for(iter = info->features; iter; iter = g_list_next(iter)) {
-			if(!strcmp((const char*)iter->data, "http://jabber.org/protocol/commands")) {
-				JabberIq *iq = jabber_iq_new_query(userdata->js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#items");
-				xmlnode *query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#items");
-				xmlnode_set_attrib(iq->node, "to", userdata->from);
-				xmlnode_set_attrib(query, "node", "http://jabber.org/protocol/commands");
-
-				jabber_iq_set_callback(iq, jabber_adhoc_disco_result_cb, NULL);
-				jabber_iq_send(iq);
-				break;
-			}
+		GList *node = g_list_find_custom(info->features, "http://jabber.org/protocol/commands", (GCompareFunc)strcmp);
+		if (node) {
+			JabberIq *iq = jabber_iq_new_query(userdata->js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#items");
+			xmlnode *query = xmlnode_get_child_with_namespace(iq->node, "query", "http://jabber.org/protocol/disco#items");
+			xmlnode_set_attrib(iq->node, "to", userdata->from);
+			xmlnode_set_attrib(query, "node", "http://jabber.org/protocol/commands");
+			jabber_iq_set_callback(iq, jabber_adhoc_disco_result_cb, NULL);
+			jabber_iq_send(iq);
 		}
 	}
 
@@ -468,7 +461,7 @@
 
 		if (buddy) {
 			jb = jabber_buddy_find(js, from, TRUE);
-			if ((jb->subscription & JABBER_SUB_TO))
+			if ((jb->subscription & (JABBER_SUB_TO | JABBER_SUB_PENDING)))
 				onlist = TRUE;
 		}
 
@@ -767,7 +760,9 @@
 					userdata->js = js;
 					userdata->jb = jb;
 					userdata->from = g_strdup(from);
-					jabber_caps_get_info(js, from, node, ver, hash, jabber_presence_set_capabilities, userdata);
+					jabber_caps_get_info(js, from, node, ver, hash,
+					    (jabber_caps_get_info_cb)jabber_presence_set_capabilities,
+					    userdata);
 				}
 			}
 		}
--- a/libpurple/protocols/sametime/sametime.c	Sun Nov 23 03:52:33 2008 +0000
+++ b/libpurple/protocols/sametime/sametime.c	Sun Nov 23 03:52:40 2008 +0000
@@ -1445,7 +1445,7 @@
 					 MW_PLUGIN_DEFAULT_HOST);
 
   if(purple_account_get_bool(account, MW_KEY_FORCE, FALSE) ||
-     (! strcmp(current_host, host)) ||
+     !host || (! strcmp(current_host, host)) ||
      (purple_proxy_connect(NULL, account, host, port, connect_cb, pd) == NULL)) {
 
     /* if we're configured to force logins, or if we're being