changeset 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 e21002d106ab (current diff) b0733d5d7621 (diff)
children 301f1597d41f
files .mtn-ignore libpurple/account.c libpurple/protocols/jabber/jabber.c libpurple/protocols/jabber/jabber.h libpurple/protocols/yahoo/yahoo.c libpurple/sslconn.c libpurple/sslconn.h libpurple/xmlnode.c
diffstat 46 files changed, 3822 insertions(+), 414 deletions(-) [+]
line wrap: on
line diff
--- a/.mtn-ignore	Wed Sep 05 12:56:35 2007 +0000
+++ b/.mtn-ignore	Wed Sep 05 22:32:14 2007 +0000
@@ -1,3 +1,4 @@
+(.*/)?\.svn
 .*/?Makefile(\.in)?$
 (.*/)?TAGS$
 .*/?.*\.pc$
--- a/libpurple/account.c	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/account.c	Wed Sep 05 22:32:14 2007 +0000
@@ -913,6 +913,15 @@
 }
 
 void
+purple_account_set_register_callback(PurpleAccount *account, PurpleAccountRegistrationCb cb, void *user_data)
+{
+	g_return_if_fail(account != NULL);
+	
+	account->registration_cb = cb;
+	account->registration_cb_user_data = user_data;
+}
+
+void
 purple_account_register(PurpleAccount *account)
 {
 	g_return_if_fail(account != NULL);
@@ -923,6 +932,17 @@
 	purple_connection_new(account, TRUE, purple_account_get_password(account));
 }
 
+void
+purple_account_unregister(PurpleAccount *account, PurpleAccountUnregistrationCb cb, void *user_data)
+{
+	g_return_if_fail(account != NULL);
+	
+	purple_debug_info("account", "Unregistering account %s\n",
+					  purple_account_get_username(account));
+	
+	purple_connection_new_unregister(account, purple_account_get_password(account), cb, user_data);
+}
+
 static void
 request_password_ok_cb(PurpleAccount *account, PurpleRequestFields *fields)
 {
--- a/libpurple/account.h	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/account.h	Wed Sep 05 22:32:14 2007 +0000
@@ -36,6 +36,8 @@
 
 typedef gboolean (*PurpleFilterAccountFunc)(PurpleAccount *account);
 typedef void (*PurpleAccountRequestAuthorizationCb)(void *);
+typedef void (*PurpleAccountRegistrationCb)(PurpleAccount *account, gboolean succeeded, void *user_data);
+typedef void (*PurpleAccountUnregistrationCb)(PurpleAccount *account, gboolean succeeded, void *user_data);
 
 #include "connection.h"
 #include "log.h"
@@ -136,6 +138,8 @@
 	PurpleLog *system_log;        /**< The system log                         */
 
 	void *ui_data;              /**< The UI can put data here.              */
+	PurpleAccountRegistrationCb registration_cb;
+	void *registration_cb_user_data;
 };
 
 #ifdef __cplusplus
@@ -172,6 +176,15 @@
 void purple_account_connect(PurpleAccount *account);
 
 /**
+ * Sets the callback for successful registration.
+ *
+ * @param account	The account for which this callback should be used
+ * @param cb	The callback
+ * @param user_data	The user data passed to the callback
+ */
+void purple_account_set_register_callback(PurpleAccount *account, PurpleAccountRegistrationCb cb, void *user_data);
+
+/**
  * Registers an account.
  *
  * @param account The account to register.
@@ -179,6 +192,15 @@
 void purple_account_register(PurpleAccount *account);
 
 /**
+ * Unregisters an account (deleting it from the server).
+ *
+ * @param account The account to unregister.
+ * @param cb Optional callback to be called when unregistration is complete
+ * @param user_data user data to pass to the callback
+ */
+void purple_account_unregister(PurpleAccount *account, PurpleAccountUnregistrationCb cb, void *user_data);
+
+/**
  * Disconnects from an account.
  *
  * @param account The account to disconnect from.
--- a/libpurple/connection.c	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/connection.c	Wed Sep 05 22:32:14 2007 +0000
@@ -158,6 +158,62 @@
 }
 
 void
+purple_connection_new_unregister(PurpleAccount *account, const char *password, PurpleAccountUnregistrationCb cb, void *user_data)
+{
+	/* Lots of copy/pasted code to avoid API changes. You might want to integrate that into the previous function when posssible. */
+	PurpleConnection *gc;
+	PurplePlugin *prpl;
+	PurplePluginProtocolInfo *prpl_info;
+	
+	g_return_if_fail(account != NULL);
+		
+	prpl = purple_find_prpl(purple_account_get_protocol_id(account));
+	
+	if (prpl != NULL)
+		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
+	else {
+		gchar *message;
+		
+		message = g_strdup_printf(_("Missing protocol plugin for %s"),
+								  purple_account_get_username(account));
+		purple_notify_error(NULL, _("Unregistration Error"), message, NULL);
+		g_free(message);
+		return;
+	}
+
+	if (!purple_account_is_disconnected(account)) {
+		prpl_info->unregister_user(account, cb, user_data);
+		return;
+	}
+	
+	if (((password == NULL) || (*password == '\0')) &&
+		!(prpl_info->options & OPT_PROTO_NO_PASSWORD) &&
+		!(prpl_info->options & OPT_PROTO_PASSWORD_OPTIONAL))
+	{
+		purple_debug_error("connection", "Can not connect to account %s without "
+						   "a password.\n", purple_account_get_username(account));
+		return;
+	}
+	
+	gc = g_new0(PurpleConnection, 1);
+	PURPLE_DBUS_REGISTER_POINTER(gc, PurpleConnection);
+	
+	gc->prpl = prpl;
+	if ((password != NULL) && (*password != '\0'))
+		gc->password = g_strdup(password);
+	purple_connection_set_account(gc, account);
+	purple_connection_set_state(gc, PURPLE_CONNECTING);
+	connections = g_list_append(connections, gc);
+	purple_account_set_connection(account, gc);
+	
+	purple_signal_emit(purple_connections_get_handle(), "signing-on", gc);
+	
+	purple_debug_info("connection", "Unregistering.  gc = %p\n", gc);
+	
+	prpl_info->unregister_user(account, cb, user_data);
+}
+
+void
 purple_connection_destroy(PurpleConnection *gc)
 {
 	PurpleAccount *account;
--- a/libpurple/connection.h	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/connection.h	Wed Sep 05 22:32:14 2007 +0000
@@ -171,6 +171,18 @@
 									const char *password);
 
 /**
+ * This function should only be called by purple_account_unregister()
+ * in account.c.
+ *
+ * Tries to unregister the account on the server. If the account is not
+ * connected, also creates a new connection.
+ *
+ * @param account  The account to unregister
+ * @param password The password to use.
+ */
+void purple_connection_new_unregister(PurpleAccount *account, const char *password, PurpleAccountUnregistrationCb cb, void *user_data);
+
+/**
  * Disconnects and destroys a PurpleConnection.
  *
  * This function should only be called by purple_account_disconnect()
--- a/libpurple/plugin.h	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/plugin.h	Wed Sep 05 22:32:14 2007 +0000
@@ -188,6 +188,8 @@
 	/** NULL for plugin actions menu, set to the PurpleConnection for
 	    account actions menu */
 	gpointer context;
+	
+	gpointer user_data;
 };
 
 #define PURPLE_PLUGIN_HAS_ACTIONS(plugin) \
--- a/libpurple/protocols/gg/gg.c	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/protocols/gg/gg.c	Wed Sep 05 22:32:14 2007 +0000
@@ -411,6 +411,8 @@
 	purple_notify_info(NULL, _("New Gadu-Gadu Account Registered"),
 			 _("Registration completed successfully!"), NULL);
 
