diff libpurple/protocols/jabber/jabber.c @ 19694:1d2002a5735e

propagate from branch 'im.pidgin.pidgin' (head 996cf0c57149ba6e1c714ebb1f11d5d4bac8fb68) to branch 'im.pidgin.soc.2007.xmpp' (head cdf63b6603891b8cd3e7f629ef5a9a927a153550)
author Andreas Monitzer <pidgin@monitzer.com>
date Wed, 05 Sep 2007 22:32:14 +0000
parents d470d42d2cb1 b0733d5d7621
children 481749fc0b6b
line wrap: on
line diff
--- a/libpurple/protocols/jabber/jabber.c	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Wed Sep 05 22:32:14 2007 +0000
@@ -10,12 +10,12 @@
  *
  * 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
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
  *
  */
 #include "internal.h"
@@ -51,19 +51,27 @@
 #include "presence.h"
 #include "jabber.h"
 #include "roster.h"
+#include "ping.h"
 #include "si.h"
 #include "xdata.h"
+#include "pep.h"
+#include "adhoccommands.h"
 
-#define JABBER_CONNECT_STEPS (js->gsc ? 8 : 5)
+#include <assert.h>
+
+#define JABBER_CONNECT_STEPS (js->gsc ? 9 : 5)
 
 static PurplePlugin *my_protocol = NULL;
+GList *jabber_features;
+
+static void jabber_unregister_account_cb(JabberStream *js);
 
 static void jabber_stream_init(JabberStream *js)
 {
 	char *open_stream;
 
 	open_stream = g_strdup_printf("<stream:stream to='%s' "
-				          "xmlns='jabber:client' "
+						  "xmlns='jabber:client' "
 						  "xmlns:stream='http://etherx.jabber.org/streams' "
 						  "version='1.0'>",
 						  js->user->domain);
@@ -80,6 +88,8 @@
 	const char *type = xmlnode_get_attrib(packet, "type");
 	if(type && !strcmp(type, "result")) {
 		jabber_stream_set_state(js, JABBER_STREAM_CONNECTED);
+		if(js->unregistration)
+			jabber_unregister_account_cb(js);
 	} else {
 		purple_connection_error(js->gc, _("Error initializing session"));
 	}
@@ -132,6 +142,9 @@
 	if(xmlnode_get_child(packet, "starttls")) {
 		if(jabber_process_starttls(js, packet))
 			return;
+	} else if(purple_account_get_bool(js->gc->account, "require_tls", FALSE) && !js->gsc) {
+		purple_connection_error(js->gc, _("You require encryption, but it is not available on this server."));
+		return;
 	}
 
 	if(js->registration) {
@@ -169,49 +182,49 @@
 
 static void tls_init(JabberStream *js);
 
-void jabber_process_packet(JabberStream *js, xmlnode *packet)
+void jabber_process_packet(JabberStream *js, xmlnode **packet)
 {
 	const char *xmlns;
 
-	purple_signal_emit(my_protocol, "jabber-receiving-xmlnode", js->gc, &packet);
+	purple_signal_emit(my_protocol, "jabber-receiving-xmlnode", js->gc, packet);
 
 	/* if the signal leaves us with a null packet, we're done */
-	if(NULL == packet)
+	if(NULL == *packet)
 		return;
 
-	xmlns = xmlnode_get_namespace(packet);
+	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") &&
+	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") &&
 		   !strcmp(xmlns, "http://etherx.jabber.org/streams")) {
-		jabber_stream_features_parse(js, packet);
-	} else if(!strcmp(packet->name, "stream:error") ||
-			 (!strcmp(packet->name, "error") &&
+		jabber_stream_features_parse(js, *packet);
+	} else if(!strcmp((*packet)->name, "stream:error") ||
+			 (!strcmp((*packet)->name, "error") &&
 				!strcmp(xmlns, "http://etherx.jabber.org/streams")))
 	{
-		jabber_stream_handle_error(js, packet);
-	} else if(!strcmp(packet->name, "challenge")) {
+		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")) {
+			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")) {
+			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")) {
+			jabber_auth_handle_failure(js, *packet);
+	} else if(!strcmp((*packet)->name, "proceed")) {
 		if(js->state == JABBER_STREAM_AUTHENTICATING && !js->gsc)
 			tls_init(js);
 	} else {
 		purple_debug(PURPLE_DEBUG_WARNING, "jabber", "Unknown packet: %s\n",
-				packet->name);
+				(*packet)->name);
 	}
 }
 
@@ -448,11 +461,14 @@
 	}	
 
 	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);
 }
 
 