+	if(account->registration_cb)
+		(account->registration_cb)(account, TRUE, account->registration_cb_user_data);
 	/* TODO: the currently open Accounts Window will not be updated withthe
 	 * new username and etc, we need to somehow have it refresh at this
 	 * point
@@ -420,6 +422,9 @@
 	purple_connection_destroy(gc);
 
 exit_err:
+	if(account->registration_cb)
+		(account->registration_cb)(account, FALSE, account->registration_cb_user_data);
+
 	gg_register_free(h);
 	g_free(email);
 	g_free(p1);
--- a/libpurple/protocols/jabber/Makefile.am	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/protocols/jabber/Makefile.am	Wed Sep 05 22:32:14 2007 +0000
@@ -27,6 +27,8 @@
 			  oob.h \
 			  parser.c \
 			  parser.h \
+			  ping.c \
+			  ping.h \
 			  presence.c \
 			  presence.h \
 			  roster.c \
@@ -34,7 +36,19 @@
 			  si.c \
 			  si.h \
 			  xdata.c \
-			  xdata.h
+			  xdata.h \
+			  caps.c \
+			  caps.h \
+			  adhoccommands.c \
+			  adhoccommands.h \
+			  pep.c \
+			  pep.h \
+			  usermood.c \
+			  usermood.h \
+			  usernick.c \
+			  usernick.h \
+			  usertune.c \
+			  usertune.h
 
 AM_CFLAGS = $(st)
 
--- a/libpurple/protocols/jabber/Makefile.mingw	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/protocols/jabber/Makefile.mingw	Wed Sep 05 22:32:14 2007 +0000
@@ -53,6 +53,7 @@
 			message.c \
 			oob.c \
 			parser.c \
+			ping.c \
 			presence.c \
 			roster.c \
 			si.c \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/adhoccommands.c	Wed Sep 05 22:32:14 2007 +0000
@@ -0,0 +1,307 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#include "adhoccommands.h"
+#include <assert.h>
+#include <string.h>
+#include "internal.h"
+#include "xdata.h"
+#include "iq.h"
+#include "request.h"
+
+static void do_adhoc_ignoreme(JabberStream *js, ...) {
+	/* we don't have to do anything */
+}
+
+typedef struct _JabberAdHocActionInfo {
+	char *sessionid;
+	char *who;
+	char *node;
+	GList *actionslist;
+} JabberAdHocActionInfo;
+
+void jabber_adhoc_disco_result_cb(JabberStream *js, xmlnode *packet, gpointer data) {
+	const char *from = xmlnode_get_attrib(packet, "from");
+	const char *type = xmlnode_get_attrib(packet, "type");
+	const char *node;
+	xmlnode *query, *item;
+	JabberID *jabberid;
+	JabberBuddy *jb;
+	JabberBuddyResource *jbr = NULL;
+	
+	if(strcmp(type, "result"))
+		return;
+	
+	query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#items");
+	if(!query)
+		return;
+	node = xmlnode_get_attrib(query,"node");
+	if(!node || strcmp(node, "http://jabber.org/protocol/commands"))
+		return;
+	
+	if((jabberid = jabber_id_new(from))) {
+		if(jabberid->resource && (jb = jabber_buddy_find(js, from, TRUE)))
+			jbr = jabber_buddy_find_resource(jb, jabberid->resource);
+		jabber_id_free(jabberid);
+	}
+	
+	if(!jbr)
+		return;
+	
+	if(jbr->commands) {
+		/* since the list we just received is complete, wipe the old one */
+		while(jbr->commands) {
+			JabberAdHocCommands *cmd = jbr->commands->data;
+			g_free(cmd->jid);
+			g_free(cmd->node);
+			g_free(cmd->name);
+			g_free(cmd);
+			jbr->commands = g_list_delete_link(jbr->commands, jbr->commands);
+		}
+	}
+	
+	for(item = query->child; item; item = item->next) {
+		JabberAdHocCommands *cmd;
+		if(item->type != XMLNODE_TYPE_TAG)
+			continue;
+		if(strcmp(item->name, "item"))
+			continue;
+		cmd = g_new0(JabberAdHocCommands, 1);
+		
+		cmd->jid = g_strdup(xmlnode_get_attrib(item,"jid"));
+		cmd->node = g_strdup(xmlnode_get_attrib(item,"node"));
+		cmd->name = g_strdup(xmlnode_get_attrib(item,"name"));
+		
+		jbr->commands = g_list_append(jbr->commands,cmd);
+	}
+}
+
+static void jabber_adhoc_parse(JabberStream *js, xmlnode *packet, gpointer data);
+
+static void do_adhoc_action_cb(JabberStream *js, xmlnode *result, const char *actionhandle, gpointer user_data) {
+	xmlnode *command;
+	GList *action;
+	JabberAdHocActionInfo *actionInfo = user_data;
+	JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET);
+	jabber_iq_set_callback(iq, jabber_adhoc_parse, NULL);
+	
+	xmlnode_set_attrib(iq->node, "to", actionInfo->who);
+	command = xmlnode_new_child(iq->node,"command");
+	xmlnode_set_namespace(command,"http://jabber.org/protocol/commands");
+	xmlnode_set_attrib(command,"sessionid",actionInfo->sessionid);
+	xmlnode_set_attrib(command,"node",actionInfo->node);
+	if(actionhandle)
+		xmlnode_set_attrib(command,"action",actionhandle);
+	xmlnode_insert_child(command,result);
+	
+	for(action = actionInfo->actionslist; action; action = g_list_next(action)) {
+		char *handle = action->data;
+		g_free(handle);
+	}
+	g_list_free(actionInfo->actionslist);
+	g_free(actionInfo->sessionid);
+	g_free(actionInfo->who);
+	g_free(actionInfo->node);
+	
+	jabber_iq_send(iq);
+}
+
+static void jabber_adhoc_parse(JabberStream *js, xmlnode *packet, gpointer data) {
+	xmlnode *command = xmlnode_get_child_with_namespace(packet, "command", "http://jabber.org/protocol/commands");
+	const char *status = xmlnode_get_attrib(command,"status");
+	xmlnode *xdata = xmlnode_get_child_with_namespace(command,"x","jabber:x:data");
+	const char *type = xmlnode_get_attrib(packet,"type");
+	
+	if(type && !strcmp(type,"error")) {
+		char *msg = jabber_parse_error(js, packet);
+		if(!msg)
+			msg = g_strdup(_("Unknown Error"));
+		
+		purple_notify_error(NULL, _("Ad-Hoc Command Failed"),
+							_("Ad-Hoc Command Failed"), msg);
+		g_free(msg);
+		return;
+	}
+	if(!type || strcmp(type,"result"))
+		return;
+	
+	if(!status)
+		return;
+	
+	if(!strcmp(status,"completed")) {
+		/* display result */
+		xmlnode *note = xmlnode_get_child(command,"note");
+		
+		if(note)
+			purple_notify_info(NULL, xmlnode_get_attrib(packet, "from"), xmlnode_get_data(note), NULL);
+		
+		if(xdata)
+			jabber_x_data_request(js, xdata, (jabber_x_data_cb)do_adhoc_ignoreme, NULL);
+		return;
+	}
+	if(!strcmp(status,"executing")) {
+		/* this command needs more steps */
+		xmlnode *actions, *action;
+		int actionindex = 0;
+		GList *actionslist = NULL;
+		JabberAdHocActionInfo *actionInfo;
+		if(!xdata)
+			return; /* shouldn't happen */
+		
+		actions = xmlnode_get_child(command,"actions");
+		if(!actions) {
+			JabberXDataAction *defaultaction = g_new0(JabberXDataAction, 1);
+			defaultaction->name = g_strdup(_("execute"));
+			defaultaction->handle = g_strdup("execute");
+			actionslist = g_list_append(actionslist, defaultaction);
+		} else {
+			const char *defaultactionhandle = xmlnode_get_attrib(actions, "execute");
+			int index = 0;
+			for(action = actions->child; action; action = action->next, ++index) {
+				if(action->type == XMLNODE_TYPE_TAG) {
+					JabberXDataAction *newaction = g_new0(JabberXDataAction, 1);
+					newaction->name = g_strdup(_(action->name));
+					newaction->handle = g_strdup(action->name);
+					actionslist = g_list_append(actionslist, newaction);
+					if(defaultactionhandle && !strcmp(defaultactionhandle, action->name))
+						actionindex = index;
+				}
+			}
+		}
+		
+		actionInfo = g_new0(JabberAdHocActionInfo, 1);
+		actionInfo->sessionid = g_strdup(xmlnode_get_attrib(command,"sessionid"));
+		actionInfo->who = g_strdup(xmlnode_get_attrib(packet,"from"));
+		actionInfo->node = g_strdup(xmlnode_get_attrib(command,"node"));
+		actionInfo->actionslist = actionslist;
+		
+		jabber_x_data_request_with_actions(js,xdata,actionslist,actionindex,do_adhoc_action_cb,actionInfo);
+	}
+}
+
+void jabber_adhoc_execute_action(PurpleBlistNode *node, gpointer data) {
+	if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+		JabberAdHocCommands *cmd = data;
+		PurpleBuddy *buddy = (PurpleBuddy *) node;
+		JabberStream *js = purple_account_get_connection(buddy->account)->proto_data;
+		
+		jabber_adhoc_execute(js, cmd);
+	}
+}
+
+static void jabber_adhoc_server_got_list_cb(JabberStream *js, xmlnode *packet, gpointer data) {
+	xmlnode *query = xmlnode_get_child_with_namespace(packet, "query", "http://jabber.org/protocol/disco#items");
+	xmlnode *item;
+	
+	if(!query)
+		return;
+
+	/* clean current list (just in case there is one) */
+	while(js->commands) {
+		JabberAdHocCommands *cmd = js->commands->data;
+		g_free(cmd->jid);
+		g_free(cmd->node);
+		g_free(cmd->node);
+		g_free(cmd);
+		js->commands = g_list_delete_link(js->commands, js->commands);
+	}
+	
+	/* re-fill list */
+	for(item = query->child; item; item = item->next) {
+		JabberAdHocCommands *cmd;
+		if(item->type != XMLNODE_TYPE_TAG)
+			continue;
+		if(strcmp(item->name, "item"))
+			continue;
+		cmd = g_new0(JabberAdHocCommands, 1);
+		cmd->jid = g_strdup(xmlnode_get_attrib(item,"jid"));
+		cmd->node = g_strdup(xmlnode_get_attrib(item,"node"));
+		cmd->name = g_strdup(xmlnode_get_attrib(item,"name"));
+		
+		js->commands = g_list_append(js->commands,cmd);
+	}
+}
+
+void jabber_adhoc_server_get_list(JabberStream *js) {
+	JabberIq *iq = jabber_iq_new_query(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",js->user->domain);
+	xmlnode_set_attrib(query,"node","http://jabber.org/protocol/commands");
+	
+	jabber_iq_set_callback(iq,jabber_adhoc_server_got_list_cb,NULL);
+	jabber_iq_send(iq);
+}
+
+void jabber_adhoc_execute(JabberStream *js, JabberAdHocCommands *cmd) {
+	JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET);
+	xmlnode *command = xmlnode_new_child(iq->node,"command");
+	xmlnode_set_attrib(iq->node,"to",cmd->jid);
+	xmlnode_set_namespace(command,"http://jabber.org/protocol/commands");
+	xmlnode_set_attrib(command,"node",cmd->node);
+	xmlnode_set_attrib(command,"action","execute");
+	
+	jabber_iq_set_callback(iq,jabber_adhoc_parse,NULL);
+	
+	jabber_iq_send(iq);
+}
+
+void jabber_adhoc_server_execute(PurplePluginAction *action) {
+	JabberAdHocCommands *cmd = action->user_data;
+	if(cmd) {
+		PurpleConnection *gc = (PurpleConnection *) action->context;
+		JabberStream *js = gc->proto_data;
+		
+		jabber_adhoc_execute(js, cmd);
+	}
+}
+
+void jabber_adhoc_init_server_commands(JabberStream *js, GList **m) {
+	GList *cmdlst;
+	JabberBuddy *jb;
+	
+	/* also add commands for other clients connected to the same account on another resource */
+	char *accountname = g_strdup_printf("%s@%s", js->user->node, js->user->domain);
+	if((jb = jabber_buddy_find(js, accountname, TRUE))) {
+		GList *iter;
+		for(iter = jb->resources; iter; iter = g_list_next(iter)) {
+			JabberBuddyResource *jbr = iter->data;
+			GList *riter;
+			for(riter = jbr->commands; riter; riter = g_list_next(riter)) {
+				JabberAdHocCommands *cmd = riter->data;
+				char *cmdname = g_strdup_printf("%s (%s)",cmd->name,jbr->name);
+				PurplePluginAction *act = purple_plugin_action_new(cmdname, jabber_adhoc_server_execute);
+				act->user_data = cmd;
+				*m = g_list_append(*m, act);
+				g_free(cmdname);
+			}
+		}
+	}
+	g_free(accountname);
+	
+	/* now add server commands */
+	for(cmdlst = js->commands; cmdlst; cmdlst = g_list_next(cmdlst)) {
+		JabberAdHocCommands *cmd = cmdlst->data;
+		PurplePluginAction *act = purple_plugin_action_new(cmd->name, jabber_adhoc_server_execute);
+		act->user_data = cmd;
+		*m = g_list_append(*m, act);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/adhoccommands.h	Wed Sep 05 22:32:14 2007 +0000
@@ -0,0 +1,39 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#ifndef _PURPLE_JABBER_ADHOCCOMMANDS_H_
+#define _PURPLE_JABBER_ADHOCCOMMANDS_H_
+
+#include "jabber.h"
+
+/* Implementation of XEP-0050 */
+
+void jabber_adhoc_disco_result_cb(JabberStream *js, xmlnode *packet, gpointer data);
+
+void jabber_adhoc_execute(JabberStream *js, JabberAdHocCommands *cmd);
+
+void jabber_adhoc_execute_action(PurpleBlistNode *node, gpointer data);
+
+void jabber_adhoc_server_get_list(JabberStream *js);
+
+void jabber_adhoc_init_server_commands(JabberStream *js, GList **m);
+
+#endif /* _PURPLE_JABBER_ADHOCCOMMANDS_H_ */
--- a/libpurple/protocols/jabber/buddy.c	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Wed Sep 05 22:32:14 2007 +0000
@@ -34,6 +34,8 @@
 #include "iq.h"
 #include "presence.h"
 #include "xdata.h"
+#include "pep.h"
+#include "adhoccommands.h"
 
 typedef struct {
 	long idle_seconds;
@@ -116,7 +118,6 @@
 		int priority, JabberBuddyState state, const char *status)
 {
 	JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource);
-
 	if(!jbr) {
 		jbr = g_new0(JabberBuddyResource, 1);
 		jbr->jb = jb;
@@ -128,7 +129,7 @@
 	jbr->state = state;
 	if(jbr->status)
 		g_free(jbr->status);
-        if (status)
+	if (status)
 		jbr->status = g_markup_escape_text(status, -1);
 	else
 		jbr->status = NULL;
@@ -141,6 +142,17 @@
 	g_return_if_fail(jbr != NULL);
 
 	jbr->jb->resources = g_list_remove(jbr->jb->resources, jbr);
+	
+	while(jbr->commands) {
+		JabberAdHocCommands *cmd = jbr->commands->data;
+		g_free(cmd->jid);
+		g_free(cmd->node);
+		g_free(cmd->name);
+		g_free(cmd);
+		jbr->commands = g_list_delete_link(jbr->commands, jbr->commands);
+	}
+	
+	jabber_caps_free_clientinfo(jbr->caps);
 
 	g_free(jbr->name);
 	g_free(jbr->status);
@@ -411,7 +423,7 @@
 		if ((img = purple_buddy_icons_find_account_icon(gc->account))) {
 			gconstpointer avatar_data;
 			gsize avatar_len;
-			xmlnode *photo, *binval;
+			xmlnode *photo, *binval, *type;
 			gchar *enc;
 			int i;
 			unsigned char hashval[20];
@@ -424,6 +436,8 @@
 				xmlnode_free(photo);
 			}
 			photo = xmlnode_new_child(vc_node, "PHOTO");
+			type = xmlnode_new_child(photo, "TYPE");
+			xmlnode_insert_data(type, "image/png", -1);
 			binval = xmlnode_new_child(photo, "BINVAL");
 			enc = purple_base64_encode(avatar_data, avatar_len);
 
@@ -452,9 +466,135 @@
 
 void jabber_set_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img)
 {
+	PurplePresence *gpresence;
+	PurpleStatus *status;
+	
+	if(((JabberStream*)gc->proto_data)->pep) {
+		/* XEP-0084: User Avatars */
+		if(img) {
+			/* A PNG header, including the IHDR, but nothing else */
+			const struct {
+				guchar signature[8]; /* must be hex 89 50 4E 47 0D 0A 1A 0A */
+				struct {
+					guint32 length; /* must be 0x0d */
+					guchar type[4]; /* must be 'I' 'H' 'D' 'R' */
+					guint32 width;
+					guint32 height;
+					guchar bitdepth;
+					guchar colortype;
+					guchar compression;
+					guchar filter;
+					guchar interlace;
+				} ihdr;
+			} *png = purple_imgstore_get_data(img); /* ATTN: this is in network byte order! */
+
+			/* check if the data is a valid png file (well, at least to some extend) */
+			if(png->signature[0] == 0x89 &&
+			   png->signature[1] == 0x50 &&
+			   png->signature[2] == 0x4e &&
+			   png->signature[3] == 0x47 &&
+			   png->signature[4] == 0x0d &&
+			   png->signature[5] == 0x0a &&
+			   png->signature[6] == 0x1a &&
+			   png->signature[7] == 0x0a &&
+			   ntohl(png->ihdr.length) == 0x0d &&
+			   png->ihdr.type[0] == 'I' &&
+			   png->ihdr.type[1] == 'H' &&
+			   png->ihdr.type[2] == 'D' &&
+			   png->ihdr.type[3] == 'R') {
+				/* parse PNG header to get the size of the image (yes, this is required) */
+				guint32 width = ntohl(png->ihdr.width);
+				guint32 height = ntohl(png->ihdr.height);
+				xmlnode *publish, *item, *data, *metadata, *info;
+				char *lengthstring, *widthstring, *heightstring;
+				
+				/* compute the sha1 hash */
+				PurpleCipherContext *ctx;
+				unsigned char digest[20];
+				char *hash;
+				char *base64avatar;
+				
+				ctx = purple_cipher_context_new_by_name("sha1", NULL);
+				purple_cipher_context_append(ctx, purple_imgstore_get_data(img), purple_imgstore_get_size(img));
+				purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL);
+				
+				/* convert digest to a string */
+				hash = g_strdup_printf("%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x",digest[0],digest[1],digest[2],digest[3],digest[4],digest[5],digest[6],digest[7],digest[8],digest[9],digest[10],digest[11],digest[12],digest[13],digest[14],digest[15],digest[16],digest[17],digest[18],digest[19]);
+				
+				publish = xmlnode_new("publish");
+				xmlnode_set_attrib(publish,"node",AVATARNAMESPACEDATA);
+				
+				item = xmlnode_new_child(publish, "item");
+				xmlnode_set_attrib(item, "id", hash);
+				
+				data = xmlnode_new_child(item, "data");
+				xmlnode_set_namespace(data,AVATARNAMESPACEDATA);
+				
+				base64avatar = purple_base64_encode(purple_imgstore_get_data(img), purple_imgstore_get_size(img));
+				xmlnode_insert_data(data,base64avatar,-1);
+				g_free(base64avatar);
+				
+				/* publish the avatar itself */
+				jabber_pep_publish((JabberStream*)gc->proto_data, publish);
+				
+				/* next step: publish the metadata */
+				publish = xmlnode_new("publish");
+				xmlnode_set_attrib(publish,"node",AVATARNAMESPACEMETA);
+				
+				item = xmlnode_new_child(publish, "item");
+				xmlnode_set_attrib(item, "id", hash);
+				
+				metadata = xmlnode_new_child(item, "metadata");
+				xmlnode_set_namespace(metadata,AVATARNAMESPACEMETA);
+				
+				info = xmlnode_new_child(metadata, "info");
+				xmlnode_set_attrib(info, "id", hash);
+				xmlnode_set_attrib(info, "type", "image/png");
+				lengthstring = g_strdup_printf("%u", (unsigned)purple_imgstore_get_size(img));
+				xmlnode_set_attrib(info, "bytes", lengthstring);
+				g_free(lengthstring);
+				widthstring = g_strdup_printf("%u", width);
+				xmlnode_set_attrib(info, "width", widthstring);
+				g_free(widthstring);
+				heightstring = g_strdup_printf("%u", height);
+				xmlnode_set_attrib(info, "height", heightstring);
+				g_free(lengthstring);
+				
+				/* publish the metadata */
+				jabber_pep_publish((JabberStream*)gc->proto_data, publish);
+				
+				g_free(hash);
+			} else { /* if(img) */
+				/* remove the metadata */
+				xmlnode *metadata, *item;
+				xmlnode *publish = xmlnode_new("publish");
+				xmlnode_set_attrib(publish,"node",AVATARNAMESPACEMETA);
+				
+				item = xmlnode_new_child(publish, "item");
+				
+				metadata = xmlnode_new_child(item, "metadata");
+				xmlnode_set_namespace(metadata,AVATARNAMESPACEMETA);
+				
+				xmlnode_new_child(metadata, "stop");
+				
+				/* publish the metadata */
+				jabber_pep_publish((JabberStream*)gc->proto_data, publish);
+			}
+		} else {
+			purple_debug(PURPLE_DEBUG_ERROR, "jabber",
+						 "jabber_set_buddy_icon received non-png data");
+		}
+	}
+
+	/* even when the image is not png, we can still publish the vCard, since this
+	   one doesn't require a specific image type */
+
+	/* publish vCard for those poor older clients */
 	jabber_set_info(gc, purple_account_get_user_info(gc->account));
 
-	jabber_presence_send(gc->account, NULL);
+	gpresence = purple_account_get_presence(gc->account);
+	status = purple_presence_get_active_status(gpresence);
+	jabber_presence_send(gc->account, status);
 }
 
 /*
@@ -659,6 +799,123 @@
 				purple_notify_user_info_add_pair(user_info, _("Operating System"), jbr->client.os);
 			}
 		}
+		if(jbr && jbr->caps) {
+			GString *tmp = g_string_new("");
+			GList *iter;
+			for(iter = jbr->caps->features; iter; iter = g_list_next(iter)) {
+				const char *feature = iter->data;
+				
+				if(!strcmp(feature, "jabber:iq:last"))
+					feature = _("Last Activity");
+				else if(!strcmp(feature, "http://jabber.org/protocol/disco#info"))
+					feature = _("Service Discovery Info");
+				else if(!strcmp(feature, "http://jabber.org/protocol/disco#items"))
+					feature = _("Service Discovery Items");
+				else if(!strcmp(feature, "http://jabber.org/protocol/address"))
+					feature = _("Extended Stanza Addressing");
+				else if(!strcmp(feature, "http://jabber.org/protocol/muc"))
+					feature = _("Multi-User Chat");
+				else if(!strcmp(feature, "http://jabber.org/protocol/muc#user"))
+					feature = _("Multi-User Chat Extended Presence Information");
+				else if(!strcmp(feature, "http://jabber.org/protocol/ibb"))
+					feature = _("In-Band Bytestreams");
+				else if(!strcmp(feature, "http://jabber.org/protocol/commands"))
+					feature = _("Ad-Hoc Commands");
+				else if(!strcmp(feature, "http://jabber.org/protocol/pubsub"))
+					feature = _("PubSub Service");
+				else if(!strcmp(feature, "http://jabber.org/protocol/bytestreams"))
+					feature = _("SOCKS5 Bytestreams");
+				else if(!strcmp(feature, "jabber:x:oob"))
+					feature = _("Out of Band Data");
+				else if(!strcmp(feature, "http://jabber.org/protocol/xhtml-im"))
+					feature = _("XHTML-IM");
+				else if(!strcmp(feature, "jabber:iq:register"))
+					feature = _("In-Band Registration");
+				else if(!strcmp(feature, "http://jabber.org/protocol/geoloc"))
+					feature = _("User Location");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0084.html"))
+					feature = _("User Avatar");
+				else if(!strcmp(feature, "http://jabber.org/protocol/chatstates"))
+					feature = _("Chat State Notifications");
+				else if(!strcmp(feature, "jabber:iq:version"))
+					feature = _("Software Version");
+				else if(!strcmp(feature, "http://jabber.org/protocol/si"))
+					feature = _("Stream Initiation");
+				else if(!strcmp(feature, "http://jabber.org/protocol/si/profile/file-transfer"))
+					feature = _("File Transfer");
+				else if(!strcmp(feature, "http://jabber.org/protocol/mood"))
+					feature = _("User Mood");
+				else if(!strcmp(feature, "http://jabber.org/protocol/activity"))
+					feature = _("User Activity");
+				else if(!strcmp(feature, "http://jabber.org/protocol/caps"))
+					feature = _("Entity Capabilities");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0116.html"))
+					feature = _("Encrypted Session Negotiations");
+				else if(!strcmp(feature, "http://jabber.org/protocol/tune"))
+					feature = _("User Tune");
+				else if(!strcmp(feature, "http://jabber.org/protocol/rosterx"))
+					feature = _("Roster Item Exchange");
+				else if(!strcmp(feature, "http://jabber.org/protocol/reach"))
+					feature = _("Reachability Address");
+				else if(!strcmp(feature, "http://jabber.org/protocol/profile"))
+					feature = _("User Profile");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0166.html#ns"))
+					feature = _("Jingle");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0167.html#ns"))
+					feature = _("Jingle Audio");
+				else if(!strcmp(feature, "http://jabber.org/protocol/nick"))
+					feature = _("User Nickname");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0176.html#ns-udp"))
+					feature = _("Jingle ICE UDP");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0176.html#ns-tcp"))
+					feature = _("Jingle ICE TCP");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0177.html#ns"))
+					feature = _("Jingle Raw UDP");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0180.html#ns"))
+					feature = _("Jingle Video");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0181.html#ns"))
+					feature = _("Jingle DTMF");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0184.html#ns"))
+					feature = _("Message Receipts");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0189.html#ns"))
+					feature = _("Public Key Publishing");
+				else if(!strcmp(feature, "http://jabber.org/protocol/chatting"))
+					feature = _("User Chatting");
+				else if(!strcmp(feature, "http://jabber.org/protocol/browsing"))
+					feature = _("User Browsing");
+				else if(!strcmp(feature, "http://jabber.org/protocol/gaming"))
+					feature = _("User Gaming");
+				else if(!strcmp(feature, "http://jabber.org/protocol/viewing"))
+					feature = _("User Viewing");
+				else if(!strcmp(feature, "urn:xmpp:ping") || !strcmp(feature, "http://www.xmpp.org/extensions/xep-0199.html#ns"))
+					feature = _("Ping");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0200.html#ns"))
+					feature = _("Stanza Encryption");
+				else if(!strcmp(feature, "urn:xmpp:time"))
+					feature = _("Entity Time");
+				else if(!strcmp(feature, "urn:xmpp:delay"))
+					feature = _("Delayed Delivery");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0204.html#ns"))
+					feature = _("Collaborative Data Objects");
+				else if(!strcmp(feature, "http://jabber.org/protocol/fileshare"))
+					feature = _("File Repository and Sharing");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0215.html#ns"))
+					feature = _("STUN Service Discovery for Jingle");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0116.html#ns"))
+					feature = _("Simplified Encrypted Session Negotiation");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0219.html#ns"))
+					feature = _("Hop Check");
+				else if(g_str_has_suffix(feature, "+notify"))
+					feature = NULL;
+				
+				if(feature)
+					g_string_append_printf(tmp, "%s\n", feature);
+			}
+			if(strlen(tmp->str) > 0)
+				purple_notify_user_info_add_pair(user_info, _("Capabilities"), tmp->str);
+			
+			g_string_free(tmp, TRUE);
+		}
 	} else {
 		for(resources = jbi->jb->resources; resources; resources = resources->next) {
 			char *purdy = NULL;
@@ -700,6 +957,123 @@
 					purple_notify_user_info_add_pair(user_info, _("Operating System"), jbr->client.os);
 				}
 			}
+			if(jbr && jbr->caps) {
+				GString *tmp = g_string_new("");
+				GList *iter;
+				for(iter = jbr->caps->features; iter; iter = g_list_next(iter)) {
+					const char *feature = iter->data;
+					
+					if(!strcmp(feature, "jabber:iq:last"))
+						feature = _("Last Activity");
+					else if(!strcmp(feature, "http://jabber.org/protocol/disco#info"))
+						feature = _("Service Discovery Info");
+					else if(!strcmp(feature, "http://jabber.org/protocol/disco#items"))
+						feature = _("Service Discovery Items");
+					else if(!strcmp(feature, "http://jabber.org/protocol/address"))
+						feature = _("Extended Stanza Addressing");
+					else if(!strcmp(feature, "http://jabber.org/protocol/muc"))
+						feature = _("Multi-User Chat");
+					else if(!strcmp(feature, "http://jabber.org/protocol/muc#user"))
+						feature = _("Multi-User Chat Extended Presence Information");
+					else if(!strcmp(feature, "http://jabber.org/protocol/ibb"))
+						feature = _("In-Band Bytestreams");
+					else if(!strcmp(feature, "http://jabber.org/protocol/commands"))
+						feature = _("Ad-Hoc Commands");
+					else if(!strcmp(feature, "http://jabber.org/protocol/pubsub"))
+						feature = _("PubSub Service");
+					else if(!strcmp(feature, "http://jabber.org/protocol/bytestreams"))
+						feature = _("SOCKS5 Bytestreams");
+					else if(!strcmp(feature, "jabber:x:oob"))
+						feature = _("Out of Band Data");
+					else if(!strcmp(feature, "http://jabber.org/protocol/xhtml-im"))
+						feature = _("XHTML-IM");
+					else if(!strcmp(feature, "jabber:iq:register"))
+						feature = _("In-Band Registration");
+					else if(!strcmp(feature, "http://jabber.org/protocol/geoloc"))
+						feature = _("User Location");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0084.html"))
+						feature = _("User Avatar");
+					else if(!strcmp(feature, "http://jabber.org/protocol/chatstates"))
+						feature = _("Chat State Notifications");
+					else if(!strcmp(feature, "jabber:iq:version"))
+						feature = _("Software Version");
+					else if(!strcmp(feature, "http://jabber.org/protocol/si"))
+						feature = _("Stream Initiation");
+					else if(!strcmp(feature, "http://jabber.org/protocol/si/profile/file-transfer"))
+						feature = _("File Transfer");
+					else if(!strcmp(feature, "http://jabber.org/protocol/mood"))
+						feature = _("User Mood");
+					else if(!strcmp(feature, "http://jabber.org/protocol/activity"))
+						feature = _("User Activity");
+					else if(!strcmp(feature, "http://jabber.org/protocol/caps"))
+						feature = _("Entity Capabilities");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0116.html"))
+						feature = _("Encrypted Session Negotiations");
+					else if(!strcmp(feature, "http://jabber.org/protocol/tune"))
+						feature = _("User Tune");
+					else if(!strcmp(feature, "http://jabber.org/protocol/rosterx"))
+						feature = _("Roster Item Exchange");
+					else if(!strcmp(feature, "http://jabber.org/protocol/reach"))
+						feature = _("Reachability Address");
+					else if(!strcmp(feature, "http://jabber.org/protocol/profile"))
+						feature = _("User Profile");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0166.html#ns"))
+						feature = _("Jingle");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0167.html#ns"))
+						feature = _("Jingle Audio");
+					else if(!strcmp(feature, "http://jabber.org/protocol/nick"))
+						feature = _("User Nickname");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0176.html#ns-udp"))
+						feature = _("Jingle ICE UDP");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0176.html#ns-tcp"))
+						feature = _("Jingle ICE TCP");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0177.html#ns"))
+						feature = _("Jingle Raw UDP");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0180.html#ns"))
+						feature = _("Jingle Video");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0181.html#ns"))
+						feature = _("Jingle DTMF");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0184.html#ns"))
+						feature = _("Message Receipts");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0189.html#ns"))
+						feature = _("Public Key Publishing");
+					else if(!strcmp(feature, "http://jabber.org/protocol/chatting"))
+						feature = _("User Chatting");
+					else if(!strcmp(feature, "http://jabber.org/protocol/browsing"))
+						feature = _("User Browsing");
+					else if(!strcmp(feature, "http://jabber.org/protocol/gaming"))
+						feature = _("User Gaming");
+					else if(!strcmp(feature, "http://jabber.org/protocol/viewing"))
+						feature = _("User Viewing");
+					else if(!strcmp(feature, "urn:xmpp:ping") || !strcmp(feature, "http://www.xmpp.org/extensions/xep-0199.html#ns"))
+						feature = _("Ping");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0200.html#ns"))
+						feature = _("Stanza Encryption");
+					else if(!strcmp(feature, "urn:xmpp:time"))
+						feature = _("Entity Time");
+					else if(!strcmp(feature, "urn:xmpp:delay"))
+						feature = _("Delayed Delivery");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0204.html#ns"))
+						feature = _("Collaborative Data Objects");
+					else if(!strcmp(feature, "http://jabber.org/protocol/fileshare"))
+						feature = _("File Repository and Sharing");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0215.html#ns"))
+						feature = _("STUN Service Discovery for Jingle");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0116.html#ns"))
+						feature = _("Simplified Encrypted Session Negotiation");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0219.html#ns"))
+						feature = _("Hop Check");
+					else if(g_str_has_suffix(feature, "+notify"))
+						feature = NULL;
+					
+					if(feature)
+						g_string_append_printf(tmp, "%s\n", feature);
+				}
+				if(strlen(tmp->str) > 0)
+					purple_notify_user_info_add_pair(user_info, _("Capabilities"), tmp->str);
+				
+				g_string_free(tmp, TRUE);
+			}
 		}
 	}
 
@@ -1023,6 +1397,109 @@
 	jabber_buddy_info_show_if_ready(jbi);
 }
 
+typedef struct _JabberBuddyAvatarUpdateURLInfo {
+	JabberStream *js;
+	char *from;
+	char *id;
+} JabberBuddyAvatarUpdateURLInfo;
+
+static void do_buddy_avatar_update_fromurl(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) {
+	JabberBuddyAvatarUpdateURLInfo *info = user_data;
+	if(!url_text) {
+		purple_debug(PURPLE_DEBUG_ERROR, "jabber",
+					 "do_buddy_avatar_update_fromurl got error \"%s\"", error_message);
+		return;
+	}
+	
+	purple_buddy_icons_set_for_user(purple_connection_get_account(info->js->gc), info->from, (void*)url_text, len, info->id);
+	g_free(info->from);
+	g_free(info->id);
+	g_free(info);
+}
+
+static void do_buddy_avatar_update_data(JabberStream *js, const char *from, xmlnode *items) {
+	xmlnode *item, *data;
+	const char *checksum;
+	char *b64data;
+	void *img;
+	size_t size;
+	if(!items)
+		return;
+	
+	item = xmlnode_get_child(items, "item");
+	if(!item)
+		return;
+	
+	data = xmlnode_get_child_with_namespace(item,"data",AVATARNAMESPACEDATA);
+	if(!data)
+		return;
+	
+	checksum = xmlnode_get_attrib(item,"id");
+	if(!checksum)
+		return;
+	
+	b64data = xmlnode_get_data(data);
+	if(!b64data)
+		return;
+	
+	img = purple_base64_decode(b64data, &size);
+	if(!img)
+		return;
+	
+	purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, img, size, checksum);
+}
+
+void jabber_buddy_avatar_update_metadata(JabberStream *js, const char *from, xmlnode *items) {
+	PurpleBuddy *buddy = purple_find_buddy(purple_connection_get_account(js->gc), from);
+	const char *checksum;
+	xmlnode *item, *metadata;
+	if(!buddy)
+		return;
+	
+	checksum = purple_buddy_icons_get_checksum_for_user(buddy);
+	item = xmlnode_get_child(items,"item");
+	metadata = xmlnode_get_child_with_namespace(item, "metadata", AVATARNAMESPACEMETA);
+	if(!metadata)
+		return;
+	/* check if we have received a stop */
+	if(xmlnode_get_child(metadata, "stop")) {
+		purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, NULL, 0, NULL);
+	} else {
+		xmlnode *info, *goodinfo = NULL;
+		
+		/* iterate over all info nodes to get one we can use */
+		for(info = metadata->child; info; info = info->next) {
+			if(info->type == XMLNODE_TYPE_TAG && !strcmp(info->name,"info")) {
+				const char *type = xmlnode_get_attrib(info,"type");
+				const char *id = xmlnode_get_attrib(info,"id");
+				
+				if(checksum && id && !strcmp(id, checksum)) {
+					/* we already have that avatar, so we don't have to do anything */
+					goodinfo = NULL;
+					break;
+				}
+				/* We'll only pick the png one for now. It's a very nice image format anyways. */
+				if(type && id && !goodinfo && !strcmp(type, "image/png"))
+					goodinfo = info;
+			}
+		}
+		if(goodinfo) {
+			const char *url = xmlnode_get_attrib(goodinfo,"url");
+			const char *id = xmlnode_get_attrib(goodinfo,"id");
+			
+			/* the avatar might either be stored in a pep node, or on a HTTP/HTTPS URL */
+			if(!url)
+				jabber_pep_request_item(js, from, AVATARNAMESPACEDATA, id, do_buddy_avatar_update_data);
+			else {
+				JabberBuddyAvatarUpdateURLInfo *info = g_new0(JabberBuddyAvatarUpdateURLInfo, 1);
+				info->js = js;
+				info->from = g_strdup(from);
+				info->id = g_strdup(id);
+				purple_util_fetch_url(url, TRUE, NULL, TRUE, do_buddy_avatar_update_fromurl, info);
+			}
+		}
+	}
+}
 
 static void jabber_buddy_info_resource_free(gpointer data)
 {
@@ -1295,7 +1772,7 @@
 	status    = purple_presence_get_active_status(gpresence);
 
 	purple_status_to_jabber(status, &state, &msg, &priority);
-	presence = jabber_presence_create(state, msg, priority);
+	presence = jabber_presence_create_js(js, state, msg, priority);
 
 	g_free(msg);
 
@@ -1389,12 +1866,54 @@
 	jabber_presence_subscription_set(js, buddy->name, "unsubscribe");
 }
 
+static void jabber_buddy_login(PurpleBlistNode *node, gpointer data) {
+	if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+		/* simply create a directed presence of the current status */
+		PurpleBuddy *buddy = (PurpleBuddy *) node;
+		PurpleConnection *gc = purple_account_get_connection(buddy->account);
+		JabberStream *js = gc->proto_data;
+		PurpleAccount *account = purple_connection_get_account(gc);
+		PurplePresence *gpresence = purple_account_get_presence(account);
+		PurpleStatus *status = purple_presence_get_active_status(gpresence);
+		xmlnode *presence;
+		JabberBuddyState state;
+		char *msg;
+		int priority;
+		
+		purple_status_to_jabber(status, &state, &msg, &priority);
+		presence = jabber_presence_create_js(js, state, msg, priority);
+		
+		g_free(msg);
+		
+		xmlnode_set_attrib(presence, "to", buddy->name);
+		
+		jabber_send(js, presence);
+		xmlnode_free(presence);
+	}
+}
+
+static void jabber_buddy_logout(PurpleBlistNode *node, gpointer data) {
+	if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+		/* simply create a directed unavailable presence */
+		PurpleBuddy *buddy = (PurpleBuddy *) node;
+		JabberStream *js = purple_account_get_connection(buddy->account)->proto_data;
+		xmlnode *presence;
+		
+		presence = jabber_presence_create_js(js, JABBER_BUDDY_STATE_UNAVAILABLE, NULL, 0);
+		
+		xmlnode_set_attrib(presence, "to", buddy->name);
+		
+		jabber_send(js, presence);
+		xmlnode_free(presence);
+	}
+}
 
 static GList *jabber_buddy_menu(PurpleBuddy *buddy)
 {
 	PurpleConnection *gc = purple_account_get_connection(buddy->account);
 	JabberStream *js = gc->proto_data;
 	JabberBuddy *jb = jabber_buddy_find(js, buddy->name, TRUE);
+	GList *jbrs;
 
 	GList *m = NULL;
 	PurpleMenuAction *act;
@@ -1439,6 +1958,38 @@
 		                           NULL, NULL);
 		m = g_list_append(m, act);
 	}
+	
+	/*
+	 * This if-condition implements parts of XEP-0100: Gateway Interaction
+	 *
+	 * According to stpeter, there is no way to know if a jid on the roster is a gateway without sending a disco#info.
+	 * However, since the gateway might appear offline to us, we cannot get that information. Therefore, I just assume
+	 * that gateways on the roster can be identified by having no '@' in their jid. This is a faily safe assumption, since
+	 * people don't tend to have a server or other service there.
+	 */
+	if (g_utf8_strchr(buddy->name, -1, '@') == NULL) {
+		act = purple_menu_action_new(_("Log In"),
+									 PURPLE_CALLBACK(jabber_buddy_login),
+									 NULL, NULL);
+		m = g_list_append(m, act);
+		act = purple_menu_action_new(_("Log Out"),
+									 PURPLE_CALLBACK(jabber_buddy_logout),
+									 NULL, NULL);
+		m = g_list_append(m, act);
+	}
+	
+	/* add all ad hoc commands to the action menu */
+	for(jbrs = jb->resources; jbrs; jbrs = g_list_next(jbrs)) {
+		JabberBuddyResource *jbr = jbrs->data;
+		GList *commands;
+		if (!jbr->commands)
+			continue;
+		for(commands = jbr->commands; commands; commands = g_list_next(commands)) {
+			JabberAdHocCommands *cmd = commands->data;
+			act = purple_menu_action_new(cmd->name, PURPLE_CALLBACK(jabber_adhoc_execute_action), cmd, NULL);
+			m = g_list_append(m, act);
+		}
+	}
 
 	return m;
 }
@@ -1728,10 +2279,10 @@
  * in purple-i18n@lists.sourceforge.net (March 2006)
  */
 static const char * jabber_user_dir_comments [] = {
-       /* current comment from Jabber User Directory users.jabber.org */
-       N_("Find a contact by entering the search criteria in the given fields. "
-          "Note: Each field supports wild card searches (%)"),
-       NULL
+	/* current comment from Jabber User Directory users.jabber.org */
+	N_("Find a contact by entering the search criteria in the given fields. "
+	   "Note: Each field supports wild card searches (%)"),
+	NULL
 };
 #endif
 
@@ -1824,14 +2375,14 @@
 				_("Search for XMPP users"), instructions, fields,
 				_("Search"), G_CALLBACK(user_search_cb),
 				_("Cancel"), G_CALLBACK(user_search_cancel_cb),
-				NULL, NULL, NULL,
+				purple_connection_get_account(js->gc), NULL, NULL,
 				usi);
 
 		g_free(instructions);
 	}
 }
 
-static void jabber_user_search_ok(JabberStream *js, const char *directory)
+void jabber_user_search(JabberStream *js, const char *directory)
 {
 	JabberIq *iq;
 
@@ -1858,7 +2409,7 @@
 			_("Select a user directory to search"),
 			js->user_directories ? js->user_directories->data : NULL,
 			FALSE, FALSE, NULL,
-			_("Search Directory"), PURPLE_CALLBACK(jabber_user_search_ok),
+			_("Search Directory"), PURPLE_CALLBACK(jabber_user_search),
 			_("Cancel"), NULL,
 			NULL, NULL, NULL,
 			js);
--- a/libpurple/protocols/jabber/buddy.h	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/protocols/jabber/buddy.h	Wed Sep 05 22:32:14 2007 +0000
@@ -22,8 +22,6 @@
 #ifndef _PURPLE_JABBER_BUDDY_H_
 #define _PURPLE_JABBER_BUDDY_H_
 
-#include "jabber.h"
-
 typedef enum {
 	JABBER_BUDDY_STATE_UNKNOWN = -2,
 	JABBER_BUDDY_STATE_ERROR = -1,
@@ -35,6 +33,12 @@
 	JABBER_BUDDY_STATE_DND
 } JabberBuddyState;
 
+#include "jabber.h"
+#include "caps.h"
+
+#define AVATARNAMESPACEDATA "http://www.xmpp.org/extensions/xep-0084.html#ns-data"
+#define AVATARNAMESPACEMETA "http://www.xmpp.org/extensions/xep-0084.html#ns-metadata"
+
 typedef struct _JabberBuddy {
 	GList *resources;
 	char *error_msg;
@@ -53,6 +57,12 @@
 	} subscription;
 } JabberBuddy;
 
+typedef struct _JabberAdHocCommands {
+	char *jid;
+	char *node;
+	char *name;
+} JabberAdHocCommands;
+
 typedef struct _JabberBuddyResource {
 	JabberBuddy *jb;
 	char *name;
@@ -71,6 +81,8 @@
 		char *name;
 		char *os;
 	} client;
+	JabberCapsClientInfo *caps;
+	GList *commands;
 } JabberBuddyResource;
 
 void jabber_buddy_free(JabberBuddy *jb);
@@ -92,6 +104,7 @@
 void jabber_set_info(PurpleConnection *gc, const char *info);
 void jabber_setup_set_info(PurplePluginAction *action);
 void jabber_set_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img);
+void jabber_buddy_avatar_update_metadata(JabberStream *js, const char *from, xmlnode *items);
 
 const char *jabber_buddy_state_get_name(JabberBuddyState state);
 const char *jabber_buddy_state_get_status_id(JabberBuddyState state);
@@ -99,6 +112,7 @@
 JabberBuddyState jabber_buddy_status_id_get_state(const char *id);
 JabberBuddyState jabber_buddy_show_get_state(const char *id);
 
+void jabber_user_search(JabberStream *js, const char *directory);
 void jabber_user_search_begin(PurplePluginAction *);
 
 void jabber_buddy_remove_all_pending_buddy_info_requests(JabberStream *js);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/caps.c	Wed Sep 05 22:32:14 2007 +0000