@@ -554,6 +570,7 @@
 	js->user = jabber_id_new(purple_account_get_username(account));
 	js->next_id = g_random_int();
 	js->write_buffer = purple_circ_buffer_new(512);
+	js->old_length = -1;
 
 	if(!js->user) {
 		purple_connection_error(gc, _("Invalid XMPP ID"));
@@ -613,6 +630,8 @@
 {
 	JabberStream *js = data;
 	PurpleAccount *account = purple_connection_get_account(js->gc);
+	
+	jabber_parser_free(js);
 
 	purple_account_disconnect(account);
 
@@ -628,12 +647,21 @@
 static void
 jabber_registration_result_cb(JabberStream *js, xmlnode *packet, gpointer data)
 {
+	PurpleAccount *account = purple_connection_get_account(js->gc);
 	const char *type = xmlnode_get_attrib(packet, "type");
 	char *buf;
+	char *to = data;
 
 	if(!strcmp(type, "result")) {
-		buf = g_strdup_printf(_("Registration of %s@%s successful"),
+		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
+			buf = g_strdup_printf(_("Registration to %s successful"),
+				to);
 		purple_notify_info(NULL, _("Registration Successful"),
 				_("Registration Successful"), buf);
 		g_free(buf);
@@ -646,20 +674,56 @@
 		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);
 	}
-	jabber_connection_schedule_close(js);
+	g_free(to);
+	if(js->registration)
+		jabber_connection_schedule_close(js);
 }
 
 static void
-jabber_register_cb(JabberStream *js, PurpleRequestFields *fields)
+jabber_unregistration_result_cb(JabberStream *js, xmlnode *packet, gpointer data)
+{
+	const char *type = xmlnode_get_attrib(packet, "type");
+	char *buf;
+	char *to = data;
+	
+	if(!strcmp(type, "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);
+		
+		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(js, JABBER_IQ_SET, "jabber:iq:register");
+	iq = jabber_iq_new_query(cbdata->js, JABBER_IQ_SET, "jabber:iq:register");
 	query = xmlnode_get_child(iq->node, "query");
+	xmlnode_set_attrib(iq->node,"to",cbdata->who);
 
 	for(groups = purple_request_fields_get_groups(fields); groups;
 			groups = groups->next) {
@@ -667,215 +731,297 @@
 				flds; flds = flds->next) {
 			PurpleRequestField *field = flds->data;
 			const char *id = purple_request_field_get_id(field);
-			const char *value = purple_request_field_string_get_value(field);
-
-			if(!strcmp(id, "username")) {
-				y = xmlnode_new_child(query, "username");
-			} else if(!strcmp(id, "password")) {
-				y = xmlnode_new_child(query, "password");
-			} else if(!strcmp(id, "name")) {
-				y = xmlnode_new_child(query, "name");
-			} else if(!strcmp(id, "email")) {
-				y = xmlnode_new_child(query, "email");
-			} else if(!strcmp(id, "nick")) {
-				y = xmlnode_new_child(query, "nick");
-			} else if(!strcmp(id, "first")) {
-				y = xmlnode_new_child(query, "first");
-			} else if(!strcmp(id, "last")) {
-				y = xmlnode_new_child(query, "last");
-			} else if(!strcmp(id, "address")) {
-				y = xmlnode_new_child(query, "address");
-			} else if(!strcmp(id, "city")) {
-				y = xmlnode_new_child(query, "city");
-			} else if(!strcmp(id, "state")) {
-				y = xmlnode_new_child(query, "state");
-			} else if(!strcmp(id, "zip")) {
-				y = xmlnode_new_child(query, "zip");
-			} else if(!strcmp(id, "phone")) {
-				y = xmlnode_new_child(query, "phone");
-			} else if(!strcmp(id, "url")) {
-				y = xmlnode_new_child(query, "url");
-			} else if(!strcmp(id, "date")) {
-				y = xmlnode_new_child(query, "date");
+			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");
+					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 {
-				continue;
-			}
-			xmlnode_insert_data(y, value, -1);
-			if(!strcmp(id, "username")) {
-				if(js->user->node)
-					g_free(js->user->node);
-				js->user->node = g_strdup(value);
+				const char *value = purple_request_field_string_get_value(field);
+				
+				if(!strcmp(id, "username")) {
+					y = xmlnode_new_child(query, "username");
+				} else if(!strcmp(id, "password")) {
+					y = xmlnode_new_child(query, "password");
+				} else if(!strcmp(id, "name")) {
+					y = xmlnode_new_child(query, "name");
+				} else if(!strcmp(id, "email")) {
+					y = xmlnode_new_child(query, "email");
+				} else if(!strcmp(id, "nick")) {
+					y = xmlnode_new_child(query, "nick");
+				} else if(!strcmp(id, "first")) {
+					y = xmlnode_new_child(query, "first");
+				} else if(!strcmp(id, "last")) {
+					y = xmlnode_new_child(query, "last");
+				} else if(!strcmp(id, "address")) {
+					y = xmlnode_new_child(query, "address");
+				} else if(!strcmp(id, "city")) {
+					y = xmlnode_new_child(query, "city");
+				} else if(!strcmp(id, "state")) {
+					y = xmlnode_new_child(query, "state");
+				} else if(!strcmp(id, "zip")) {
+					y = xmlnode_new_child(query, "zip");
+				} else if(!strcmp(id, "phone")) {
+					y = xmlnode_new_child(query, "phone");
+				} else if(!strcmp(id, "url")) {
+					y = xmlnode_new_child(query, "url");
+				} else if(!strcmp(id, "date")) {
+					y = xmlnode_new_child(query, "date");
+				} else {
+					continue;
+				}
+				xmlnode_insert_data(y, value, -1);
+				if(cbdata->js->registration && !strcmp(id, "username")) {
+					if(cbdata->js->user->node)
+						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);
 			}
 		}
 	}
 
-	username = g_strdup_printf("%s@%s/%s", js->user->node, js->user->domain,
-			js->user->resource);
-	purple_account_set_username(js->gc->account, username);
-	g_free(username);
+	if(cbdata->js->registration) {
+		username = g_strdup_printf("%s@%s/%s", cbdata->js->user->node, cbdata->js->user->domain,
+				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, NULL);
+	jabber_iq_set_callback(iq, jabber_registration_result_cb, cbdata->who);
 
 	jabber_iq_send(iq);
-
+	g_free(cbdata);
 }
 
 static void
-jabber_register_cancel_cb(JabberStream *js, PurpleRequestFields *fields)
+jabber_register_cancel_cb(JabberRegisterCBData *cbdata, PurpleRequestFields *fields)
 {
-	jabber_connection_schedule_close(js);
+	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");
+	xmlnode_set_attrib(iq->node,"to",to);
 
 	xmlnode_insert_child(query, result);
 
-	jabber_iq_set_callback(iq, jabber_registration_result_cb, NULL);
+	jabber_iq_set_callback(iq, jabber_registration_result_cb, to);
 	jabber_iq_send(iq);
 }
 
 void jabber_register_parse(JabberStream *js, xmlnode *packet)
 {
+	PurpleAccount *account = purple_connection_get_account(js->gc);
 	const char *type;
+	const char *from = xmlnode_get_attrib(packet, "from");
+	PurpleRequestFields *fields;
+	PurpleRequestFieldGroup *group;
+	PurpleRequestField *field;
+	xmlnode *query, *x, *y;
+	char *instructions;
+	JabberRegisterCBData *cbdata;
+	gboolean registered = FALSE;
+
 	if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "result"))
 		return;
 
-	if(js->registration) {
-		PurpleRequestFields *fields;
-		PurpleRequestFieldGroup *group;
-		PurpleRequestField *field;
-		xmlnode *query, *x, *y;
-		char *instructions;
-
+	if(js->registration)
 		/* get rid of the login thingy */
 		purple_connection_set_state(js->gc, PURPLE_CONNECTED);
 
-		query = xmlnode_get_child(packet, "query");
+	query = xmlnode_get_child(packet, "query");
 
-		if(xmlnode_get_child(query, "registered")) {
+	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(packet, "x",
-						"jabber:x:data"))) {
-			jabber_x_data_request(js, x, jabber_register_x_data_cb, NULL);
-			return;
-		} else if((x = xmlnode_get_child_with_namespace(packet, "x",
-						"jabber:x:oob"))) {
-			xmlnode *url;
+	if((x = xmlnode_get_child_with_namespace(packet, "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(packet, "x",
+					"jabber:x:oob"))) {
+		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((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;
 				}
+				return;
 			}
 		}
+	}
 
-		/* as a last resort, use the old jabber:iq:register syntax */
+	/* 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);
+	fields = purple_request_fields_new();
+	group = purple_request_field_group_new(NULL);
+	purple_request_fields_add_group(fields, group);
 
+	if(js->registration)
 		field = purple_request_field_string_new("username", _("Username"),
 				js->user->node, FALSE);
-		purple_request_field_group_add_field(group, field);
+	else
+		field = purple_request_field_string_new("username", _("Username"),
+				NULL, FALSE);
 
+	purple_request_field_group_add_field(group, field);
+
+	if(js->registration)
 		field = purple_request_field_string_new("password", _("Password"),
 				purple_connection_get_password(js->gc), FALSE);
-		purple_request_field_string_set_masked(field, TRUE);
-		purple_request_field_group_add_field(group, field);
+	else
+		field = purple_request_field_string_new("password", _("Password"),
+				NULL, FALSE);
 
-		if(xmlnode_get_child(query, "name")) {
+	purple_request_field_string_set_masked(field, TRUE);
+	purple_request_field_group_add_field(group, field);
+
+	if(xmlnode_get_child(query, "name")) {
+		if(js->registration)
 			field = purple_request_field_string_new("name", _("Name"),
 					purple_account_get_alias(js->gc->account), FALSE);
-			purple_request_field_group_add_field(group, field);
-		}
-		if(xmlnode_get_child(query, "email")) {
-			field = purple_request_field_string_new("email", _("E-mail"),
-					NULL, FALSE);
-			purple_request_field_group_add_field(group, field);
-		}
-		if(xmlnode_get_child(query, "nick")) {
-			field = purple_request_field_string_new("nick", _("Nickname"),
-					NULL, FALSE);
-			purple_request_field_group_add_field(group, field);
-		}
-		if(xmlnode_get_child(query, "first")) {
-			field = purple_request_field_string_new("first", _("First name"),
-					NULL, FALSE);
-			purple_request_field_group_add_field(group, field);
-		}
-		if(xmlnode_get_child(query, "last")) {
-			field = purple_request_field_string_new("last", _("Last name"),
-					NULL, FALSE);
-			purple_request_field_group_add_field(group, field);
-		}
-		if(xmlnode_get_child(query, "address")) {
-			field = purple_request_field_string_new("address", _("Address"),
-					NULL, FALSE);
-			purple_request_field_group_add_field(group, field);
-		}
-		if(xmlnode_get_child(query, "city")) {
-			field = purple_request_field_string_new("city", _("City"),
+		else
+			field = purple_request_field_string_new("name", _("Name"),
 					NULL, FALSE);
-			purple_request_field_group_add_field(group, field);
-		}
-		if(xmlnode_get_child(query, "state")) {
-			field = purple_request_field_string_new("state", _("State"),
-					NULL, FALSE);
-			purple_request_field_group_add_field(group, field);
-		}
-		if(xmlnode_get_child(query, "zip")) {
-			field = purple_request_field_string_new("zip", _("Postal code"),
-					NULL, FALSE);
-			purple_request_field_group_add_field(group, field);
-		}
-		if(xmlnode_get_child(query, "phone")) {
-			field = purple_request_field_string_new("phone", _("Phone"),
-					NULL, FALSE);
-			purple_request_field_group_add_field(group, field);
-		}
-		if(xmlnode_get_child(query, "url")) {
-			field = purple_request_field_string_new("url", _("URL"),
-					NULL, FALSE);
-			purple_request_field_group_add_field(group, field);
-		}
-		if(xmlnode_get_child(query, "date")) {
-			field = purple_request_field_string_new("date", _("Date"),
-					NULL, FALSE);
-			purple_request_field_group_add_field(group, field);
-		}
+		purple_request_field_group_add_field(group, field);
+	}
+	if(xmlnode_get_child(query, "email")) {
+		field = purple_request_field_string_new("email", _("E-mail"),
+				NULL, FALSE);
+		purple_request_field_group_add_field(group, field);
+	}
+	if(xmlnode_get_child(query, "nick")) {
+		field = purple_request_field_string_new("nick", _("Nickname"),
+				NULL, FALSE);
+		purple_request_field_group_add_field(group, field);
+	}
+	if(xmlnode_get_child(query, "first")) {
+		field = purple_request_field_string_new("first", _("First name"),
+				NULL, FALSE);
+		purple_request_field_group_add_field(group, field);
+	}
+	if(xmlnode_get_child(query, "last")) {
+		field = purple_request_field_string_new("last", _("Last name"),
+				NULL, FALSE);
+		purple_request_field_group_add_field(group, field);
+	}
+	if(xmlnode_get_child(query, "address")) {
+		field = purple_request_field_string_new("address", _("Address"),
+				NULL, FALSE);
+		purple_request_field_group_add_field(group, field);
+	}
+	if(xmlnode_get_child(query, "city")) {
+		field = purple_request_field_string_new("city", _("City"),
+				NULL, FALSE);
+		purple_request_field_group_add_field(group, field);
+	}
+	if(xmlnode_get_child(query, "state")) {
+		field = purple_request_field_string_new("state", _("State"),
+				NULL, FALSE);
+		purple_request_field_group_add_field(group, field);
+	}
+	if(xmlnode_get_child(query, "zip")) {
+		field = purple_request_field_string_new("zip", _("Postal code"),
+				NULL, FALSE);
+		purple_request_field_group_add_field(group, field);
+	}
+	if(xmlnode_get_child(query, "phone")) {
+		field = purple_request_field_string_new("phone", _("Phone"),
+				NULL, FALSE);
+		purple_request_field_group_add_field(group, field);
+	}
+	if(xmlnode_get_child(query, "url")) {
+		field = purple_request_field_string_new("url", _("URL"),
+				NULL, FALSE);
+		purple_request_field_group_add_field(group, field);
+	}
+	if(xmlnode_get_child(query, "date")) {
+		field = purple_request_field_string_new("date", _("Date"),
+				NULL, FALSE);
+		purple_request_field_group_add_field(group, field);
+	}
+	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
-			instructions = g_strdup(_("Please fill out the information below "
-						"to register your new account."));
+	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,
-				js);
+				cbdata);
+	else {
+		char *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);
-	}
+	g_free(instructions);
 }
 
 void jabber_register_start(JabberStream *js)
@@ -886,6 +1032,14 @@
 	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)
 {
 	PurpleConnection *gc = purple_account_get_connection(account);
@@ -904,6 +1058,7 @@
 			g_free, g_free);
 	js->user = jabber_id_new(purple_account_get_username(account));
 	js->next_id = g_random_int();
+	js->old_length = -1;
 
 	if(!js->user) {
 		purple_connection_error(gc, _("Invalid XMPP ID"));
@@ -945,18 +1100,77 @@
 	if(!js->gsc) {
 		if (connect_server[0]) {
 			jabber_login_connect(js, js->user->domain, server,
-			                     purple_account_get_int(account,
-			                                          "port", 5222));
+								 purple_account_get_int(account,
+													  "port", 5222));
 		} else {
 			js->srv_query_data = purple_srv_resolve("xmpp-client",
-			                                      "tcp",
-			                                      js->user->domain,
-			                                      srv_resolved_cb,
-			                                      js);
+												  "tcp",
+												  js->user->domain,
+												  srv_resolved_cb,
+												  js);
 		}
 	}
 }
 
+static void jabber_unregister_account_iq_cb(JabberStream *js, xmlnode *packet, gpointer data) {
+	PurpleAccount *account = purple_connection_get_account(js->gc);
+	const char *type = xmlnode_get_attrib(packet,"type");
+	if(!strcmp(type,"error")) {
+		char *msg = jabber_parse_error(js, packet);
+		
+		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 if(!strcmp(type,"result")) {
+		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;
+	assert(js->unregistration);
+	
+	iq = jabber_iq_new_query(js,JABBER_IQ_SET,"jabber:iq:register");
+	assert(iq);
+	query = xmlnode_get_child_with_namespace(iq->node,"query","jabber:iq:register");
+	assert(query);
+	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;
+	assert(!js->unregistration); /* don't allow multiple calls */
+	js->unregistration = TRUE;
+	js->unregistration_cb = cb;
+	js->unregistration_user_data = user_data;
+	
+	jabber_unregister_account_cb(js);
+}
+
 void jabber_close(PurpleConnection *gc)
 {
 	JabberStream *js = gc->proto_data;
@@ -984,6 +1198,8 @@
 
 	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->disco_callbacks)
@@ -1019,9 +1235,32 @@
 #endif
 	if(js->serverFQDN)
 		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->gmail_last_time);
 	g_free(js->gmail_last_tid);
+	if(js->old_msg)
+		g_free(js->old_msg);
+	if(js->old_avatarhash)
+		g_free(js->old_avatarhash);
+	if(js->old_artist)
+		g_free(js->old_artist);
+	if(js->old_title)
+		g_free(js->old_title);
+	if(js->old_source)
+		g_free(js->old_source);
+	if(js->old_uri)
+		g_free(js->old_uri);
+	if(js->old_track)
+		g_free(js->old_track);
+	
 	g_free(js);
 
 	gc->proto_data = NULL;
@@ -1042,9 +1281,13 @@
 					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 ? 6 : 3, JABBER_CONNECT_STEPS);
+					js->gsc ? 7 : 3, JABBER_CONNECT_STEPS);
 			if(js->protocol_version == JABBER_PROTO_0_9 && js->registration) {
 				jabber_register_start(js);
 			} else if(js->auth_type == JABBER_AUTH_IQ_AUTH) {
@@ -1053,7 +1296,7 @@
 			break;
 		case JABBER_STREAM_REINITIALIZING:
 			purple_connection_update_progress(js->gc, _("Re-initializing Stream"),
-					(js->gsc ? 7 : 4), JABBER_CONNECT_STEPS);
+					(js->gsc ? 8 : 4), JABBER_CONNECT_STEPS);
 
 			/* The stream will be reinitialized later, in jabber_recv_cb_ssl() */
 			js->reinit = TRUE;