@@ -0,0 +1,530 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#include "caps.h"
+#include <string.h>
+#include "internal.h"
+#include "util.h"
+#include "iq.h"
+
+#define JABBER_CAPS_FILENAME "xmpp-caps.xml"
+
+static GHashTable *capstable = NULL; /* JabberCapsKey -> JabberCapsValue */
+
+typedef struct _JabberCapsKey {
+	char *node;
+	char *ver;
+} JabberCapsKey;
+
+typedef struct _JabberCapsValueExt {
+	GList *identities; /* JabberCapsIdentity */
+	GList *features; /* char * */
+} JabberCapsValueExt;
+
+typedef struct _JabberCapsValue {
+	GList *identities; /* JabberCapsIdentity */
+	GList *features; /* char * */
+	GHashTable *ext; /* char * -> JabberCapsValueExt */
+} JabberCapsValue;
+
+static guint jabber_caps_hash(gconstpointer key) {
+	const JabberCapsKey *name = key;
+	guint nodehash = g_str_hash(name->node);
+	guint verhash = g_str_hash(name->ver);
+	
+	return nodehash ^ verhash;
+}
+
+static gboolean jabber_caps_compare(gconstpointer v1, gconstpointer v2) {
+	const JabberCapsKey *name1 = v1;
+	const JabberCapsKey *name2 = v2;
+
+	return strcmp(name1->node,name2->node) == 0 && strcmp(name1->ver,name2->ver) == 0;
+}
+
+static void jabber_caps_destroy_key(gpointer key) {
+	JabberCapsKey *keystruct = key;
+	g_free(keystruct->node);
+	g_free(keystruct->ver);
+	g_free(keystruct);
+}
+
+static void jabber_caps_destroy_value(gpointer value) {
+	JabberCapsValue *valuestruct = value;
+	while(valuestruct->identities) {
+		JabberCapsIdentity *id = valuestruct->identities->data;
+		g_free(id->category);
+		g_free(id->type);
+		g_free(id->name);
+		g_free(id);
+		
+		valuestruct->identities = g_list_delete_link(valuestruct->identities,valuestruct->identities);
+	}
+	while(valuestruct->features) {
+		g_free(valuestruct->features->data);
+		valuestruct->features = g_list_delete_link(valuestruct->features,valuestruct->features);
+	}
+	g_hash_table_destroy(valuestruct->ext);
+	g_free(valuestruct);
+}
+
+static void jabber_caps_ext_destroy_value(gpointer value) {
+	JabberCapsValueExt *valuestruct = value;
+	while(valuestruct->identities) {
+		JabberCapsIdentity *id = valuestruct->identities->data;
+		g_free(id->category);
+		g_free(id->type);
+		g_free(id->name);
+		g_free(id);
+		
+		valuestruct->identities = g_list_delete_link(valuestruct->identities,valuestruct->identities);
+	}
+	while(valuestruct->features) {
+		g_free(valuestruct->features->data);
+		valuestruct->features = g_list_delete_link(valuestruct->features,valuestruct->features);
+	}
+	g_free(valuestruct);
+}
+
+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);
+	jabber_caps_load();
+}
+
+static void jabber_caps_load(void) {
+	xmlnode *capsdata = purple_util_read_xml_from_file(JABBER_CAPS_FILENAME, "XMPP capabilities cache");
+	xmlnode *client;
+	if(!capsdata || strcmp(capsdata->name, "capabilities"))
+		return;
+	
+	for(client = capsdata->child; client; client = client->next) {
+		if(client->type != XMLNODE_TYPE_TAG)
+			continue;
+		if(!strcmp(client->name, "client")) {
+			JabberCapsKey *key = g_new0(JabberCapsKey, 1);
+			JabberCapsValue *value = g_new0(JabberCapsValue, 1);
+			xmlnode *child;
+			key->node = g_strdup(xmlnode_get_attrib(client,"node"));
+			key->ver  = g_strdup(xmlnode_get_attrib(client,"ver"));
+			value->ext = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, jabber_caps_ext_destroy_value);
+			for(child = client->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);
+					
+					value->identities = g_list_append(value->identities,id);
+				} else if(!strcmp(child->name,"ext")) {
+					const char *identifier = xmlnode_get_attrib(child, "identifier");
+					if(identifier) {
+						xmlnode *extchild;
+						
+						JabberCapsValueExt *extvalue = g_new0(JabberCapsValueExt, 1);
+						
+						for(extchild = child->child; extchild; extchild = extchild->next) {
+							if(extchild->type != XMLNODE_TYPE_TAG)
+								continue;
+							if(!strcmp(extchild->name,"feature")) {
+								const char *var = xmlnode_get_attrib(extchild, "var");
+								if(!var)
+									continue;
+								extvalue->features = g_list_append(extvalue->features,g_strdup(var));
+							} else if(!strcmp(extchild->name,"identity")) {
+								const char *category = xmlnode_get_attrib(extchild, "category");
+								const char *type = xmlnode_get_attrib(extchild, "type");
+								const char *name = xmlnode_get_attrib(extchild, "name");
+								
+								JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1);
+								id->category = g_strdup(category);
+								id->type = g_strdup(type);
+								id->name = g_strdup(name);
+								
+								extvalue->identities = g_list_append(extvalue->identities,id);
+							}
+						}
+						g_hash_table_replace(value->ext, g_strdup(identifier), extvalue);
+					}
+				}
+			}
+			g_hash_table_replace(capstable, key, value);
+		}
+	}
+}
+
+static void jabber_caps_store_ext(gpointer key, gpointer value, gpointer user_data) {
+	const char *extname = key;
+	JabberCapsValueExt *props = value;
+	xmlnode *root = user_data;
+	xmlnode *ext = xmlnode_new_child(root,"ext");
+	GList *iter;
+	
+	xmlnode_set_attrib(ext,"identifier",extname);
+	
+	for(iter = props->identities; iter; iter = g_list_next(iter)) {
+		JabberCapsIdentity *id = iter->data;
+		xmlnode *identity = xmlnode_new_child(ext, "identity");
+		xmlnode_set_attrib(identity, "category", id->category);
+		xmlnode_set_attrib(identity, "type", id->type);
+		xmlnode_set_attrib(identity, "name", id->name);
+	}
+	
+	for(iter = props->features; iter; iter = g_list_next(iter)) {
+		const char *feat = iter->data;
+		xmlnode *feature = xmlnode_new_child(ext, "feature");
+		xmlnode_set_attrib(feature, "var", feat);
+	}
+}
+
+static void jabber_caps_store_client(gpointer key, gpointer value, gpointer user_data) {
+	JabberCapsKey *clientinfo = key;
+	JabberCapsValue *props = value;
+	xmlnode *root = user_data;
+	xmlnode *client = xmlnode_new_child(root,"client");
+	GList *iter;
+
+	xmlnode_set_attrib(client,"node",clientinfo->node);
+	xmlnode_set_attrib(client,"ver",clientinfo->ver);
+	
+	for(iter = props->identities; iter; iter = g_list_next(iter)) {
+		JabberCapsIdentity *id = iter->data;
+		xmlnode *identity = xmlnode_new_child(client, "identity");
+		xmlnode_set_attrib(identity, "category", id->category);
+		xmlnode_set_attrib(identity, "type", id->type);
+		xmlnode_set_attrib(identity, "name", id->name);
+	}
+
+	for(iter = props->features; iter; iter = g_list_next(iter)) {
+		const char *feat = iter->data;
+		xmlnode *feature = xmlnode_new_child(client, "feature");
+		xmlnode_set_attrib(feature, "var", feat);
+	}
+	
+	g_hash_table_foreach(props->ext,jabber_caps_store_ext,client);
+}
+
+static void jabber_caps_store(void) {
+	xmlnode *root = xmlnode_new("capabilities");
+	g_hash_table_foreach(capstable, jabber_caps_store_client, root);
+	purple_util_write_data_to_file(JABBER_CAPS_FILENAME, xmlnode_to_formatted_str(root, NULL), -1);
+}
+
+/* this function assumes that all information is available locally */
+static JabberCapsClientInfo *jabber_caps_collect_info(const char *node, const char *ver, GList *ext) {
+	JabberCapsClientInfo *result = g_new0(JabberCapsClientInfo, 1);
+	JabberCapsKey *key = g_new0(JabberCapsKey, 1);
+	JabberCapsValue *caps;
+	GList *iter;
+	
+	key->node = g_strdup(node);
+	key->ver = g_strdup(ver);
+	
+	caps = g_hash_table_lookup(capstable,key);
+	
+	g_free(key->node);
+	g_free(key->ver);
+	g_free(key);
+	
+	/* join all information */
+	for(iter = caps->identities; iter; iter = g_list_next(iter)) {
+		JabberCapsIdentity *id = iter->data;
+		JabberCapsIdentity *newid = g_new0(JabberCapsIdentity, 1);
+		newid->category = g_strdup(id->category);
+		newid->type = g_strdup(id->type);
+		newid->name = g_strdup(id->name);
+		
+		result->identities = g_list_append(result->identities,newid);
+	}
+	for(iter = caps->features; iter; iter = g_list_next(iter)) {
+		const char *feat = iter->data;
+		char *newfeat = g_strdup(feat);
+		
+		result->features = g_list_append(result->features,newfeat);
+	}
+	
+	for(iter = ext; iter; iter = g_list_next(iter)) {
+		const char *extname = iter->data;
+		JabberCapsValueExt *extinfo = g_hash_table_lookup(caps->ext,extname);
+		
+		if(extinfo) {
+			GList *iter2;
+			for(iter2 = extinfo->identities; iter2; iter2 = g_list_next(iter2)) {
+				JabberCapsIdentity *id = iter2->data;
+				JabberCapsIdentity *newid = g_new0(JabberCapsIdentity, 1);
+				newid->category = g_strdup(id->category);
+				newid->type = g_strdup(id->type);
+				newid->name = g_strdup(id->name);
+				
+				result->identities = g_list_append(result->identities,newid);
+			}
+			for(iter2 = extinfo->features; iter2; iter2 = g_list_next(iter2)) {
+				const char *feat = iter2->data;
+				char *newfeat = g_strdup(feat);
+				
+				result->features = g_list_append(result->features,newfeat);
+			}
+		}
+	}
+	return result;
+}
+
+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;
+	
+	char *who;
+	char *node;
+	char *ver;
+	GList *ext;
+	unsigned extOutstanding;
+} jabber_caps_cbplususerdata;
+
+static void jabber_caps_get_info_check_completion(jabber_caps_cbplususerdata *userdata) {
+	if(userdata->extOutstanding == 0) {
+		userdata->cb(jabber_caps_collect_info(userdata->node, userdata->ver, userdata->ext), userdata->user_data);
+		g_free(userdata->who);
+		g_free(userdata->node);
+		g_free(userdata->ver);
+		while(userdata->ext) {
+			g_free(userdata->ext->data);
+			userdata->ext = g_list_delete_link(userdata->ext,userdata->ext);
+		}
+		g_free(userdata);
+	}
+}
+
+static void jabber_caps_ext_iqcb(JabberStream *js, xmlnode *packet, gpointer data) {
+	/* collect data and fetch all exts */
+	xmlnode *query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#info");
+	xmlnode *child;
+	jabber_caps_cbplususerdata *userdata = data;
+	JabberCapsKey *clientkey = g_new0(JabberCapsKey, 1);
+	JabberCapsValue *client;
+	JabberCapsValueExt *value = g_new0(JabberCapsValueExt, 1);
+	const char *node = xmlnode_get_attrib(query, "node");
+	const char *key;
+	
+	--userdata->extOutstanding;
+	
+	if(node) {
+		clientkey->node = g_strdup(userdata->node);
+		clientkey->ver = g_strdup(userdata->ver);
+		
+		client = g_hash_table_lookup(capstable,clientkey);
+		
+		g_free(clientkey->node);
+		g_free(clientkey->ver);
+		g_free(clientkey);
+		
+		/* split node by #, key either points to \0 or the correct ext afterwards */
+		for(key = node; key[0] != '\0'; ++key) {
+			if(key[0] == '#') {
+				++key;
+				break;
+			}
+		}
+
+		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);
+				
+				value->identities = g_list_append(value->identities,id);
+			}
+		}
+		g_hash_table_replace(client->ext, g_strdup(key), value);
+		
+		jabber_caps_store();
+	}
+	
+	jabber_caps_get_info_check_completion(userdata);
+}
+
+static void jabber_caps_client_iqcb(JabberStream *js, xmlnode *packet, gpointer data) {
+	/* collect data and fetch all exts */
+	xmlnode *query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#info");
+	xmlnode *child;
+	GList *iter;
+	jabber_caps_cbplususerdata *userdata = data;
+	JabberCapsKey *key = g_new0(JabberCapsKey, 1);
+	JabberCapsValue *value = g_new0(JabberCapsValue, 1);
+	key->node = g_strdup(userdata->node);
+	key->ver = g_strdup(userdata->ver);
+	
+	value->ext = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, jabber_caps_ext_destroy_value);
+
+	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);
+			
+			value->identities = g_list_append(value->identities,id);
+		}
+	}
+	g_hash_table_replace(capstable, key, value);
+				
+	/* 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);
+		xmlnode_set_attrib(query, "node", node);
+		g_free(node);
+		xmlnode_set_attrib(iq->node, "to", userdata->who);
+
+		jabber_iq_set_callback(iq,jabber_caps_ext_iqcb,userdata);
+		jabber_iq_send(iq);
+	}
+	
+	jabber_caps_store();
+	
+	jabber_caps_get_info_check_completion(userdata);
+}
+
+void jabber_caps_get_info(JabberStream *js, const char *who, const char *node, const char *ver, const char *ext, jabber_caps_get_info_cb cb, gpointer user_data) {
+	JabberCapsValue *client;
+	JabberCapsKey *key = g_new0(JabberCapsKey, 1);
+	char *originalext = g_strdup(ext);
+	char *oneext, *ctx;
+	jabber_caps_cbplususerdata *userdata = g_new0(jabber_caps_cbplususerdata, 1);
+	userdata->cb = cb;
+	userdata->user_data = user_data;
+	userdata->who = g_strdup(who);
+	userdata->node = g_strdup(node);
+	userdata->ver = g_strdup(ver);
+	
+	if(originalext)
+		for(oneext = strtok_r(originalext, " ", &ctx); oneext; oneext = strtok_r(NULL, " ", &ctx)) {
+			userdata->ext = g_list_append(userdata->ext,g_strdup(oneext));
+			++userdata->extOutstanding;
+		}
+	g_free(originalext);
+	
+	key->node = g_strdup(node);
+	key->ver = g_strdup(ver);
+	
+	client = g_hash_table_lookup(capstable, key);
+	
+	g_free(key->node);
+	g_free(key->ver);
+	g_free(key);
+	
+	if(!client) {
+		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 *nodever = g_strdup_printf("%s#%s", node, ver);
+		xmlnode_set_attrib(query, "node", nodever);
+		g_free(nodever);
+		xmlnode_set_attrib(iq->node, "to", who);
+		
+		jabber_iq_set_callback(iq,jabber_caps_client_iqcb,userdata);
+		jabber_iq_send(iq);
+	} else {
+		GList *iter; 
+		/* fetch unknown exts only */
+		for(iter = userdata->ext; iter; iter = g_list_next(iter)) {
+			JabberCapsValueExt *extvalue = g_hash_table_lookup(client->ext, (const char*)iter->data);
+			JabberIq *iq;
+			xmlnode *query;
+			char *nodever;
+			
+			if(extvalue) {
+				/* we already have this ext, don't bother with it */
+				--userdata->extOutstanding;
+				continue;
+			}
+			
+			iq = jabber_iq_new_query(js,JABBER_IQ_GET,"http://jabber.org/protocol/disco#info");
+			query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#info");
+			nodever = g_strdup_printf("%s#%s", node, (const char*)iter->data);
+			xmlnode_set_attrib(query, "node", nodever);
+			g_free(nodever);
+			xmlnode_set_attrib(iq->node, "to", who);
+			
+			jabber_iq_set_callback(iq,jabber_caps_ext_iqcb,userdata);
+			jabber_iq_send(iq);
+		}
+		/* maybe we have all data available anyways? This is the ideal case where no network traffic is necessary */
+		jabber_caps_get_info_check_completion(userdata);
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/caps.h	Wed Sep 05 22:32:14 2007 +0000
@@ -0,0 +1,49 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#ifndef _PURPLE_JABBER_CAPS_H_
+#define _PURPLE_JABBER_CAPS_H_
+
+typedef struct _JabberCapsClientInfo JabberCapsClientInfo;
+
+#include "jabber.h"
+
+/* Implementation of XEP-0115 */
+
+typedef struct _JabberCapsIdentity {
+	char *category;
+	char *type;
+	char *name;
+} JabberCapsIdentity;
+
+struct _JabberCapsClientInfo {
+	GList *identities; /* JabberCapsIdentity */
+	GList *features; /* char * */
+};
+
+typedef void (*jabber_caps_get_info_cb)(JabberCapsClientInfo *info, gpointer user_data);
+
+void jabber_caps_init(void);
+
+void jabber_caps_get_info(JabberStream *js, const char *who, const char *node, const char *ver, const char *ext, jabber_caps_get_info_cb cb, gpointer user_data);
+void jabber_caps_free_clientinfo(JabberCapsClientInfo *clientinfo);
+
+#endif /* _PURPLE_JABBER_CAPS_H_ */
--- a/libpurple/protocols/jabber/chat.c	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/protocols/jabber/chat.c	Wed Sep 05 22:32:14 2007 +0000
@@ -261,7 +261,7 @@
 
 	purple_status_to_jabber(status, &state, &msg, &priority);
 
-	presence = jabber_presence_create(state, msg, priority);
+	presence = jabber_presence_create_js(js, state, msg, priority);
 	full_jid = g_strdup_printf("%s/%s", room_jid, handle);
 	xmlnode_set_attrib(presence, "to", full_jid);
 	g_free(full_jid);
@@ -634,7 +634,7 @@
 
 	purple_status_to_jabber(status, &state, &msg, &priority);
 
-	presence = jabber_presence_create(state, msg, priority);
+	presence = jabber_presence_create_js(chat->js, state, msg, priority);
 	full_jid = g_strdup_printf("%s@%s/%s", chat->room, chat->server, nick);
 	xmlnode_set_attrib(presence, "to", full_jid);
 	g_free(full_jid);
--- a/libpurple/protocols/jabber/disco.c	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/protocols/jabber/disco.c	Wed Sep 05 22:32:14 2007 +0000
@@ -30,15 +30,19 @@
 #include "jabber.h"
 #include "presence.h"
 #include "roster.h"
+#include "pep.h"
+#include "adhoccommands.h"
+
 
 struct _jabber_disco_info_cb_data {
 	gpointer data;
 	JabberDiscoInfoCallback *callback;
 };
 
-#define SUPPORT_FEATURE(x) \
+#define SUPPORT_FEATURE(x) { \
 	feature = xmlnode_new_child(query, "feature"); \
-	xmlnode_set_attrib(feature, "var", x);
+	xmlnode_set_attrib(feature, "var", x); \
+}
 
 
 void jabber_disco_info_parse(JabberStream *js, xmlnode *packet) {
@@ -72,7 +76,6 @@
 			xmlnode_set_attrib(query, "node", node);
 
 		if(!node || !strcmp(node, CAPS0115_NODE "#" VERSION)) {
-
 			identity = xmlnode_new_child(query, "identity");
 			xmlnode_set_attrib(identity, "category", "client");
 			xmlnode_set_attrib(identity, "type", "pc"); /* XXX: bot, console,
@@ -98,18 +101,62 @@
 			SUPPORT_FEATURE("http://jabber.org/protocol/si/profile/file-transfer")
 			SUPPORT_FEATURE("http://jabber.org/protocol/xhtml-im")
 			SUPPORT_FEATURE("urn:xmpp:ping")
+			SUPPORT_FEATURE("http://www.xmpp.org/extensions/xep-0199.html#ns")
+			
+			if(!node) { /* non-caps disco#info, add all enabled extensions */
+				GList *features;
+				for(features = jabber_features; features; features = features->next) {
+					JabberFeature *feat = (JabberFeature*)features->data;
+					if(feat->is_enabled == NULL || feat->is_enabled(js, feat->shortname, feat->namespace) == TRUE)
+						SUPPORT_FEATURE(feat->namespace);
+				}
+			}
 		} else {
-			xmlnode *error, *inf;
-
-			/* XXX: gross hack, implement jabber_iq_set_type or something */
-			xmlnode_set_attrib(iq->node, "type", "error");
-			iq->type = JABBER_IQ_ERROR;
-
-			error = xmlnode_new_child(query, "error");
-			xmlnode_set_attrib(error, "code", "404");
-			xmlnode_set_attrib(error, "type", "cancel");
-			inf = xmlnode_new_child(error, "item-not-found");
-			xmlnode_set_namespace(inf, "urn:ietf:params:xml:ns:xmpp-stanzas");
+			const char *ext = NULL;
+			unsigned pos;
+			unsigned nodelen = strlen(node);
+			unsigned capslen = strlen(CAPS0115_NODE);
+			/* do a basic plausability check */
+			if(nodelen > capslen+1) {
+				/* verify that the string is CAPS0115#<ext> and get the pointer to the ext part */
+				for(pos = 0; pos < capslen+1; ++pos) {
+					if(pos == capslen) {
+						if(node[pos] == '#')
+							ext = &node[pos+1];
+						else
+							break;
+					} else if(node[pos] != CAPS0115_NODE[pos])
+					break;
+				}
+				
+				if(ext != NULL) {
+					/* look for that ext */
+					GList *features;
+					for(features = jabber_features; features; features = features->next) {
+						JabberFeature *feat = (JabberFeature*)features->data;
+						if(!strcmp(feat->shortname, ext)) {
+							SUPPORT_FEATURE(feat->namespace);
+							break;
+						}
+					}
+					if(features == NULL)
+						ext = NULL;
+				}
+			}
+			
+			if(ext == NULL) {
+				xmlnode *error, *inf;
+				
+				/* XXX: gross hack, implement jabber_iq_set_type or something */
+				xmlnode_set_attrib(iq->node, "type", "error");
+				iq->type = JABBER_IQ_ERROR;
+				
+				error = xmlnode_new_child(query, "error");
+				xmlnode_set_attrib(error, "code", "404");
+				xmlnode_set_attrib(error, "type", "cancel");
+				inf = xmlnode_new_child(error, "item-not-found");
+				xmlnode_set_namespace(inf, "urn:ietf:params:xml:ns:xmpp-stanzas");
+			}
 		}
 
 		jabber_iq_send(iq);