@@ -1080,6 +1323,38 @@
 	js->idle = idle ? time(NULL) - idle : idle;
 }
 
+void jabber_add_feature(const char *shortname, const char *namespace, JabberFeatureEnabled cb) {
+	JabberFeature *feat;
+	
+	assert(shortname != NULL);
+	assert(namespace != NULL);
+	
+	feat = g_new0(JabberFeature,1);
+	feat->shortname = g_strdup(shortname);
+	feat->namespace = g_strdup(namespace);
+	feat->is_enabled = cb;
+	
+	/* try to remove just in case it already exists in the list */
+	jabber_remove_feature(shortname);
+	
+	jabber_features = g_list_append(jabber_features, feat);
+}
+
+void jabber_remove_feature(const char *shortname) {
+	GList *feature;
+	for(feature = jabber_features; feature; feature = feature->next) {
+		JabberFeature *feat = (JabberFeature*)feature->data;
+		if(!strcmp(feat->shortname, shortname)) {
+			g_free(feat->shortname);
+			g_free(feat->namespace);
+			
+			g_free(feature->data);
+			feature = g_list_delete_link(feature, feature);
+			break;
+		}
+	}
+}
+
 const char *jabber_list_icon(PurpleAccount *a, PurpleBuddy *b)
 {
 	return "jabber";
@@ -1154,6 +1429,9 @@
 		GList *l;
 
 		if (full) {
+			PurpleStatus *status;
+			PurpleValue *value;
+			
 			if(jb->subscription & JABBER_SUB_FROM) {
 				if(jb->subscription & JABBER_SUB_TO)
 					sub = _("Both");
@@ -1171,6 +1449,21 @@
 			}
 			
 			purple_notify_user_info_add_pair(user_info, _("Subscription"), sub);
+			
+			status = purple_presence_get_active_status(purple_buddy_get_presence(b));
+			value = purple_status_get_attr_value(status, "mood");
+			if(value && purple_value_get_type(value) == PURPLE_TYPE_STRING) {
+				const char *mood = purple_value_get_string(value);
+				
+				value = purple_status_get_attr_value(status, "moodtext");
+				if(value && purple_value_get_type(value) == PURPLE_TYPE_STRING) {
+					char *moodplustext = g_strdup_printf("%s (%s)",mood,purple_value_get_string(value));
+					
+					purple_notify_user_info_add_pair(user_info, _("Mood"), moodplustext);
+					g_free(moodplustext);
+				} else
+					purple_notify_user_info_add_pair(user_info, _("Mood"), mood);
+			}
 		}
 
 		for(l=jb->resources; l; l = l->next) {
@@ -1233,6 +1526,19 @@
 			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),
+			"tune_artist", _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_title", _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_album", _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_genre", _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_comment", _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_track", _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_time", _("Tune Time"), purple_value_new(PURPLE_TYPE_INT),
+			"tune_year", _("Tune Year"), purple_value_new(PURPLE_TYPE_INT),
+			"tune_url", _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING),
+			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
+			"buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN),
 			NULL);
 	types = g_list_append(types, type);
 
@@ -1243,6 +1549,19 @@
 			_("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),
+			"tune_artist", _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_title", _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_album", _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_genre", _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_comment", _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_track", _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_time", _("Tune Time"), purple_value_new(PURPLE_TYPE_INT),
+			"tune_year", _("Tune Year"), purple_value_new(PURPLE_TYPE_INT),
+			"tune_url", _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING),
+			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
+			"buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN),
 			NULL);
 	types = g_list_append(types, type);
 
@@ -1253,6 +1572,19 @@
 			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),
+			"tune_artist", _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_title", _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_album", _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_genre", _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_comment", _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_track", _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_time", _("Tune Time"), purple_value_new(PURPLE_TYPE_INT),
+			"tune_year", _("Tune Year"), purple_value_new(PURPLE_TYPE_INT),
+			"tune_url", _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING),
+			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
+			"buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN),
 			NULL);
 	types = g_list_append(types, type);
 
@@ -1263,6 +1595,19 @@
 			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),
+			"tune_artist", _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_title", _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_album", _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_genre", _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_comment", _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_track", _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_time", _("Tune Time"), purple_value_new(PURPLE_TYPE_INT),
+			"tune_year", _("Tune Year"), purple_value_new(PURPLE_TYPE_INT),
+			"tune_url", _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING),
+			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
+			"buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN),
 			NULL);
 	types = g_list_append(types, type);
 