@@ -165,6 +212,11 @@
 					capabilities |= JABBER_CAP_IQ_SEARCH;
 				else if(!strcmp(var, "jabber:iq:register"))
 					capabilities |= JABBER_CAP_IQ_REGISTER;
+				else if(!strcmp(var, "http://www.xmpp.org/extensions/xep-0199.html#ns"))
+					capabilities |= JABBER_CAP_PING;
+				else if(!strcmp(var, "http://jabber.org/protocol/commands")) {
+					capabilities |= JABBER_CAP_ADHOC;
+				}
 			}
 		}
 
@@ -208,6 +260,17 @@
 	if(type && !strcmp(type, "get")) {
 		JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_RESULT,
 				"http://jabber.org/protocol/disco#items");
+		
+		/* preserve node */
+		xmlnode *iq_query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#items");
+		if(iq_query) {
+			xmlnode *query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#items");
+			if(query) {
+				const char *node = xmlnode_get_attrib(query,"node");
+				if(node)
+					xmlnode_set_attrib(iq_query,"node",node);
+			}
+		}
 
 		jabber_iq_set_id(iq, xmlnode_get_attrib(packet, "id"));
 
@@ -227,7 +290,13 @@
 		jabber_roster_request(js);
 	}
 
-	/* when we get the roster back, we'll send our initial presence */
+	/* Send initial presence; this will trigger receipt of presence for contacts on the roster */
+	jabber_presence_send(js->gc->account, NULL);
+	
+	if (js->server_caps & JABBER_CAP_ADHOC) {
+		/* The server supports ad-hoc commands, so let's request the list */
+		jabber_adhoc_server_get_list(js);
+	}
 }
 
 static void
@@ -260,9 +329,11 @@
 	     child = xmlnode_get_next_twin(child)) {
 		const char *category, *type, *name;
 		category = xmlnode_get_attrib(child, "category");
+		type = xmlnode_get_attrib(child, "type");
+		if(category && type && !strcmp(category, "pubsub") && !strcmp(type,"pep"))
+			js->pep = TRUE;
 		if (!category || strcmp(category, "server"))
 			continue;
-		type = xmlnode_get_attrib(child, "type");
 		if (!type || strcmp(type, "im"))
 			continue;
 
@@ -291,6 +362,8 @@
 		} else if (!strcmp("google:roster", var)) {
 			js->server_caps |= JABBER_CAP_GOOGLE_ROSTER;
 			jabber_google_roster_init(js);
+		} else if (!strcmp("http://jabber.org/protocol/commands", var)) {
+			js->server_caps |= JABBER_CAP_ADHOC;
 		}
 	}
 
--- a/libpurple/protocols/jabber/iq.c	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/protocols/jabber/iq.c	Wed Sep 05 22:32:14 2007 +0000
@@ -31,6 +31,8 @@
 #include "oob.h"
 #include "roster.h"
 #include "si.h"
+#include "ping.h"
+#include "adhoccommands.h"
 
 #ifdef _WIN32
 #include "utsname.h"
@@ -343,6 +345,13 @@
 		jabber_gmail_poke(js, packet);
 		return;
 	}
+	
+	purple_debug_info("jabber", "jabber_iq_parse\n");
+
+	if(xmlnode_get_child_with_namespace(packet, "ping", "urn:xmpp:ping")) {
+		jabber_ping_parse(js, packet);
+		return;
+	}
 
 	/* If we get here, send the default error reply mandated by XMPP-CORE */
 	if(type && (!strcmp(type, "set") || !strcmp(type, "get"))) {
--- 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;
 }
--- a/libpurple/protocols/jabber/jabber.h	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Wed Sep 05 22:32:14 2007 +0000
@@ -12,16 +12,42 @@
  *
  * 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
  */
 #ifndef _PURPLE_JABBER_H_
 #define _PURPLE_JABBER_H_
 
+typedef enum {
+	JABBER_CAP_NONE			  = 0,
+	JABBER_CAP_XHTML		  = 1 << 0,
+	JABBER_CAP_COMPOSING	  = 1 << 1,
+	JABBER_CAP_SI			  = 1 << 2,
+	JABBER_CAP_SI_FILE_XFER	  = 1 << 3,
+	JABBER_CAP_BYTESTREAMS	  = 1 << 4,
+	JABBER_CAP_IBB			  = 1 << 5,
+	JABBER_CAP_CHAT_STATES	  = 1 << 6,
+	JABBER_CAP_IQ_SEARCH	  = 1 << 7,
+	JABBER_CAP_IQ_REGISTER	  = 1 << 8,
+	
+	/* Google Talk extensions: 
+	* http://code.google.com/apis/talk/jep_extensions/extensions.html
+	*/
+	JABBER_CAP_GMAIL_NOTIFY	  = 1 << 9,
+	JABBER_CAP_GOOGLE_ROSTER  = 1 << 10,
+	
+	JABBER_CAP_PING			  = 1 << 11,
+	JABBER_CAP_ADHOC		  = 1 << 12,
+	
+	JABBER_CAP_RETRIEVED	  = 1 << 31
+} JabberCapabilities;
+
+typedef struct _JabberStream JabberStream;
+
 #include <libxml/parser.h>
 #include <glib.h>
 #include "circbuffer.h"
@@ -32,6 +58,7 @@
 
 #include "jutil.h"
 #include "xmlnode.h"
+#include "buddy.h"
 
 #ifdef HAVE_CYRUS_SASL
 #include <sasl/sasl.h>
@@ -40,36 +67,16 @@
 #define CAPS0115_NODE "http://pidgin.im/caps"
 
 typedef enum {
-	JABBER_CAP_NONE           = 0,
-	JABBER_CAP_XHTML          = 1 << 0,
-	JABBER_CAP_COMPOSING      = 1 << 1,
-	JABBER_CAP_SI             = 1 << 2,
-	JABBER_CAP_SI_FILE_XFER   = 1 << 3,
-	JABBER_CAP_BYTESTREAMS    = 1 << 4,
-	JABBER_CAP_IBB            = 1 << 5,
-	JABBER_CAP_CHAT_STATES    = 1 << 6,
-	JABBER_CAP_IQ_SEARCH      = 1 << 7,
-	JABBER_CAP_IQ_REGISTER    = 1 << 8,
-
-	/* Google Talk extensions: 
-	 * http://code.google.com/apis/talk/jep_extensions/extensions.html
-	 */
-	JABBER_CAP_GMAIL_NOTIFY   = 1 << 9,
-	JABBER_CAP_GOOGLE_ROSTER  = 1 << 10,
-
-	JABBER_CAP_RETRIEVED      = 1 << 31
-} JabberCapabilities;
-
-typedef enum {
 	JABBER_STREAM_OFFLINE,
 	JABBER_STREAM_CONNECTING,
 	JABBER_STREAM_INITIALIZING,
+	JABBER_STREAM_INITIALIZING_ENCRYPTION,
 	JABBER_STREAM_AUTHENTICATING,
 	JABBER_STREAM_REINITIALIZING,
 	JABBER_STREAM_CONNECTED
 } JabberStreamState;
 
-typedef struct _JabberStream
+struct _JabberStream
 {
 	int fd;
 
@@ -152,12 +159,50 @@
 	int sasl_state;
 	int sasl_maxbuf;
 	GString *sasl_mechs;
-
+	
+	gboolean unregistration;
+	PurpleAccountUnregistrationCb unregistration_cb;
+	void *unregistration_user_data;
+	
 	gboolean vcard_fetched;
+	
+	/* does the local server support PEP? */
+	gboolean pep;
+	
+	/* Is Buzz enabled? */
+	gboolean allowBuzz;
+	
+	/* A list of JabberAdHocCommands supported by the server */
+	GList *commands;
+	
+	/* last presence update to check for differences */
+	JabberBuddyState old_state;
+	char *old_msg;
+	int old_priority;
+	char *old_avatarhash;
+	
+	/* same for user tune */
+	char *old_artist;
+	char *old_title;
+	char *old_source;
+	char *old_uri;
+	int old_length;
+	char *old_track;
+};
 
-} JabberStream;
+typedef gboolean (JabberFeatureEnabled)(JabberStream *js, const gchar *shortname, const gchar *namespace);
 
-void jabber_process_packet(JabberStream *js, xmlnode *packet);
+typedef struct _JabberFeature
+{
+	gchar *shortname;
+	gchar *namespace;
+	JabberFeatureEnabled *is_enabled;
+} JabberFeature;
+
+/* what kind of additional features as returned from disco#info are supported? */
+extern GList *jabber_features;
+
+void jabber_process_packet(JabberStream *js, xmlnode **packet);
 void jabber_send(JabberStream *js, xmlnode *data);
 void jabber_send_raw(JabberStream *js, const char *data, int len);
 
@@ -170,6 +215,9 @@
 
 char *jabber_parse_error(JabberStream *js, xmlnode *packet);
 
+void jabber_add_feature(const gchar *shortname, const gchar *namespace, JabberFeatureEnabled cb); /* cb may be NULL */
+void jabber_remove_feature(const gchar *shortname);
+
 /** PRPL functions */
 const char *jabber_list_icon(PurpleAccount *a, PurpleBuddy *b);
 const char* jabber_list_emblem(PurpleBuddy *b);
@@ -180,7 +228,9 @@
 void jabber_close(PurpleConnection *gc);
 void jabber_idle_set(PurpleConnection *gc, int idle);
 void jabber_keepalive(PurpleConnection *gc);
+void jabber_register_gateway(JabberStream *js, const char *gateway);
 void jabber_register_account(PurpleAccount *account);
+void jabber_unregister_account(PurpleAccount *account, PurpleAccountUnregistrationCb cb, void *user_data);
 void jabber_convo_closed(PurpleConnection *gc, const char *who);
 PurpleChat *jabber_find_blist_chat(PurpleAccount *account, const char *name);
 gboolean jabber_offline_message(const PurpleBuddy *buddy);
--- a/libpurple/protocols/jabber/jutil.h	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/protocols/jabber/jutil.h	Wed Sep 05 22:32:14 2007 +0000
@@ -22,6 +22,8 @@
 #ifndef _PURPLE_JABBER_JUTIL_H_
 #define _PURPLE_JABBER_JUTIL_H_
 
+#include "account.h"
+
 typedef struct _JabberID {
 	char *node;
 	char *domain;
--- a/libpurple/protocols/jabber/libxmpp.c	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Wed Sep 05 22:32:14 2007 +0000
@@ -39,6 +39,9 @@
 #include "message.h"
 #include "presence.h"
 #include "google.h"
+#include "pep.h"
+#include "usertune.h"
+#include "caps.h"
 
 static PurplePluginProtocolInfo prpl_info =
 {
@@ -52,7 +55,7 @@
 #endif
 	NULL,							/* user_splits */
 	NULL,							/* protocol_options */
-	{"png,gif,jpeg", 32, 32, 96, 96, 8191, PURPLE_ICON_SCALE_SEND | PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */
+	{"png", 32, 32, 96, 96, 8191, PURPLE_ICON_SCALE_SEND | PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */
 	jabber_list_icon,				/* list_icon */
 	jabber_list_emblem,			/* list_emblems */
 	jabber_status_text,				/* status_text */
@@ -111,11 +114,11 @@
 	NULL,							/* whiteboard_prpl_ops */
 	jabber_prpl_send_raw,			/* send_raw */
 	jabber_roomlist_room_serialize, /* roomlist_room_serialize */
+	jabber_unregister_account,		/* unregister_user */
 
 	/* padding */
 	NULL,
 	NULL,
-	NULL,
 	NULL
 };
 
@@ -191,49 +194,63 @@
 static void
 init_plugin(PurplePlugin *plugin)
 {
-        PurpleAccountUserSplit *split;
-        PurpleAccountOption *option;
-
+	PurpleAccountUserSplit *split;
+	PurpleAccountOption *option;
+	
 	/* Translators: 'domain' is used here in the context of Internet domains, e.g. pidgin.im */
-        split = purple_account_user_split_new(_("Domain"), NULL, '@');
-		purple_account_user_split_set_reverse(split, FALSE);
-        prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
-
-        split = purple_account_user_split_new(_("Resource"), "Home", '/');
-		purple_account_user_split_set_reverse(split, FALSE);
-        prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
-
-        option = purple_account_option_bool_new(_("Force old (port 5223) SSL"), "old_ssl", FALSE);
-        prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
-                        option);
-
-        option = purple_account_option_bool_new(
-                        _("Allow plaintext auth over unencrypted streams"),
-                        "auth_plain_in_clear", FALSE);
-        prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
-                        option);
+	split = purple_account_user_split_new(_("Domain"), NULL, '@');
+	purple_account_user_split_set_reverse(split, FALSE);
+	prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
+	
+	split = purple_account_user_split_new(_("Resource"), "Home", '/');
+	purple_account_user_split_set_reverse(split, FALSE);
+	prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
+	
+	option = purple_account_option_bool_new(_("Require SSL/TLS"), "require_tls", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+											   option);
+	
+	option = purple_account_option_bool_new(_("Force old (port 5223) SSL"), "old_ssl", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+											   option);
+	
+	option = purple_account_option_bool_new(
+											_("Allow plaintext auth over unencrypted streams"),
+											"auth_plain_in_clear", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+											   option);
+	
+	option = purple_account_option_int_new(_("Connect port"), "port", 5222);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+											   option);
+	
+	option = purple_account_option_string_new(_("Connect server"),
+											  "connect_server", NULL);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+											   option);
+	
+	
+	jabber_init_plugin(plugin);
+	
+	purple_prefs_remove("/plugins/prpl/jabber");
+	
+	/* XXX - If any other plugin wants SASL this won't be good ... */
+#ifdef HAVE_CYRUS_SASL
+	sasl_client_init(NULL);
+#endif
+	jabber_register_commands();
+	
+	jabber_iq_init();
+	jabber_pep_init();
+	
+	jabber_tune_init();
+	jabber_caps_init();
 
-        option = purple_account_option_int_new(_("Connect port"), "port", 5222);
-        prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
-                        option);
-
-        option = purple_account_option_string_new(_("Connect server"),
-                        "connect_server", NULL);
-        prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
-                        option);
-
-
-        jabber_init_plugin(plugin);
-
-        purple_prefs_remove("/plugins/prpl/jabber");
-
-        /* XXX - If any other plugin wants SASL this won't be good ... */
-#ifdef HAVE_CYRUS_SASL
-        sasl_client_init(NULL);
-#endif
-        jabber_register_commands();
-
-        jabber_iq_init();
+	jabber_add_feature("avatarmeta", AVATARNAMESPACEMETA, jabber_pep_namespace_only_when_pep_enabled_cb);
+	jabber_add_feature("avatardata", AVATARNAMESPACEDATA, jabber_pep_namespace_only_when_pep_enabled_cb);
+	jabber_add_feature("buzz", "http://www.xmpp.org/extensions/xep-0224.html#ns", jabber_buzz_isenabled);
+	
+	jabber_pep_register_handler("avatar", AVATARNAMESPACEMETA, jabber_buddy_avatar_update_metadata);
 }
 
 
--- a/libpurple/protocols/jabber/message.c	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/protocols/jabber/message.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"
@@ -30,6 +30,7 @@
 #include "google.h"
 #include "message.h"
 #include "xmlnode.h"
+#include "pep.h"
 
 void jabber_message_free(JabberMessage *jm)
 {
@@ -61,7 +62,7 @@
 
 	if(jabber_find_unnormalized_conv(jm->from, jm->js->gc->account)) {
 		from = g_strdup(jm->from);
-	} else  if(jid->node) {
+	} else	if(jid->node) {
 		if(jid->resource) {
 			PurpleConversation *conv;
 
@@ -99,13 +100,13 @@
 					escaped = g_markup_escape_text(who, -1);
 
 					g_snprintf(buf, sizeof(buf),
-					           _("%s has left the conversation."), escaped);
+							   _("%s has left the conversation."), escaped);
 
 					/* At some point when we restructure PurpleConversation,
 					 * this should be able to be implemented by removing the
 					 * user from the conversation like we do with chats now. */
 					purple_conversation_write(conv, "", buf,
-					                        PURPLE_MESSAGE_SYSTEM, time(NULL));
+											PURPLE_MESSAGE_SYSTEM, time(NULL));
 				}
 			}
 			serv_got_typing_stopped(jm->js->gc, from);
@@ -147,9 +148,13 @@
 static void handle_headline(JabberMessage *jm)
 {
 	char *title;
-	GString *body = g_string_new("");
+	GString *body;
 	GList *etc;
+	
+	if(!jm->xhtml && !jm->body)
+		return; /* ignore headlines without any content */
 
+	body = g_string_new("");
 	title = g_strdup_printf(_("Message from %s"), jm->from);
 
 	if(jm->xhtml)
@@ -273,6 +278,36 @@
 	g_free(buf);
 }
 