@@ -1273,6 +1618,19 @@
 			_("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),
+			"tune_artist", _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_title", _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_album", _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_genre", _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_comment", _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_track", _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_time", _("Tune Time"), purple_value_new(PURPLE_TYPE_INT),
+			"tune_year", _("Tune Year"), purple_value_new(PURPLE_TYPE_INT),
+			"tune_url", _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING),
+			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
+			"buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN),
 			NULL);
 	types = g_list_append(types, type);
 
@@ -1379,23 +1737,33 @@
 
 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);
+								 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);
+									 jabber_password_change);
 		m = g_list_append(m, act);
 	/* } */
 
 	act = purple_plugin_action_new(_("Search for Users..."),
-	                             jabber_user_search_begin);
+								 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;
 }
 
@@ -1690,10 +2058,10 @@
 		return PURPLE_CMD_RET_FAILED;
 
 	if (strcmp(args[1], "owner") != 0 && 
-	    strcmp(args[1], "admin") != 0 &&
-	    strcmp(args[1], "member") != 0 &&
-	    strcmp(args[1], "outcast") != 0 &&
-	    strcmp(args[1], "none") != 0) {
+		strcmp(args[1], "admin") != 0 &&
+		strcmp(args[1], "member") != 0 &&
+		strcmp(args[1], "outcast") != 0 &&
+		strcmp(args[1], "none") != 0) {
 		*error = g_strdup_printf(_("Unknown affiliation: \"%s\""), args[1]);
 		return PURPLE_CMD_RET_FAILED;
 	}
@@ -1715,16 +2083,16 @@
 		return PURPLE_CMD_RET_FAILED;
 
 	if (strcmp(args[1], "moderator") != 0 &&
-	    strcmp(args[1], "participant") != 0 &&
-	    strcmp(args[1], "visitor") != 0 &&
-	    strcmp(args[1], "none") != 0) {
+		strcmp(args[1], "participant") != 0 &&
+		strcmp(args[1], "visitor") != 0 &&
+		strcmp(args[1], "none") != 0) {
 		*error = g_strdup_printf(_("Unknown role: \"%s\""), args[1]);
 		return PURPLE_CMD_RET_FAILED;
 	}
 
 	if (!jabber_chat_role_user(chat, args[0], args[1])) {
 		*error = g_strdup_printf(_("Unable to set role \"%s\" for user: %s"),
-		                         args[1], args[0]);
+								 args[1], args[0]);
 		return PURPLE_CMD_RET_FAILED;
 	}
 
@@ -1800,6 +2168,71 @@
 	return PURPLE_CMD_RET_OK;
 }
 
+static PurpleCmdRet jabber_cmd_ping(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	if(!args || !args[0])
+		return PURPLE_CMD_RET_FAILED;
+
+	if(!jabber_ping_jid(conv, args[0])) {
+		*error = g_strdup_printf(_("Unable to ping user %s"), args[0]);
+		return PURPLE_CMD_RET_FAILED;
+	}
+
+	return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet jabber_cmd_buzz(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	JabberStream *js = conv->account->gc->proto_data;
+	xmlnode *msg, *buzz;
+	JabberBuddy *jb;
+	JabberBuddyResource *jbr;
+	char *to;
+	GList *iter;
+
+	if(!args || !args[0])
+		return PURPLE_CMD_RET_FAILED;
+	
+	jb = jabber_buddy_find(js, args[0], FALSE);
+	if(!jb) {
+		*error = g_strdup_printf(_("Unable to buzz, because there is nothing known about user %s."), args[0]);
+		return PURPLE_CMD_RET_FAILED;
+	}
+	
+	jbr = jabber_buddy_find_resource(jb, NULL);
+	if(!jbr) {
+		*error = g_strdup_printf(_("Unable to buzz, because user %s might be offline."), args[0]);
+		return PURPLE_CMD_RET_FAILED;
+	}
+	if(!jbr->caps) {
+		*error = g_strdup_printf(_("Unable to buzz, because there is nothing known about user %s."), args[0]);
+		return PURPLE_CMD_RET_FAILED;
+	}
+	for(iter = jbr->caps->features; iter; iter = g_list_next(iter)) {
+		if(!strcmp(iter->data, "http://www.xmpp.org/extensions/xep-0224.html#ns")) {
+			msg = xmlnode_new("message");
+			to = g_strdup_printf("%s/%s", args[0], 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,"http://www.xmpp.org/extensions/xep-0224.html#ns");
+			
+			jabber_send(js,msg);
+			xmlnode_free(msg);
+			
+			return PURPLE_CMD_RET_OK;
+		}
+	}
+	*error = g_strdup_printf(_("Unable to buzz, because the user %s does not support it."), args[0]);
+	return PURPLE_CMD_RET_FAILED;
+}
+
 gboolean jabber_offline_message(const PurpleBuddy *buddy)
 {
 	return TRUE;
@@ -1808,79 +2241,89 @@
 void jabber_register_commands(void)
 {
 	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);
+					  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
+					  "prpl-jabber", jabber_cmd_chat_config,
+					  _("config:  Configure a chat room."), NULL);
 	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);