+static void handle_buzz(JabberMessage *jm) {
+	PurpleBuddy *buddy;
+	PurpleAccount *account;
+	PurpleConversation *c;
+	char *username, *str;
+	
+	/* Delayed buzz MUST NOT be accepted */
+	if(jm->delayed)
+		return;
+	
+	/* Reject buzz when it's not enabled */
+	if(!jm->js->allowBuzz)
+		return;
+	
+	account = purple_connection_get_account(jm->js->gc);
+	
+	if ((buddy = purple_find_buddy(account, jm->from)) != NULL)
+		username = g_markup_escape_text(purple_buddy_get_alias(buddy), -1);
+	else
+		return; /* Do not accept buzzes from unknown people */
+
+	c = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, jm->from);
+
+	str = g_strdup_printf(_("%s just sent you a Buzz!"), username);
+	
+	purple_conversation_write(c, NULL, str, PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NOTIFY, time(NULL));
+	g_free(username);
+	g_free(str);
+}
+
 void jabber_message_parse(JabberStream *js, xmlnode *packet)
 {
 	JabberMessage *jm;
@@ -308,48 +343,58 @@
 	jm->id = g_strdup(xmlnode_get_attrib(packet, "id"));
 
 	for(child = packet->child; child; child = child->next) {
+		const char *xmlns = xmlnode_get_namespace(child);
+		if(!xmlns)
+			xmlns = "";
 		if(child->type != XMLNODE_TYPE_TAG)
 			continue;
 
-		if(!strcmp(child->name, "subject")) {
+		if(!strcmp(child->name, "subject") && !strcmp(xmlns,"jabber:client")) {
 			if(!jm->subject)
 				jm->subject = xmlnode_get_data(child);
-		} else if(!strcmp(child->name, "thread")) {
+		} else if(!strcmp(child->name, "thread") && !strcmp(xmlns,"jabber:client")) {
 			if(!jm->thread_id)
 				jm->thread_id = xmlnode_get_data(child);
-		} else if(!strcmp(child->name, "body")) {
+		} else if(!strcmp(child->name, "body") && !strcmp(xmlns,"jabber:client")) {
 			if(!jm->body) {
 				char *msg = xmlnode_to_str(child, NULL);
 				jm->body = purple_strdup_withhtml(msg);
 				g_free(msg);
 			}
-		} else if(!strcmp(child->name, "html")) {
+		} else if(!strcmp(child->name, "html") && !strcmp(xmlns,"http://jabber.org/protocol/xhtml-im")) {
 			if(!jm->xhtml && xmlnode_get_child(child, "body")) {
 				char *c;
 				jm->xhtml = xmlnode_to_str(child, NULL);
-			        /* Convert all newlines to whitespace. Technically, even regular, non-XML HTML is supposed to ignore newlines, but Pidgin has, as convention
-			 	 * treated \n as a newline for compatibility with other protocols
+					/* Convert all newlines to whitespace. Technically, even regular, non-XML HTML is supposed to ignore newlines, but Pidgin has, as convention
+				 * treated \n as a newline for compatibility with other protocols
 				 */
 				for (c = jm->xhtml; *c != '\0'; c++) {
 					if (*c == '\n') 
 						*c = ' ';
 				}
 			}
-		} else if(!strcmp(child->name, "active")) {
+		} else if(!strcmp(child->name, "active") && !strcmp(xmlns,"http://jabber.org/protocol/chatstates")) {
 			jm->chat_state = JM_STATE_ACTIVE;
 			jm->typing_style |= JM_TS_JEP_0085;
-		} else if(!strcmp(child->name, "composing")) {
+		} else if(!strcmp(child->name, "composing") && !strcmp(xmlns,"http://jabber.org/protocol/chatstates")) {
 			jm->chat_state = JM_STATE_COMPOSING;
 			jm->typing_style |= JM_TS_JEP_0085;
-		} else if(!strcmp(child->name, "paused")) {
+		} else if(!strcmp(child->name, "paused") && !strcmp(xmlns,"http://jabber.org/protocol/chatstates")) {
 			jm->chat_state = JM_STATE_PAUSED;
 			jm->typing_style |= JM_TS_JEP_0085;
-		} else if(!strcmp(child->name, "inactive")) {
+		} else if(!strcmp(child->name, "inactive") && !strcmp(xmlns,"http://jabber.org/protocol/chatstates")) {
 			jm->chat_state = JM_STATE_INACTIVE;
 			jm->typing_style |= JM_TS_JEP_0085;
-		} else if(!strcmp(child->name, "gone")) {
+		} else if(!strcmp(child->name, "gone") && !strcmp(xmlns,"http://jabber.org/protocol/chatstates")) {
 			jm->chat_state = JM_STATE_GONE;
 			jm->typing_style |= JM_TS_JEP_0085;
+		} else if(!strcmp(child->name, "event") && !strcmp(xmlns,"http://jabber.org/protocol/pubsub#event")) {
+			xmlnode *items;
+			jm->type = JABBER_MESSAGE_EVENT;
+			for(items = xmlnode_get_child(child,"items"); items; items = items->next)
+				jm->eventitems = g_list_append(jm->eventitems, items);
+		} else if(!strcmp(child->name, "attention") && !strcmp(xmlns,"http://www.xmpp.org/extensions/xep-0224.html#ns")) {
+			jm->hasBuzz = TRUE;
 		} else if(!strcmp(child->name, "error")) {
 			const char *code = xmlnode_get_attrib(child, "code");
 			char *code_txt = NULL;
@@ -364,8 +409,12 @@
 
 			g_free(code_txt);
 			g_free(text);
+		} else if(!strcmp(child->name, "delay") && xmlns && !strcmp(xmlns,"urn:xmpp:delay")) {
+			const char *timestamp = xmlnode_get_attrib(child, "stamp");
+			jm->delayed = TRUE;
+			if(timestamp)
+				jm->sent = purple_str_to_time(timestamp, TRUE, NULL, NULL, NULL);
 		} else if(!strcmp(child->name, "x")) {
-			const char *xmlns = xmlnode_get_namespace(child);
 			if(xmlns && !strcmp(xmlns, "jabber:x:event")) {
 				if(xmlnode_get_child(child, "composing")) {
 					if(jm->chat_state == JM_STATE_ACTIVE)
@@ -410,6 +459,9 @@
 			}
 		}
 	}
+	
+	if(jm->hasBuzz)
+		handle_buzz(jm);
 
 	switch(jm->type) {
 		case JABBER_MESSAGE_NORMAL:
@@ -425,6 +477,9 @@
 		case JABBER_MESSAGE_GROUPCHAT_INVITE:
 			handle_groupchat_invite(jm);
 			break;
+		case JABBER_MESSAGE_EVENT:
+			jabber_handle_event(jm);
+			break;
 		case JABBER_MESSAGE_ERROR:
 			handle_error(jm);
 			break;
@@ -461,6 +516,7 @@
 			type = "error";
 			break;
 		case JABBER_MESSAGE_OTHER:
+		default:
 			type = NULL;
 			break;
 	}
@@ -689,3 +745,8 @@
 	jabber_message_send(jm);
 	jabber_message_free(jm);
 }
+
+gboolean jabber_buzz_isenabled(JabberStream *js, const gchar *shortname, const gchar *namespace) {
+	return js->allowBuzz;
+}
+
--- a/libpurple/protocols/jabber/message.h	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/protocols/jabber/message.h	Wed Sep 05 22:32:14 2007 +0000
@@ -12,12 +12,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
  */
 #ifndef _PURPLE_JABBER_MESSAGE_H_
 #define _PURPLE_JABBER_MESSAGE_H_
@@ -35,10 +35,12 @@
 		JABBER_MESSAGE_HEADLINE,
 		JABBER_MESSAGE_ERROR,
 		JABBER_MESSAGE_GROUPCHAT_INVITE,
+		JABBER_MESSAGE_EVENT,
 		JABBER_MESSAGE_OTHER
 	} type;
 	time_t sent;
 	gboolean delayed;
+	gboolean hasBuzz;
 	char *id;
 	char *from;
 	char *to;
@@ -61,6 +63,7 @@
 		JM_STATE_GONE
 	} chat_state;
 	GList *etc;
+	GList *eventitems;
 } JabberMessage;
 
 void jabber_message_free(JabberMessage *jm);
@@ -75,4 +78,6 @@
 unsigned int jabber_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state);
 void jabber_message_conv_closed(JabberStream *js, const char *who);
 
+gboolean jabber_buzz_isenabled(JabberStream *js, const gchar *shortname, const gchar *namespace);
+
 #endif /* _PURPLE_JABBER_MESSAGE_H_ */
--- a/libpurple/protocols/jabber/parser.c	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/protocols/jabber/parser.c	Wed Sep 05 22:32:14 2007 +0000
@@ -63,7 +63,7 @@
 		if(js->protocol_version == JABBER_PROTO_0_9)
 			js->auth_type = JABBER_AUTH_IQ_AUTH;
 
-		if(js->state == JABBER_STREAM_INITIALIZING)
+		if(js->state == JABBER_STREAM_INITIALIZING || js->state == JABBER_STREAM_INITIALIZING_ENCRYPTION)
 			jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING);
 	} else {
 
@@ -113,7 +113,7 @@
 	} else {
 		xmlnode *packet = js->current;
 		js->current = NULL;
-		jabber_process_packet(js, packet);
+		jabber_process_packet(js, &packet);
 		xmlnode_free(packet);
 	}
 }
@@ -174,6 +174,10 @@
 	 * the parser context when you try to use it (this way, it can figure
 	 * out the encoding at creation time. So, setting up the parser is
 	 * just a matter of destroying any current parser. */
+	jabber_parser_free(js);
+}
+
+void jabber_parser_free(JabberStream *js) {
 	if (js->context) {
 		xmlParseChunk(js->context, NULL,0,1);
 		xmlFreeParserCtxt(js->context);
@@ -181,7 +185,6 @@
 	}
 }
 