+					  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
+					  "prpl-jabber", jabber_cmd_chat_config,
+					  _("configure:	 Configure a chat room."), NULL);
 	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);
+					  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
+					  "prpl-jabber", jabber_cmd_chat_nick,
+					  _("nick &lt;new nickname&gt;:	 Change your nickname."),
+					  NULL);
 	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 [room]:  Leave the room."),
-	                  NULL);
+					  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+					  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
+					  jabber_cmd_chat_part, _("part [room]:	 Leave the room."),
+					  NULL);
 	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);
+					  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
+					  "prpl-jabber", jabber_cmd_chat_register,
+					  _("register:	Register with a chat room."), NULL);
 	/* XXX: there needs to be a core /topic cmd, methinks */
 	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);
+					  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);
 	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; [room]:  Ban a user from the room."),
-	                  NULL);
+					  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; [room]:  Ban a user from the room."),
+					  NULL);
 	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;user&gt; &lt;owner|admin|member|outcast|none&gt;: Set a user's affiliation with the room."),
-	                  NULL);
+					  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+					  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
+					  jabber_cmd_chat_affiliate,
+					  _("affiliate &lt;user&gt; &lt;owner|admin|member|outcast|none&gt;: Set a user's affiliation with the room."),
+					  NULL);
 	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;user&gt; &lt;moderator|participant|visitor|none&gt;: Set a user's role in the room."),
-	                  NULL);
+					  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+					  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
+					  jabber_cmd_chat_role,
+					  _("role &lt;user&gt; &lt;moderator|participant|visitor|none&gt;: Set a user's role in the room."),
+					  NULL);
 	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);
+					  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);
 	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; [server]:  Join a chat on this server."),
-	                  NULL);
+					  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; [server]:  Join a chat on this server."),
+					  NULL);
 	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; [room]:  Kick a user from the room."),
-	                  NULL);
+					  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; [room]:	Kick a user from the room."),
+					  NULL);
 	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);
+					  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);
+	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);
+	purple_cmd_register("buzz", "s", PURPLE_CMD_P_PRPL,
+					  PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PRPL_ONLY,
+					  "prpl-jabber", jabber_cmd_buzz,
+					  _("buzz: Buzz a user to get their attention"), NULL);
 }
 
 void
 jabber_init_plugin(PurplePlugin *plugin)
 {
-        my_protocol = plugin;
+	my_protocol = plugin;
 }