-
 void jabber_parser_process(JabberStream *js, const char *buf, int len)
 {
 	if (js->context ==  NULL) {
--- a/libpurple/protocols/jabber/parser.h	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/protocols/jabber/parser.h	Wed Sep 05 22:32:14 2007 +0000
@@ -25,6 +25,7 @@
 #include "jabber.h"
 
 void jabber_parser_setup(JabberStream *js);
+void jabber_parser_free(JabberStream *js);
 void jabber_parser_process(JabberStream *js, const char *buf, int len);
 
 #endif /* _PURPLE_JABBER_PARSER_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/pep.c	Wed Sep 05 22:32:14 2007 +0000
@@ -0,0 +1,127 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#include "pep.h"
+#include "iq.h"
+#include <string.h>
+#include "usermood.h"
+#include "usernick.h"
+
+static GHashTable *pep_handlers = NULL;
+
+void jabber_pep_init(void) {
+	if(!pep_handlers) {
+		pep_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+		
+		/* register PEP handlers */
+		jabber_mood_init();
+		jabber_nick_init();
+	}
+}
+
+void jabber_pep_init_actions(GList **m) {
+	/* register the PEP-specific actions */
+	jabber_mood_init_action(m);
+	jabber_nick_init_action(m);
+}
+
+void jabber_pep_register_handler(const char *shortname, const char *xmlns, JabberPEPHandler handlerfunc) {
+	gchar *notifyns = g_strdup_printf("%s+notify", xmlns);
+	jabber_add_feature(shortname, notifyns, NULL); /* receiving PEPs is always supported */
+	g_free(notifyns);
+	g_hash_table_replace(pep_handlers, g_strdup(xmlns), handlerfunc);
+}
+
+static void do_pep_iq_request_item_callback(JabberStream *js, xmlnode *packet, gpointer data) {
+	const char *from = xmlnode_get_attrib(packet,"from");
+	xmlnode *pubsub = xmlnode_get_child_with_namespace(packet,"pubsub","http://jabber.org/protocol/pubsub#event");
+	xmlnode *items = NULL;
+	JabberPEPHandler *cb = data;
+	
+	if(pubsub)
+		items = xmlnode_get_child(pubsub, "items");
+	
+	cb(js, from, items);
+}
+
+void jabber_pep_request_item(JabberStream *js, const char *to, const char *node, const char *id, JabberPEPHandler cb) {
+	JabberIq *iq = jabber_iq_new(js, JABBER_IQ_GET);
+	xmlnode *pubsub, *items, *item;
+	
+	xmlnode_set_attrib(iq->node,"to",to);
+	pubsub = xmlnode_new_child(iq->node,"pubsub");
+	
+	xmlnode_set_namespace(pubsub,"http://jabber.org/protocol/pubsub");
+	
+	items = xmlnode_new_child(pubsub, "items");
+	xmlnode_set_attrib(items,"node",node);
+	
+	item = xmlnode_new_child(items, "item");
+	if(id)
+		xmlnode_set_attrib(item, "id", id);
+	
+	jabber_iq_set_callback(iq,do_pep_iq_request_item_callback,(gpointer)cb);
+	
+	jabber_iq_send(iq);
+}
+
+gboolean jabber_pep_namespace_only_when_pep_enabled_cb(JabberStream *js, const gchar *shortname, const gchar *namespace) {
+	return js->pep;
+}
+
+void jabber_handle_event(JabberMessage *jm) {
+	/* this may be called even when the own server doesn't support pep! */
+	JabberPEPHandler *jph;
+	GList *itemslist;
+	char *jid = jabber_get_bare_jid(jm->from);
+	
+	for(itemslist = jm->eventitems; itemslist; itemslist = itemslist->next) {
+		xmlnode *items = (xmlnode*)itemslist->data;
+		const char *nodename = xmlnode_get_attrib(items,"node");
+		
+		if(nodename && (jph = g_hash_table_lookup(pep_handlers, nodename)))
+			jph(jm->js, jid, items);
+	}
+	
+	/* discard items we don't have a handler for */
+	g_free(jid);
+}
+
+void jabber_pep_publish(JabberStream *js, xmlnode *publish) {
+	JabberIq *iq;
+	
+	if(js->pep != TRUE) {
+		/* ignore when there's no PEP support on the server */
+		xmlnode_free(publish);
+		return;
+	}
+	
+	iq = jabber_iq_new(js, JABBER_IQ_SET);
+	
+	xmlnode *pubsub = xmlnode_new("pubsub");
+	xmlnode_set_namespace(pubsub, "http://jabber.org/protocol/pubsub");
+	
+	xmlnode_insert_child(pubsub, publish);
+	
+	xmlnode_insert_child(iq->node, pubsub);
+	
+	jabber_iq_send(iq);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/pep.h	Wed Sep 05 22:32:14 2007 +0000
@@ -0,0 +1,85 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#ifndef _PURPLE_JABBER_PEP_H_
+#define _PURPLE_JABBER_PEP_H_
+
+#include "jabber.h"
+#include "message.h"
+#include "buddy.h"
+
+void jabber_pep_init(void);
+
+void jabber_pep_init_actions(GList **m);
+
+/*
+ * Callback for receiving PEP events.
+ *
+ * @parameter js	The JabberStream this item was received on
+ * @parameter items	The &lt;items/>-tag with the &lt;item/>-children
+ */
+typedef void (JabberPEPHandler)(JabberStream *js, const char *from, xmlnode *items);
+
+/*
+ * Registers a callback for PEP events. Also automatically announces this receiving capability via disco#info.
+ * Don't forget to use jabber_add_feature when supporting the sending of PEP events of this type.
+ *
+ * @parameter shortname		A short name for this feature for XEP-0115. It has no semantic meaning, it just has to be unique.
+ * @parameter xmlns		The namespace for this event
+ * @parameter handlerfunc	The callback to be used when receiving an event with this namespace
+ */
+void jabber_pep_register_handler(const char *shortname, const char *xmlns, JabberPEPHandler handlerfunc);
+
+/*
+ * Request a specific item from another PEP node.
+ *
+ * @parameter js	The JabberStream that should be used
+ * @parameter to	The target PEP node
+ * @parameter node	The node name of the item that is requested
+ * @parameter id	The item id of the requested item (may be NULL)
+ * @parameter cb	The callback to be used when this item is received
+ *
+ * The items element passed to the callback will be NULL if any error occured (like a permission error, node doesn't exist etc.)
+ */
+void jabber_pep_request_item(JabberStream *js, const char *to, const char *node, const char *id, JabberPEPHandler cb);
+
+/*
+ * Default callback that can be used for namespaces which should only be enabled when PEP is supported
+ *
+ * @parameter js	The JabberStream struct for this connection
+ * @parameter shortname	The namespace's shortname (for caps), ignored.
+ * @parameter namespace The namespace that's queried, ignored.
+ *
+ * @returns TRUE when PEP is enabled, FALSE otherwise
+ */
+gboolean jabber_pep_namespace_only_when_pep_enabled_cb(JabberStream *js, const gchar *shortname, const gchar *namespace);
+
+void jabber_handle_event(JabberMessage *jm);
+
+/*
+ * Publishes PEP item(s)
+ *
+ * @parameter js	The JabberStream associated with the connection this event should be published
+ * @parameter publish	The publish node. This could be for example &lt;publish node='http://jabber.org/protocol/tune'/> with an &lt;item/> as subnode
+ */
+void jabber_pep_publish(JabberStream *js, xmlnode *publish);
+
+#endif /* _PURPLE_JABBER_PEP_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/ping.c	Wed Sep 05 22:32:14 2007 +0000
@@ -0,0 +1,80 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include "internal.h"
+
+#include "debug.h"
+#include "xmlnode.h"
+
+#include "jabber.h"
+#include "ping.h"
+#include "iq.h"
+
+void
+jabber_ping_parse(JabberStream *js, xmlnode *packet)
+{
+	JabberIq *iq;
+
+	purple_debug_info("jabber", "jabber_ping_parse\n");
+
+	iq = jabber_iq_new(js, JABBER_IQ_RESULT);
+
+	xmlnode_set_attrib(iq->node, "to", xmlnode_get_attrib(packet, "from") );
+
+	jabber_iq_set_id(iq, xmlnode_get_attrib(packet, "id"));
+
+	jabber_iq_send(iq);
+}
+
+static void jabber_ping_result_cb(JabberStream *js, xmlnode *packet,
+		gpointer data)
+{
+	const char *type = xmlnode_get_attrib(packet, "type");
+
+	purple_debug_info("jabber", "jabber_ping_result_cb\n");
+	if(type && !strcmp(type, "result")) {
+		purple_debug_info("jabber", "PONG!\n");
+	} else {
+		purple_debug_info("jabber", "(not supported)\n");
+	}
+}
+
+gboolean jabber_ping_jid(PurpleConversation *conv, const char *jid)
+{
+	JabberIq *iq;
+	xmlnode *ping;
+
+	purple_debug_info("jabber", "jabber_ping_jid\n");
+
+	iq = jabber_iq_new(conv->account->gc->proto_data, JABBER_IQ_GET);
+	xmlnode_set_attrib(iq->node, "to", jid);
+
+	ping = xmlnode_new_child(iq->node, "ping");
+	xmlnode_set_namespace(ping, "urn:xmpp:ping");
+
+	jabber_iq_set_callback(iq, jabber_ping_result_cb, NULL);
+	jabber_iq_send(iq);
+
+
+
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/ping.h	Wed Sep 05 22:32:14 2007 +0000
@@ -0,0 +1,35 @@
+/**
+ * @file ping.h utility functions
+ *
+ * purple
+ *
+ * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef _PURPLE_JABBER_PING_H_
+#define _PURPLE_JABBER_PING_H_
+
+#include "jabber.h"
+#include "conversation.h"
+
+void jabber_ping_parse(JabberStream *js,
+						xmlnode *packet);
+
+
+gboolean jabber_ping_jid(PurpleConversation *conv, const char *jid);
+
+
+#endif /* _PURPLE_JABBER_PING_H_ */
--- a/libpurple/protocols/jabber/presence.c	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/protocols/jabber/presence.c	Wed Sep 05 22:32:14 2007 +0000
@@ -36,6 +36,9 @@
 #include "presence.h"
 #include "iq.h"
 #include "jutil.h"
+#include "adhoccommands.h"
+
+#include "usertune.h"
 
 
 static void chats_send_presence_foreach(gpointer key, gpointer val,
@@ -101,6 +104,9 @@
 	char *stripped = NULL;
 	JabberBuddyState state;
 	int priority;
+	const char *artist, *title, *source, *uri, *track;
+	int length;
+	gboolean allowBuzz;
 
 	if(NULL == status) {
 		PurplePresence *gpresence = purple_account_get_presence(account);
@@ -127,28 +133,95 @@
 	}
 
 	purple_status_to_jabber(status, &state, &stripped, &priority);
-
+	
+	/* check for buzz support */
+	allowBuzz = purple_status_get_attr_boolean(status,"buzz");
+	/* changing the buzz state has to trigger a re-broadcasting of the presence for caps */
+	
+#define CHANGED(a,b) ((!a && b) || (a && a[0] == '\0' && b && b[0] != '\0') || \
+					  (a && !b) || (a && a[0] != '\0' && b && b[0] == '\0') || (a && b && strcmp(a,b)))
+	/* check if there are any differences to the <presence> and send them in that case */
+	if (allowBuzz != js->allowBuzz || js->old_state != state || CHANGED(js->old_msg, stripped) ||
+		js->old_priority != priority || CHANGED(js->old_avatarhash, js->avatar_hash)) {
+		js->allowBuzz = allowBuzz;
+		presence = jabber_presence_create_js(js, state, stripped, priority);
 
-	presence = jabber_presence_create(state, stripped, priority);
-	g_free(stripped);
+		if(js->avatar_hash) {
+			x = xmlnode_new_child(presence, "x");
+			xmlnode_set_namespace(x, "vcard-temp:x:update");
+			photo = xmlnode_new_child(x, "photo");
+			xmlnode_insert_data(photo, js->avatar_hash, -1);
+		}
+
+		jabber_send(js, presence);
 
-	if(js->avatar_hash) {
-		x = xmlnode_new_child(presence, "x");
-		xmlnode_set_namespace(x, "vcard-temp:x:update");
-		photo = xmlnode_new_child(x, "photo");
-		xmlnode_insert_data(photo, js->avatar_hash, -1);
+		g_hash_table_foreach(js->chats, chats_send_presence_foreach, presence);
+		xmlnode_free(presence);
+		
+		/* update old values */
+		
+		if(js->old_msg)
+			g_free(js->old_msg);
+		if(js->old_avatarhash)
+			g_free(js->old_avatarhash);
+		js->old_msg = g_strdup(stripped);
+		js->old_avatarhash = g_strdup(js->avatar_hash);
+		js->old_state = state;
+		js->old_priority = priority;
+		g_free(stripped);
 	}
-
-	jabber_send(js, presence);
-
-	g_hash_table_foreach(js->chats, chats_send_presence_foreach, presence);
-	xmlnode_free(presence);
-
+					  	
+	/* next, check if there are any changes to the tune values */
+	artist = purple_status_get_attr_string(status, PURPLE_TUNE_ARTIST);
+	title = purple_status_get_attr_string(status, PURPLE_TUNE_TITLE);
+	source = purple_status_get_attr_string(status, PURPLE_TUNE_ALBUM);
+	uri = purple_status_get_attr_string(status, PURPLE_TUNE_URL);
+	track = purple_status_get_attr_string(status, PURPLE_TUNE_TRACK);
+	length = (!purple_status_get_attr_value(status, PURPLE_TUNE_TIME))?-1:purple_status_get_attr_int(status, PURPLE_TUNE_TIME);
+	
+	if(CHANGED(artist, js->old_artist) || CHANGED(title, js->old_title) || CHANGED(source, js->old_source) ||
+	   CHANGED(uri, js->old_uri) || CHANGED(track, js->old_track) || (length != js->old_length)) {
+		PurpleJabberTuneInfo tuneinfo = {
+			(char*)artist,
+			(char*)title,
+			(char*)source,
+			(char*)track,
+			length,
+			(char*)uri
+		};
+		jabber_tune_set(js->gc, &tuneinfo);
+		
+		/* update old values */
+		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);
+		js->old_artist = g_strdup(artist);
+		js->old_title = g_strdup(title);
+		js->old_source = g_strdup(source);
+		js->old_uri = g_strdup(uri);
+		js->old_length = length;
+		js->old_track = g_strdup(track);
+	}
+	
+#undef CHANGED(a,b)
+		
 	jabber_presence_fake_to_self(js, status);
 }
 
 xmlnode *jabber_presence_create(JabberBuddyState state, const char *msg, int priority)
 {
+    return jabber_presence_create_js(NULL, state, msg, priority);
+}
+
+xmlnode *jabber_presence_create_js(JabberStream *js, JabberBuddyState state, const char *msg, int priority)
+{
 	xmlnode *show, *status, *presence, *pri, *c;
 	const char *show_string = NULL;
 
@@ -183,7 +256,39 @@
 	xmlnode_set_namespace(c, "http://jabber.org/protocol/caps");
 	xmlnode_set_attrib(c, "node", CAPS0115_NODE);
 	xmlnode_set_attrib(c, "ver", VERSION);
-
+	
+	if(js != NULL) {
+		/* add the extensions */
+		char extlist[1024];
+		unsigned remaining = 1023; /* one less for the \0 */
+		GList *feature;
+		
+		extlist[0] = '\0';
+		for(feature = jabber_features; feature && remaining > 0; feature = feature->next) {
+			JabberFeature *feat = (JabberFeature*)feature->data;
+			unsigned featlen;
+			
+			if(feat->is_enabled != NULL && feat->is_enabled(js, feat->shortname, feat->namespace) == FALSE)
+				continue; /* skip this feature */
+			
+			featlen = strlen(feat->shortname);
+			
+			/* cut off when we don't have any more space left in our buffer (too bad) */
+			if(featlen > remaining)
+				break;
+			
+			strncat(extlist,feat->shortname,remaining);
+			remaining -= featlen;
+			if(feature->next) { /* no space at the end */
+				strncat(extlist," ",remaining);
+				--remaining;
+			}
+		}
+		/* did we add anything? */
+		if(remaining < 1023)
+			xmlnode_set_attrib(c, "ext", extlist);
+	}
+	
 	return presence;
 }
 
@@ -252,6 +357,35 @@
 	}
 }
 
+typedef struct _JabberPresenceCapabilities {
+	JabberStream *js;
+	JabberBuddyResource *jbr;
+	char *from;
+} JabberPresenceCapabilities;
+
+static void jabber_presence_set_capabilities(JabberCapsClientInfo *info, gpointer user_data) {
+	JabberPresenceCapabilities *userdata = user_data;
+	GList *iter;
+	
+	if(userdata->jbr->caps)
+		jabber_caps_free_clientinfo(userdata->jbr->caps);
+	userdata->jbr->caps = 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;
+		}
+	}
+	g_free(user_data);
+}
+
 void jabber_presence_parse(JabberStream *js, xmlnode *packet)
 {
 	const char *from = xmlnode_get_attrib(packet, "from");
@@ -273,6 +407,7 @@
 	xmlnode *y;
 	gboolean muc = FALSE;
 	char *avatar_hash = NULL;
+	xmlnode *caps = NULL;
 
 	if(!(jb = jabber_buddy_find(js, from, TRUE)))
 		return;
@@ -335,8 +470,10 @@
 
 
 	for(y = packet->child; y; y = y->next) {
+		const char *xmlns;
 		if(y->type != XMLNODE_TYPE_TAG)
 			continue;
+		xmlns = xmlnode_get_namespace(y);
 
 		if(!strcmp(y->name, "status")) {
 			g_free(status);
@@ -347,6 +484,11 @@
 				priority = atoi(p);
 				g_free(p);
 			}
+		} else if(!strcmp(y->name, "delay") && !strcmp(xmlns, "urn:xmpp:delay")) {
+			/* XXX: compare the time.  jabber:x:delay can happen on presence packets that aren't really and truly delayed */
+			delayed = TRUE;
+		} else if(!strcmp(y->name, "c") && !strcmp(xmlns, "http://jabber.org/protocol/caps")) {
+			caps = y; /* store for later, when creating buddy resource */
 		} else if(!strcmp(y->name, "x")) {
 			const char *xmlns = xmlnode_get_namespace(y);
 			if(xmlns && !strcmp(xmlns, "jabber:x:delay")) {
@@ -524,18 +666,22 @@
 		g_free(room_jid);
 	} else {
 		buddy_name = g_strdup_printf("%s%s%s", jid->node ? jid->node : "",
-				jid->node ? "@" : "", jid->domain);
+									 jid->node ? "@" : "", jid->domain);
 		if((b = purple_find_buddy(js->gc->account, buddy_name)) == NULL) {
-			purple_debug_warning("jabber", "Got presence for unknown buddy %s on account %s (%x)\n",
-				buddy_name, purple_account_get_username(js->gc->account), js->gc->account);
-			jabber_id_free(jid);
-			g_free(avatar_hash);
-			g_free(buddy_name);
-			g_free(status);
-			return;
+			if(!jid->node || strcmp(jid->node,js->user->node) || strcmp(jid->domain,js->user->domain)) {
+				purple_debug_warning("jabber", "Got presence for unknown buddy %s on account %s (%x)\n",
+									 buddy_name, purple_account_get_username(js->gc->account), js->gc->account);
+				jabber_id_free(jid);
+				g_free(avatar_hash);
+				g_free(buddy_name);
+				g_free(status);
+				return;
+			} else {
+				/* this is a different resource of our own account. Resume even when this account isn't on our blist */
+			}
 		}
 
-		if(avatar_hash) {
+		if(b && avatar_hash) {
 			const char *avatar_hash2 = purple_buddy_icons_get_checksum_for_user(b);
 			if(!avatar_hash2 || strcmp(avatar_hash, avatar_hash2)) {
 				JabberIq *iq;
@@ -573,6 +719,19 @@
 		} else {
 			jbr = jabber_buddy_track_resource(jb, jid->resource, priority,
 					state, status);
+			if(caps) {
+				const char *node = xmlnode_get_attrib(caps,"node");
+				const char *ver = xmlnode_get_attrib(caps,"ver");
+				const char *ext = xmlnode_get_attrib(caps,"ext");
+				
+				if(node && ver) {
+					JabberPresenceCapabilities *userdata = g_new0(JabberPresenceCapabilities, 1);
+					userdata->js = js;
+					userdata->jbr = jbr;
+					userdata->from = g_strdup(from);
+					jabber_caps_get_info(js, from, node, ver, ext, jabber_presence_set_capabilities, userdata);
+				}
+			}
 		}
 
 		if((found_jbr = jabber_buddy_find_resource(jb, NULL))) {
--- a/libpurple/protocols/jabber/presence.h	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/protocols/jabber/presence.h	Wed Sep 05 22:32:14 2007 +0000
@@ -27,7 +27,8 @@
 #include "xmlnode.h"
 
 void jabber_presence_send(PurpleAccount *account, PurpleStatus *status);
-xmlnode *jabber_presence_create(JabberBuddyState state, const char *msg, int priority);
+xmlnode *jabber_presence_create(JabberBuddyState state, const char *msg, int priority); /* DEPRECATED */
+xmlnode *jabber_presence_create_js(JabberStream *js, JabberBuddyState state, const char *msg, int priority);
 void jabber_presence_parse(JabberStream *js, xmlnode *packet);
 void jabber_presence_subscription_set(JabberStream *js, const char *who,
 		const char *type);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/usermood.c	Wed Sep 05 22:32:14 2007 +0000
@@ -0,0 +1,214 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#include "usermood.h"
+#include "pep.h"
+#include <assert.h>
+#include <string.h>
+#include "internal.h"
+#include "request.h"
+
+static const char *moodstrings[] = {
+	"afraid",
+	"amazed",
+	"angry",
+	"annoyed",
+	"anxious",
+	"aroused",
+	"ashamed",
+	"bored",
+	"brave",
+	"calm",
+	"cold",
+	"confused",
+	"contented",
+	"cranky",
+	"curious",
+	"depressed",
+	"disappointed",
+	"disgusted",
+	"distracted",
+	"embarrassed",
+	"excited",
+	"flirtatious",
+	"frustrated",
+	"grumpy",
+	"guilty",
+	"happy",
+	"hot",
+	"humbled",
+	"humiliated",
+	"hungry",
+	"hurt",
+	"impressed",
+	"in_awe",
+	"in_love",
+	"indignant",
+	"interested",
+	"intoxicated",
+	"invincible",
+	"jealous",
+	"lonely",
+	"mean",
+	"moody",
+	"nervous",
+	"neutral",
+	"offended",
+	"playful",
+	"proud",
+	"relieved",
+	"remorseful",
+	"restless",
+	"sad",
+	"sarcastic",
+	"serious",
+	"shocked",
+	"shy",
+	"sick",
+	"sleepy",
+	"stressed",
+	"surprised",
+	"thirsty",
+	"worried",
+	NULL
+};
+
+static void jabber_mood_cb(JabberStream *js, const char *from, xmlnode *items) {
+	/* it doesn't make sense to have more than one item here, so let's just pick the first one */
+	xmlnode *item = xmlnode_get_child(items, "item");
+	const char *newmood = NULL;
+	char *moodtext = NULL;
+	JabberBuddy *buddy = jabber_buddy_find(js, from, FALSE);
+	xmlnode *moodinfo, *mood;
+	/* ignore the mood of people not on our buddy list */
+	if (!buddy || !item)
+		return;
+	
+	mood = xmlnode_get_child_with_namespace(item, "mood", "http://jabber.org/protocol/mood");
+	if (!mood)
+		return;
+	for (moodinfo = mood->child; moodinfo; moodinfo = moodinfo->next) {
+		if (moodinfo->type == XMLNODE_TYPE_TAG) {
+			if (!strcmp(moodinfo->name, "text")) {
+				if (!moodtext) /* only pick the first one */
+					moodtext = xmlnode_get_data(moodinfo);
+			} else {
+				int i;
+				for (i = 0; moodstrings[i]; ++i) {
+					/* verify that the mood is known (valid) */
+					if (!strcmp(moodinfo->name, moodstrings[i])) {
+						newmood = moodstrings[i];
+						break;
+					}
+				}
+			}
+			if (newmood != NULL && moodtext != NULL)
+			   break;
+		}
+	}
+	if (newmood != NULL) {
+		JabberBuddyResource *resource = jabber_buddy_find_resource(buddy, NULL);
+		if(!resource) { /* huh? */
+			if (moodtext)
+				g_free(moodtext);
+			return;
+		}
+		const char *status_id = jabber_buddy_state_get_status_id(resource->state);
+		
+		purple_prpl_got_user_status(js->gc->account, from, status_id, "mood", _(newmood), "moodtext", moodtext?moodtext:"", NULL);
+	}
+	if (moodtext)
+		g_free(moodtext);
+}
+
+void jabber_mood_init(void) {
+	jabber_add_feature("mood", "http://jabber.org/protocol/mood", jabber_pep_namespace_only_when_pep_enabled_cb);
+	jabber_pep_register_handler("moodn", "http://jabber.org/protocol/mood", jabber_mood_cb);
+}
+
+static void do_mood_set_from_fields(PurpleConnection *gc, PurpleRequestFields *fields) {
+	JabberStream *js = gc->proto_data;
+	
+	jabber_mood_set(js, moodstrings[purple_request_fields_get_choice(fields, "mood")], purple_request_fields_get_string(fields, "text"));
+}
+
+static void do_mood_set_mood(PurplePluginAction *action) {
+	PurpleConnection *gc = (PurpleConnection *) action->context;
+
+	PurpleRequestFields *fields;
+	PurpleRequestFieldGroup *group;
+	PurpleRequestField *field;
+	int i;
+
+	fields = purple_request_fields_new();
+	group = purple_request_field_group_new(NULL);
+	purple_request_fields_add_group(fields, group);
+
+	field = purple_request_field_choice_new("mood",
+											_("Mood"), 0);
+	
+	for(i = 0; moodstrings[i]; ++i)
+		purple_request_field_choice_add(field, _(moodstrings[i]));
+	
+	purple_request_field_set_required(field, TRUE);
+	purple_request_field_group_add_field(group, field);
+
+	field = purple_request_field_string_new("text",
+											_("Description"), NULL,
+											FALSE);
+	purple_request_field_group_add_field(group, field);
+	
+	purple_request_fields(gc, _("Edit User Mood"),
+						  _("Edit User Mood"),
+						  _("Please select your mood from the list."),
+						  fields,
+						  _("Set"), G_CALLBACK(do_mood_set_from_fields),
+						  _("Cancel"), NULL,
+						  purple_connection_get_account(gc), NULL, NULL,
+						  gc);
+	
+}
+
+void jabber_mood_init_action(GList **m) {
+	PurplePluginAction *act = purple_plugin_action_new(_("Set Mood..."), do_mood_set_mood);
+	*m = g_list_append(*m, act);
+}
+
+void jabber_mood_set(JabberStream *js, const char *mood, const char *text) {
+	xmlnode *publish, *moodnode;
+	
+	assert(mood != NULL);
+	
+	publish = xmlnode_new("publish");
+	xmlnode_set_attrib(publish,"node","http://jabber.org/protocol/mood");
+	moodnode = xmlnode_new_child(xmlnode_new_child(publish, "item"), "mood");
+	xmlnode_set_namespace(moodnode, "http://jabber.org/protocol/mood");
+	xmlnode_new_child(moodnode, mood);
+
+	if (text && text[0] != '\0') {
+		xmlnode *textnode = xmlnode_new_child(moodnode, "text");
+		xmlnode_insert_data(textnode, text, -1);
+	}
+	
+	jabber_pep_publish(js, publish);
+	/* publish is freed by jabber_pep_publish -> jabber_iq_send -> jabber_iq_free
+	   (yay for well-defined memory management rules) */
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/usermood.h	Wed Sep 05 22:32:14 2007 +0000
@@ -0,0 +1,37 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#ifndef _PURPLE_JABBER_USERMOOD_H_
+#define _PURPLE_JABBER_USERMOOD_H_
+
+#include "jabber.h"
+
+/* Implementation of XEP-0107 */
+
+void jabber_mood_init(void);
+
+void jabber_mood_init_action(GList **m);
+
+void jabber_mood_set(JabberStream *js,
+		     const char *mood, /* must be one of the valid strings defined in the XEP */
+		     const char *text /* might be NULL */);
+
+#endif /* _PURPLE_JABBER_USERMOOD_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/usernick.c	Wed Sep 05 22:32:14 2007 +0000
@@ -0,0 +1,100 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#include "usernick.h"
+#include "pep.h"
+#include <assert.h>
+#include <string.h>
+#include "internal.h"
+#include "request.h"
+#include "status.h"
+
+static void jabber_nick_cb(JabberStream *js, const char *from, xmlnode *items) {
+	/* it doesn't make sense to have more than one item here, so let's just pick the first one */
+	xmlnode *item = xmlnode_get_child(items, "item");
+	JabberBuddy *buddy = jabber_buddy_find(js, from, FALSE);
+	xmlnode *nick;
+	const char *nickname = NULL;
+	
+	/* ignore the tune of people not on our buddy list */
+	if (!buddy || !item)
+		return;
+	
+	nick = xmlnode_get_child_with_namespace(item, "nick", "http://jabber.org/protocol/nick");
+	if (!nick)
+		return;
+	nickname = xmlnode_get_data(nick);
+
+	serv_got_alias(js->gc, from, nickname);
+}
+
+static void do_nick_set(JabberStream *js, const char *nick) {
+	xmlnode *publish, *nicknode;
+	
+	publish = xmlnode_new("publish");
+	xmlnode_set_attrib(publish,"node","http://jabber.org/protocol/nick");
+	nicknode = xmlnode_new_child(xmlnode_new_child(publish, "item"), "nick");
+	xmlnode_set_namespace(nicknode, "http://jabber.org/protocol/nick");
+	
+	if(nick && nick[0] != '\0')
+		xmlnode_insert_data(nicknode, nick, -1);
+	
+	jabber_pep_publish(js, publish);
+	/* publish is freed by jabber_pep_publish -> jabber_iq_send -> jabber_iq_free
+		(yay for well-defined memory management rules) */
+}
+
+static void do_nick_got_own_nick_cb(JabberStream *js, const char *from, xmlnode *items) {
+	const char *oldnickname = NULL;
+	xmlnode *item = xmlnode_get_child(items,"item");
+	
+	if(item) {
+		xmlnode *nick = xmlnode_get_child_with_namespace(item,"nick","http://jabber.org/protocol/nick");
+		if(nick)
+			oldnickname = xmlnode_get_data(nick);
+	}
+	
+	purple_request_input(js->gc, _("Set User Nickname"), _("Please specify a new nickname for you."),
+		_("This information is visible to all contacts on your contact list, so choose something appropriate."),
+		oldnickname, FALSE, FALSE, NULL, _("Set"), PURPLE_CALLBACK(do_nick_set), _("Cancel"), NULL,
+		purple_connection_get_account(js->gc), NULL, NULL, js);
+}
+
+static void do_nick_set_nick(PurplePluginAction *action) {
+	PurpleConnection *gc = (PurpleConnection *) action->context;
+	JabberStream *js = gc->proto_data;
+	char *jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain);
+	
+	/* since the nickname might have been changed by another resource of this account, we always have to request the old one
+		from the server to present as the default for the new one */
+	jabber_pep_request_item(js, jid, "http://jabber.org/protocol/nick", NULL, do_nick_got_own_nick_cb);
+	g_free(jid);
+}
+
+void jabber_nick_init(void) {
+	jabber_add_feature("nick", "http://jabber.org/protocol/nick", jabber_pep_namespace_only_when_pep_enabled_cb);
+	jabber_pep_register_handler("nickn", "http://jabber.org/protocol/nick", jabber_nick_cb);
+}
+
+void jabber_nick_init_action(GList **m) {
+	PurplePluginAction *act = purple_plugin_action_new(_("Set Nickname..."), do_nick_set_nick);
+	*m = g_list_append(*m, act);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/usernick.h	Wed Sep 05 22:32:14 2007 +0000
@@ -0,0 +1,32 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#ifndef _PURPLE_JABBER_USERNICK_H_
+#define _PURPLE_JABBER_USERNICK_H_
+
+#include "jabber.h"
+
+/* Implementation of XEP-0172 */
+
+void jabber_nick_init(void);
+void jabber_nick_init_action(GList **m);
+
+#endif /* _PURPLE_JABBER_USERNICK_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/usertune.c	Wed Sep 05 22:32:14 2007 +0000
@@ -0,0 +1,122 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#include "usertune.h"
+#include "pep.h"
+#include <assert.h>
+#include <string.h>
+#include "internal.h"
+#include "request.h"
+#include "status.h"
+
+static void jabber_tune_cb(JabberStream *js, const char *from, xmlnode *items) {
+	/* it doesn't make sense to have more than one item here, so let's just pick the first one */
+	xmlnode *item = xmlnode_get_child(items, "item");
+	JabberBuddy *buddy = jabber_buddy_find(js, from, FALSE);
+	xmlnode *tuneinfo, *tune;
+	PurpleJabberTuneInfo tuneinfodata;
+	JabberBuddyResource *resource;
+	const char *status_id;
+	
+	/* ignore the tune of people not on our buddy list */
+	if (!buddy || !item)
+		return;
+	
+	tuneinfodata.artist = "";
+	tuneinfodata.title = "";
+	tuneinfodata.album = "";
+	tuneinfodata.track = "";
+	tuneinfodata.time = -1;
+	tuneinfodata.url = "";
+	
+	tune = xmlnode_get_child_with_namespace(item, "tune", "http://jabber.org/protocol/tune");
+	if (!tune)
+		return;
+	for (tuneinfo = tune->child; tuneinfo; tuneinfo = tuneinfo->next) {
+		if (tuneinfo->type == XMLNODE_TYPE_TAG) {
+			if (!strcmp(tuneinfo->name, "artist")) {
+				if (tuneinfodata.artist[0] == '\0') /* only pick the first one */
+					tuneinfodata.artist = xmlnode_get_data(tuneinfo);
+			} else if (!strcmp(tuneinfo->name, "length")) {
+				if (tuneinfodata.time == -1) {
+					char *length = xmlnode_get_data(tuneinfo);
+					if (length)
+						tuneinfodata.time = strtol(length, NULL, 10);
+				}
+			} else if (!strcmp(tuneinfo->name, "source")) {
+				if (tuneinfodata.album[0] == '\0') /* only pick the first one */
+					tuneinfodata.album = xmlnode_get_data(tuneinfo);
+			} else if (!strcmp(tuneinfo->name, "title")) {
+				if (tuneinfodata.title[0] == '\0') /* only pick the first one */
+					tuneinfodata.title = xmlnode_get_data(tuneinfo);
+			} else if (!strcmp(tuneinfo->name, "track")) {
+				if (tuneinfodata.track[0] == '\0') /* only pick the first one */
+					tuneinfodata.track = xmlnode_get_data(tuneinfo);
+			} else if (!strcmp(tuneinfo->name, "uri")) {
+				if (tuneinfodata.url[0] == '\0') /* only pick the first one */
+					tuneinfodata.url = xmlnode_get_data(tuneinfo);
+			}
+		}
+	}
+	resource = jabber_buddy_find_resource(buddy, NULL);
+	if(!resource)
+		return; /* huh? */
+	status_id = jabber_buddy_state_get_status_id(resource->state);
+
+	purple_prpl_got_user_status(js->gc->account, from, status_id, PURPLE_TUNE_ARTIST, tuneinfodata.artist, PURPLE_TUNE_TITLE, tuneinfodata.title, PURPLE_TUNE_ALBUM, tuneinfodata.album, PURPLE_TUNE_TRACK, tuneinfodata.track, PURPLE_TUNE_TIME, tuneinfodata.time, PURPLE_TUNE_URL, tuneinfodata.url, NULL);
+}
+
+void jabber_tune_init(void) {
+	jabber_add_feature("tune", "http://jabber.org/protocol/tune", jabber_pep_namespace_only_when_pep_enabled_cb);
+	jabber_pep_register_handler("tunen", "http://jabber.org/protocol/tune", jabber_tune_cb);
+}
+
+void jabber_tune_set(PurpleConnection *gc, const PurpleJabberTuneInfo *tuneinfo) {
+	xmlnode *publish, *tunenode;
+	JabberStream *js = gc->proto_data;
+	
+	publish = xmlnode_new("publish");
+	xmlnode_set_attrib(publish,"node","http://jabber.org/protocol/tune");
+	tunenode = xmlnode_new_child(xmlnode_new_child(publish, "item"), "tune");
+	xmlnode_set_namespace(tunenode, "http://jabber.org/protocol/tune");
+	
+	if(tuneinfo) {
+		if(tuneinfo->artist && tuneinfo->artist[0] != '\0')
+			xmlnode_insert_data(xmlnode_new_child(tunenode, "artist"),tuneinfo->artist,-1);
+		if(tuneinfo->title && tuneinfo->title[0] != '\0')
+			xmlnode_insert_data(xmlnode_new_child(tunenode, "title"),tuneinfo->title,-1);
+		if(tuneinfo->album && tuneinfo->album[0] != '\0')
+			xmlnode_insert_data(xmlnode_new_child(tunenode, "source"),tuneinfo->album,-1);
+		if(tuneinfo->url && tuneinfo->url[0] != '\0')
+			xmlnode_insert_data(xmlnode_new_child(tunenode, "uri"),tuneinfo->url,-1);
+		if(tuneinfo->time >= 0) {
+			char *length = g_strdup_printf("%d", tuneinfo->time);
+			xmlnode_insert_data(xmlnode_new_child(tunenode, "length"),length,-1);
+			g_free(length);
+		}
+		if(tuneinfo->track && tuneinfo->track[0] != '\0')
+			xmlnode_insert_data(xmlnode_new_child(tunenode, "track"),tuneinfo->track,-1);
+	}
+	
+	jabber_pep_publish(js, publish);
+	/* publish is freed by jabber_pep_publish -> jabber_iq_send -> jabber_iq_free
+	   (yay for well-defined memory management rules) */
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/usertune.h	Wed Sep 05 22:32:14 2007 +0000
@@ -0,0 +1,43 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#ifndef _PURPLE_JABBER_USERTUNE_H_
+#define _PURPLE_JABBER_USERTUNE_H_
+
+#include "jabber.h"
+
+/* Implementation of XEP-0118 */
+
+typedef struct _PurpleJabberTuneInfo PurpleJabberTuneInfo;
+struct _PurpleJabberTuneInfo {
+	char *artist;
+	char *title;
+	char *album;
+	char *track; /* either the index of the track in the album or the URL for a stream */
+	int time; /* in seconds, -1 for unknown */
+	char *url;
+};
+
+void jabber_tune_init(void);
+
+void jabber_tune_set(PurpleConnection *gc, const PurpleJabberTuneInfo *tuneinfo);
+
+#endif /* _PURPLE_JABBER_USERTUNE_H_ */
--- a/libpurple/protocols/jabber/xdata.c	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/protocols/jabber/xdata.c	Wed Sep 05 22:32:14 2007 +0000
@@ -37,22 +37,39 @@
 struct jabber_x_data_data {
 	GHashTable *fields;
 	GSList *values;
-	jabber_x_data_cb cb;
+	jabber_x_data_action_cb cb;
 	gpointer user_data;
 	JabberStream *js;
+	GList *actions;
+	PurpleRequestFieldGroup *actiongroup;
 };
 
 static void jabber_x_data_ok_cb(struct jabber_x_data_data *data, PurpleRequestFields *fields) {
 	xmlnode *result = xmlnode_new("x");
-	jabber_x_data_cb cb = data->cb;
+	jabber_x_data_action_cb cb = data->cb;
 	gpointer user_data = data->user_data;
 	JabberStream *js = data->js;
 	GList *groups, *flds;
+	char *actionhandle = NULL;
+	gboolean hasActions = (data->actions != NULL);
 
 	xmlnode_set_namespace(result, "jabber:x:data");
 	xmlnode_set_attrib(result, "type", "submit");
 
 	for(groups = purple_request_fields_get_groups(fields); groups; groups = groups->next) {
+		if(groups->data == data->actiongroup) {
+			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);
+				int handleindex;
+				if(strcmp(id, "libpurple:jabber:xdata:actions"))
+					continue;
+				handleindex = purple_request_field_choice_get_value(field);
+				actionhandle = g_strdup(g_list_nth_data(data->actions, handleindex));
+				break;
+			}
+			continue;
+		}
 		for(flds = purple_request_field_group_get_fields(groups->data); flds; flds = flds->next) {
 			xmlnode *fieldnode, *valuenode;
 			PurpleRequestField *field = flds->data;
@@ -127,31 +144,59 @@
 		g_free(data->values->data);
 		data->values = g_slist_delete_link(data->values, data->values);
 	}
+	if (data->actions) {
+		GList *action;
+		for(action = data->actions; action; action = g_list_next(action)) {
+			g_free(action->data);
+		}
+		g_list_free(data->actions);
+	}
 	g_free(data);
 
-	cb(js, result, user_data);
+	if (hasActions) {
+		cb(js, result, actionhandle, user_data);
+		g_free(actionhandle);
+	} else
+		((jabber_x_data_cb)cb)(js, result, user_data);
 }
 
 static void jabber_x_data_cancel_cb(struct jabber_x_data_data *data, PurpleRequestFields *fields) {
 	xmlnode *result = xmlnode_new("x");
-	jabber_x_data_cb cb = data->cb;
+	jabber_x_data_action_cb cb = data->cb;
 	gpointer user_data = data->user_data;
 	JabberStream *js = data->js;
+	gboolean hasActions = FALSE;
 	g_hash_table_destroy(data->fields);
 	while(data->values) {
 		g_free(data->values->data);
 		data->values = g_slist_delete_link(data->values, data->values);
 	}
+	if (data->actions) {
+		hasActions = TRUE;
+		GList *action;
+		for(action = data->actions; action; action = g_list_next(action)) {
+			g_free(action->data);
+		}
+		g_list_free(data->actions);
+	}
 	g_free(data);
 
 	xmlnode_set_namespace(result, "jabber:x:data");
 	xmlnode_set_attrib(result, "type", "cancel");
 
-	cb(js, result, user_data);
+	if (hasActions)
+		cb(js, result, NULL, user_data);
+	else
+		((jabber_x_data_cb)cb)(js, result, user_data);
 }
 
 void *jabber_x_data_request(JabberStream *js, xmlnode *packet, jabber_x_data_cb cb, gpointer user_data)
 {
+	return jabber_x_data_request_with_actions(js, packet, NULL, 0, (jabber_x_data_action_cb)cb, user_data);
+}
+
+void *jabber_x_data_request_with_actions(JabberStream *js, xmlnode *packet, GList *actions, int defaultaction, jabber_x_data_action_cb cb, gpointer user_data)
+{
 	void *handle;
 	xmlnode *fn, *x;
 	PurpleRequestFields *fields;
@@ -180,7 +225,7 @@
 		char *value = NULL;
 
 		if(!type)
-			continue;
+			type = "text-single";
 
 		if(!var && strcmp(type, "fixed"))
 			continue;
@@ -191,8 +236,6 @@
 			value = xmlnode_get_data(valuenode);
 
 
-		/* XXX: handle <required/> */
-
 		if(!strcmp(type, "text-private")) {
 			if((valuenode = xmlnode_get_child(fn, "value")))
 				value = xmlnode_get_data(valuenode);
@@ -324,6 +367,26 @@
 
 			g_free(value);
 		}
+
+		if(field && xmlnode_get_child(fn, "required"))
+			purple_request_field_set_required(field,TRUE);
+	}
+	
+	if(actions != NULL) {
+		PurpleRequestField *actionfield;
+		GList *action;
+		data->actiongroup = group = purple_request_field_group_new(_("Actions"));
+		purple_request_fields_add_group(fields, group);
+		actionfield = purple_request_field_choice_new("libpurple:jabber:xdata:actions", _("Select an action"), defaultaction);
+
+		for(action = actions; action; action = g_list_next(action)) {
+			JabberXDataAction *a = action->data;
+			
+			purple_request_field_choice_add(actionfield, a->name);
+			data->actions = g_list_append(data->actions, g_strdup(a->handle));
+		}
+		purple_request_field_set_required(actionfield,TRUE);
+		purple_request_field_group_add_field(group, actionfield);
 	}
 
 	if((x = xmlnode_get_child(packet, "title")))
--- a/libpurple/protocols/jabber/xdata.h	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/protocols/jabber/xdata.h	Wed Sep 05 22:32:14 2007 +0000
@@ -25,7 +25,14 @@
 #include "jabber.h"
 #include "xmlnode.h"
 
+typedef struct _JabberXDataAction {
+	char *name;
+	char *handle;
+} JabberXDataAction;
+
 typedef void (*jabber_x_data_cb)(JabberStream *js, xmlnode *result, gpointer user_data);
+typedef void (*jabber_x_data_action_cb)(JabberStream *js, xmlnode *result, const char *actionhandle, gpointer user_data);
 void *jabber_x_data_request(JabberStream *js, xmlnode *packet, jabber_x_data_cb cb, gpointer user_data);
+void *jabber_x_data_request_with_actions(JabberStream *js, xmlnode *packet, GList *actions, int defaultaction, jabber_x_data_action_cb cb, gpointer user_data);
 
 #endif /* _PURPLE_JABBER_XDATA_H_ */
--- a/libpurple/protocols/msn/msn.c	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/protocols/msn/msn.c	Wed Sep 05 22:32:14 2007 +0000
@@ -2138,6 +2138,7 @@
 	NULL,					/* whiteboard_prpl_ops */
 	NULL,					/* send_raw */
 	NULL,					/* roomlist_room_serialize */
+	NULL,					/* unregister_user */
 
 #ifdef MSN_USE_ATTENTION_API
 	msn_send_attention,                     /* send_attention */
@@ -2147,7 +2148,6 @@
 	NULL,
 	NULL,
 #endif
-	NULL,
 	NULL
 };
 
--- a/libpurple/protocols/yahoo/yahoo.c	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Wed Sep 05 22:32:14 2007 +0000
@@ -4345,6 +4345,7 @@
 	&yahoo_whiteboard_prpl_ops,
 	NULL, /* send_raw */
 	NULL, /* roomlist_room_serialize */
+	NULL, /* unregister_user */
 
 #ifdef YAHOO_USE_ATTENTION_API
 	yahoo_send_attention,
@@ -4355,7 +4356,6 @@
 #endif
 
 	/* padding */
-	NULL,
 	NULL
 };
 
--- a/libpurple/prpl.h	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/prpl.h	Wed Sep 05 22:32:14 2007 +0000
@@ -347,12 +347,17 @@
 	/* room list serialize */
 	char *(*roomlist_room_serialize)(PurpleRoomlistRoom *room);
 
+	/* Remove the user from the server. (This is only at the bottom to keep binary compatibility.)
+	 * The account can either be connected or disconnected. After the removal is finished,
+	 * the connection will stay open and has to be closed!
+	 */
+	void (*unregister_user)(PurpleAccount *, PurpleAccountUnregistrationCb cb, void *user_data);
+	
 	/* Attention API for sending & receiving zaps/nudges/buzzes etc. */
 	gboolean (*send_attention)(PurpleConnection *gc, const char *username, guint type);
 	GList *(*attention_types)(PurpleAccount *acct);
 
-	void (*_purple_reserved3)(void);
-	void (*_purple_reserved4)(void);
+	void (*_purple_reserved1)(void);
 };
 
 #define PURPLE_IS_PROTOCOL_PLUGIN(plugin) \
--- a/libpurple/status.h	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/status.h	Wed Sep 05 22:32:14 2007 +0000
@@ -113,6 +113,16 @@
 #include "conversation.h"
 #include "value.h"
 
+#define PURPLE_TUNE_ARTIST	"tune_artist"
+#define PURPLE_TUNE_TITLE	"tune_title"
+#define PURPLE_TUNE_ALBUM	"tune_album"
+#define PURPLE_TUNE_GENRE	"tune_genre"
+#define PURPLE_TUNE_COMMENT	"tune_comment"
+#define PURPLE_TUNE_TRACK	"tune_track"
+#define PURPLE_TUNE_TIME	"tune_time"
+#define PURPLE_TUNE_YEAR	"tune_year"
+#define PURPLE_TUNE_URL	"tune_url"
+
 #ifdef __cplusplus
 extern "C" {
 #endif
--- a/libpurple/xmlnode.c	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/xmlnode.c	Wed Sep 05 22:32:14 2007 +0000
@@ -636,6 +636,7 @@
 	g_return_val_if_fail(src != NULL, NULL);
 
 	ret = new_node(src->name, src->type);
+	ret->xmlns = g_strdup(src->xmlns);
 	if(src->data) {
 		if(src->data_sz) {
 			ret->data = g_memdup(src->data, src->data_sz);
--- a/libpurple/xmlnode.h	Wed Sep 05 12:56:35 2007 +0000
+++ b/libpurple/xmlnode.h	Wed Sep 05 22:32:14 2007 +0000
@@ -262,7 +262,7 @@
 xmlnode *xmlnode_copy(const xmlnode *src);
 
 /**
- * Frees a node and all of it's children.
+ * Frees a node and all of its children.
  *
  * @param node The node to free.
  */