diff src/protocols/jabber/jabber.c @ 2086:424a40f12a6c

[gaim-migrate @ 2096] moving protocols from plugins/ to src/protocols. making it so that you can select which protocols are compiled statically. committer: Tailor Script <tailor@pidgin.im>
author Eric Warmenhoven <eric@warmenhoven.org>
date Tue, 31 Jul 2001 01:00:39 +0000
parents
children b66aca8e8dce
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/jabber/jabber.c	Tue Jul 31 01:00:39 2001 +0000
@@ -0,0 +1,2081 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * gaim
+ *
+ * Some code copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
+ * libfaim code copyright 1998, 1999 Adam Fritzler <afritz@auk.cx>
+ *
+ * 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
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+
+#include <gtk/gtk.h>
+#ifdef MAX
+#undef MAX
+#endif
+#ifdef MIN
+#undef MIN
+#endif
+#include <netdb.h>
+#include <unistd.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <sys/socket.h>
+#include <sys/utsname.h>
+#include <sys/stat.h>
+#include "multi.h"
+#include "prpl.h"
+#include "gaim.h"
+#include "jabber.h"
+#include "proxy.h"
+
+#include "pixmaps/available.xpm"
+#include "pixmaps/available-away.xpm"
+#include "pixmaps/available-chat.xpm"
+#include "pixmaps/available-xa.xpm"
+#include "pixmaps/available-dnd.xpm"
+
+/* The priv member of gjconn's is a gaim_connection for now. */
+#define GJ_GC(x) ((struct gaim_connection *)(x)->priv)
+
+#define IQID_AUTH "__AUTH__"
+
+#define IQ_NONE -1
+#define IQ_AUTH 0
+#define IQ_ROSTER 1
+
+#define UC_AWAY 0x38
+#define UC_CHAT 0x48
+#define UC_XA   0x98
+#define UC_DND  0x118
+
+#define DEFAULT_SERVER "jabber.org"
+#define DEFAULT_GROUPCHAT "conference.jabber.org"
+#define DEFAULT_PORT 5222
+
+#define USEROPT_PORT 0
+
+typedef struct gjconn_struct {
+	/* Core structure */
+	pool p;			/* Memory allocation pool */
+	int state;		/* Connection state flag */
+	int fd;			/* Connection file descriptor */
+	jid user;		/* User info */
+	char *pass;		/* User passwd */
+
+	/* Stream stuff */
+	int id;			/* id counter for jab_getid() function */
+	char idbuf[9];		/* temporary storage for jab_getid() */
+	char *sid;		/* stream id from server, for digest auth */
+	XML_Parser parser;	/* Parser instance */
+	xmlnode current;	/* Current node in parsing instance.. */
+
+	/* Event callback ptrs */
+	void (*on_state)(struct gjconn_struct *j, int state);
+	void (*on_packet)(struct gjconn_struct *j, jpacket p);
+
+	void *priv;
+
+} *gjconn, gjconn_struct;
+
+typedef void (*gjconn_state_h)(gjconn j, int state);
+typedef void (*gjconn_packet_h)(gjconn j, jpacket p);
+
+static gjconn gjab_new(char *user, char *pass, void *priv);
+static void gjab_delete(gjconn j);
+static void gjab_state_handler(gjconn j, gjconn_state_h h);
+static void gjab_packet_handler(gjconn j, gjconn_packet_h h);
+static void gjab_start(gjconn j);
+static void gjab_stop(gjconn j);
+/*
+static int gjab_getfd(gjconn j);
+static jid gjab_getjid(gjconn j);
+static char *gjab_getsid(gjconn j);
+*/
+static char *gjab_getid(gjconn j);
+static void gjab_send(gjconn j, xmlnode x);
+static void gjab_send_raw(gjconn j, const char *str);
+static void gjab_recv(gjconn j);
+static void gjab_auth(gjconn j);
+
+struct jabber_data {
+	gjconn jc;
+	gboolean did_import;
+	GSList *pending_chats;
+	GSList *existing_chats;
+	GHashTable *hash;
+	time_t idle;
+};
+
+struct jabber_chat {
+	jid Jid;
+	struct gaim_connection *gc;
+	struct conversation *b;
+	int id;
+};
+
+static char *jabber_name()
+{
+	return "Jabber";
+}
+
+#define STATE_EVT(arg) if(j->on_state) { (j->on_state)(j, (arg) ); }
+
+static char *create_valid_jid(const char *given, char *server, char *resource)
+{
+	char *valid;
+
+	if (!strchr(given, '@'))
+		valid = g_strdup_printf("%s@%s/%s", given, server, resource);
+	else if (!strchr(strchr(given, '@'), '/'))
+		valid = g_strdup_printf("%s/%s", given, resource);
+	else
+		valid = g_strdup(given);
+
+	return valid;
+}
+
+static gjconn gjab_new(char *user, char *pass, void *priv)
+{
+	pool p;
+	gjconn j;
+
+	if (!user)
+		return (NULL);
+
+	p = pool_new();
+	if (!p)
+		return (NULL);
+	j = pmalloc_x(p, sizeof(gjconn_struct), 0);
+	if (!j)
+		return (NULL);
+	j->p = p;
+
+	j->user = jid_new(p, user);
+	j->pass = pstrdup(p, pass);
+
+	j->state = JCONN_STATE_OFF;
+	j->id = 1;
+	j->fd = -1;
+
+	j->priv = priv;
+
+	return j;
+}
+
+static void gjab_delete(gjconn j)
+{
+	if (!j)
+		return;
+
+	gjab_stop(j);
+	pool_free(j->p);
+}
+
+static void gjab_state_handler(gjconn j, gjconn_state_h h)
+{
+	if (!j)
+		return;
+
+	j->on_state = h;
+}
+
+static void gjab_packet_handler(gjconn j, gjconn_packet_h h)
+{
+	if (!j)
+		return;
+
+	j->on_packet = h;
+}
+
+static void gjab_stop(gjconn j)
+{
+	if (!j || j->state == JCONN_STATE_OFF)
+		return;
+
+	j->state = JCONN_STATE_OFF;
+	gjab_send_raw(j, "</stream:stream>");
+	close(j->fd);
+	j->fd = -1;
+	XML_ParserFree(j->parser);
+	j->parser = NULL;
+}
+
+/*
+static int gjab_getfd(gjconn j)
+{
+	if (j)
+		return j->fd;
+	else
+		return -1;
+}
+
+static jid gjab_getjid(gjconn j)
+{
+	if (j)
+		return (j->user);
+	else
+		return NULL;
+}
+
+static char *gjab_getsid(gjconn j)
+{
+	if (j)
+		return (j->sid);
+	else
+		return NULL;
+}
+*/
+
+static char *gjab_getid(gjconn j)
+{
+	snprintf(j->idbuf, 8, "%d", j->id++);
+	return &j->idbuf[0];
+}
+
+static void gjab_send(gjconn j, xmlnode x)
+{
+	if (j && j->state != JCONN_STATE_OFF) {
+		char *buf = xmlnode2str(x);
+		if (buf)
+			write(j->fd, buf, strlen(buf));
+		debug_printf("gjab_send: %s\n", buf);
+	}
+}
+
+static void gjab_send_raw(gjconn j, const char *str)
+{
+	if (j && j->state != JCONN_STATE_OFF) {
+		write(j->fd, str, strlen(str));
+		debug_printf("gjab_send_raw: %s\n", str);
+	}
+}
+
+static void gjab_reqroster(gjconn j)
+{
+	xmlnode x;
+
+	x = jutil_iqnew(JPACKET__GET, NS_ROSTER);
+	xmlnode_put_attrib(x, "id", gjab_getid(j));
+
+	gjab_send(j, x);
+	xmlnode_free(x);
+}
+
+static void gjab_auth(gjconn j)
+{
+	xmlnode x, y, z;
+	char *hash, *user;
+
+	if (!j)
+		return;
+
+	x = jutil_iqnew(JPACKET__SET, NS_AUTH);
+	xmlnode_put_attrib(x, "id", IQID_AUTH);
+	y = xmlnode_get_tag(x, "query");
+
+	user = j->user->user;
+
+	if (user) {
+		z = xmlnode_insert_tag(y, "username");
+		xmlnode_insert_cdata(z, user, -1);
+	}
+
+	z = xmlnode_insert_tag(y, "resource");
+	xmlnode_insert_cdata(z, j->user->resource, -1);
+
+	if (j->sid) {
+		z = xmlnode_insert_tag(y, "digest");
+		hash = pmalloc(x->p, strlen(j->sid) + strlen(j->pass) + 1);
+		strcpy(hash, j->sid);
+		strcat(hash, j->pass);
+		hash = shahash(hash);
+		xmlnode_insert_cdata(z, hash, 40);
+	} else {
+		z = xmlnode_insert_tag(y, "password");
+		xmlnode_insert_cdata(z, j->pass, -1);
+	}
+
+	gjab_send(j, x);
+	xmlnode_free(x);
+
+	return;
+}
+
+static void gjab_recv(gjconn j)
+{
+	static char buf[4096];
+	int len;
+
+	if (!j || j->state == JCONN_STATE_OFF)
+		return;
+
+	if ((len = read(j->fd, buf, sizeof(buf) - 1))) {
+		buf[len] = '\0';
+		debug_printf("input (len %d): %s\n", len, buf);
+		XML_Parse(j->parser, buf, len, 0);
+	} else if (len <= 0) {
+		STATE_EVT(JCONN_STATE_OFF)
+	}
+}
+
+static void startElement(void *userdata, const char *name, const char **attribs)
+{
+	xmlnode x;
+	gjconn j = (gjconn) userdata;
+
+	if (j->current) {
+		/* Append the node to the current one */
+		x = xmlnode_insert_tag(j->current, name);
+		xmlnode_put_expat_attribs(x, attribs);
+
+		j->current = x;
+	} else {
+		x = xmlnode_new_tag(name);
+		xmlnode_put_expat_attribs(x, attribs);
+		if (strcmp(name, "stream:stream") == 0) {
+			/* special case: name == stream:stream */
+			/* id attrib of stream is stored for digest auth */
+			j->sid = xmlnode_get_attrib(x, "id");
+			/* STATE_EVT(JCONN_STATE_AUTH) */
+		} else {
+			j->current = x;
+		}
+	}
+}
+
+static void endElement(void *userdata, const char *name)
+{
+	gjconn j = (gjconn) userdata;
+	xmlnode x;
+	jpacket p;
+
+	if (j->current == NULL) {
+		/* we got </stream:stream> */
+		STATE_EVT(JCONN_STATE_OFF)
+		    return;
+	}
+
+	x = xmlnode_get_parent(j->current);
+
+	if (!x) {
+		/* it is time to fire the event */
+		p = jpacket_new(j->current);
+
+		if (j->on_packet)
+			(j->on_packet) (j, p);
+		else
+			xmlnode_free(j->current);
+	}
+
+	j->current = x;
+}
+
+static void jabber_callback(gpointer data, gint source, GdkInputCondition condition)
+{
+	struct gaim_connection *gc = (struct gaim_connection *)data;
+	struct jabber_data *jd = (struct jabber_data *)gc->proto_data;
+
+	gjab_recv(jd->jc);
+}
+
+static void charData(void *userdata, const char *s, int slen)
+{
+	gjconn j = (gjconn) userdata;
+
+	if (j->current)
+		xmlnode_insert_cdata(j->current, s, slen);
+}
+
+static void gjab_connected(gpointer data, gint source, GdkInputCondition cond)
+{
+	xmlnode x;
+	char *t, *t2;
+	struct gaim_connection *gc = data;
+	struct jabber_data *jd;
+	gjconn j;
+
+	if (!g_slist_find(connections, gc)) {
+		close(source);
+		return;
+	}
+
+	jd = gc->proto_data;
+	j = jd->jc;
+
+	if (source == -1) {
+		STATE_EVT(JCONN_STATE_OFF)
+		return;
+	}
+
+	if (j->fd != source)
+		j->fd = source;
+
+	j->state = JCONN_STATE_CONNECTED;
+	STATE_EVT(JCONN_STATE_CONNECTED)
+
+	/* start stream */
+	x = jutil_header(NS_CLIENT, j->user->server);
+	t = xmlnode2str(x);
+	/* this is ugly, we can create the string here instead of jutil_header */
+	/* what do you think about it? -madcat */
+	t2 = strstr(t, "/>");
+	*t2++ = '>';
+	*t2 = '\0';
+	gjab_send_raw(j, "<?xml version='1.0'?>");
+	gjab_send_raw(j, t);
+	xmlnode_free(x);
+
+	j->state = JCONN_STATE_ON;
+	STATE_EVT(JCONN_STATE_ON);
+
+	gc = GJ_GC(j);
+	gc->inpa = gdk_input_add(j->fd, GDK_INPUT_READ, jabber_callback, gc);
+}
+
+static void gjab_start(gjconn j)
+{
+	struct aim_user *user;
+	int port;
+
+	if (!j || j->state != JCONN_STATE_OFF)
+		return;
+
+	user = GJ_GC(j)->user;
+	port = user->proto_opt[USEROPT_PORT][0] ? atoi(user->proto_opt[USEROPT_PORT]) : DEFAULT_PORT;
+
+	j->parser = XML_ParserCreate(NULL);
+	XML_SetUserData(j->parser, (void *)j);
+	XML_SetElementHandler(j->parser, startElement, endElement);
+	XML_SetCharacterDataHandler(j->parser, charData);
+
+	j->fd = proxy_connect(j->user->server, port, gjab_connected, GJ_GC(j));
+	if (!user->gc || (j->fd < 0)) {
+		STATE_EVT(JCONN_STATE_OFF)
+		return;
+	}
+}
+
+static struct conversation *find_chat(struct gaim_connection *gc, char *name)
+{
+	GSList *bcs = gc->buddy_chats;
+	struct conversation *b = NULL;
+	char *chat = g_strdup(normalize(name));
+
+	while (bcs) {
+		b = bcs->data;
+		if (!strcasecmp(normalize(b->name), chat))
+			break;
+		b = NULL;
+		bcs = bcs->next;
+	}
+
+	g_free(chat);
+	return b;
+}
+
+static struct jabber_chat *find_existing_chat(struct gaim_connection *gc, jid chat)
+{
+	GSList *bcs = ((struct jabber_data *)gc->proto_data)->existing_chats;
+	struct jabber_chat *jc = NULL;
+
+	while (bcs) {
+		jc = bcs->data;
+		if (!jid_cmpx(chat, jc->Jid, JID_USER | JID_SERVER))
+			break;
+		jc = NULL;
+		bcs = bcs->next;
+	}
+
+	return jc;
+}
+
+static struct jabber_chat *find_pending_chat(struct gaim_connection *gc, jid chat)
+{
+	GSList *bcs = ((struct jabber_data *)gc->proto_data)->pending_chats;
+	struct jabber_chat *jc = NULL;
+
+	while (bcs) {
+		jc = bcs->data;
+		if (!jid_cmpx(chat, jc->Jid, JID_USER | JID_SERVER))
+			break;
+		jc = NULL;
+		bcs = bcs->next;
+	}
+
+	return jc;
+}
+
+static gboolean find_chat_buddy(struct conversation *b, char *name)
+{
+	GList *m = b->in_room;
+
+	while (m) {
+		if (!strcmp(m->data, name))
+			return TRUE;
+		m = m->next;
+	}
+
+	return FALSE;
+}
+
+static void jabber_handlemessage(gjconn j, jpacket p)
+{
+	xmlnode y, xmlns, subj;
+
+	char *from = NULL, *msg = NULL, *type = NULL, *topic = NULL;
+	char m[BUF_LONG * 2];
+
+	type = xmlnode_get_attrib(p->x, "type");
+
+	if (!type || !strcasecmp(type, "normal") || !strcasecmp(type, "chat")) {
+
+		/* XXX namespaces could be handled better. (mid) */
+		if ((xmlns = xmlnode_get_tag(p->x, "x")))
+			type = xmlnode_get_attrib(xmlns, "xmlns");
+
+		from = jid_full(p->from);
+		/*
+		if ((y = xmlnode_get_tag(p->x, "html"))) {
+			msg = xmlnode_get_data(y);
+		} else
+		*/
+		if ((y = xmlnode_get_tag(p->x, "body"))) {
+			msg = xmlnode_get_data(y);
+		}
+
+		msg = utf8_to_str(msg);
+
+		if (!from)
+			return;
+
+		if (type && !strcasecmp(type, "jabber:x:conference")) {
+			char *room;
+
+			room = xmlnode_get_attrib(xmlns, "jid");
+
+			serv_got_chat_invite(GJ_GC(j), room, 0, from, msg);
+		} else if (msg) { /* whisper */
+			struct jabber_chat *jc;
+			g_snprintf(m, sizeof(m), "%s", msg);
+			if (((jc = find_existing_chat(GJ_GC(j), p->from)) != NULL) && jc->b)
+				serv_got_chat_in(GJ_GC(j), jc->b->id, p->from->resource, 1, m, time(NULL));
+			else {
+				if (find_conversation(jid_full(p->from)))
+					serv_got_im(GJ_GC(j), jid_full(p->from), m, 0, time(NULL));
+				else {
+					from = g_strdup_printf("%s@%s", p->from->user, p->from->server);
+					serv_got_im(GJ_GC(j), from, m, 0, time(NULL));
+					g_free(from);
+				}
+			}
+		}
+
+		if (msg)
+			g_free(msg);
+
+	} else if (!strcasecmp(type, "error")) {
+		if ((y = xmlnode_get_tag(p->x, "error"))) {
+			type = xmlnode_get_attrib(y, "code");
+			msg = xmlnode_get_data(y);
+		}
+
+		if (msg) {
+			from = g_strdup_printf("Error %s", type ? type : "");
+			do_error_dialog(msg, from);
+			g_free(from);
+		}
+	} else if (!strcasecmp(type, "groupchat")) {
+		struct jabber_chat *jc;
+		static int i = 0;
+
+		/*
+		if ((y = xmlnode_get_tag(p->x, "html"))) {
+			msg = xmlnode_get_data(y);
+		} else
+		*/
+		if ((y = xmlnode_get_tag(p->x, "body"))) {
+			msg = xmlnode_get_data(y);
+		}
+
+		msg = utf8_to_str(msg);
+		
+		if ((subj = xmlnode_get_tag(p->x, "subject"))) {
+		   	topic = xmlnode_get_data(subj);
+		} 
+		topic = utf8_to_str(topic);
+
+		jc = find_existing_chat(GJ_GC(j), p->from);
+		if (!jc) {
+			/* we're not in this chat. are we supposed to be? */
+			struct jabber_data *jd = GJ_GC(j)->proto_data;
+			if ((jc = find_pending_chat(GJ_GC(j), p->from)) != NULL) {
+				/* yes, we're supposed to be. so now we are. */
+				jc->b = serv_got_joined_chat(GJ_GC(j), i++, p->from->user);
+				jc->id = jc->b->id;
+				jd->existing_chats = g_slist_append(jd->existing_chats, jc);
+				jd->pending_chats = g_slist_remove(jd->pending_chats, jc);
+			} else {
+				/* no, we're not supposed to be. */
+				g_free(msg);
+				return;
+			}
+		}
+		if (p->from->resource) {
+			if (!y) {
+				if (!find_chat_buddy(jc->b, p->from->resource))
+					add_chat_buddy(jc->b, p->from->resource);
+				else if ((y = xmlnode_get_tag(p->x, "status"))) {
+					char buf[8192];
+					msg = xmlnode_get_data(y);
+					g_snprintf(buf, sizeof(buf), "%s now has status: %s",
+							p->from->resource, msg);
+					write_to_conv(jc->b, buf, WFLAG_SYSTEM, NULL, time(NULL));
+				}
+			} else if (jc->b && msg) {
+				char buf[8192];
+
+				if (topic) {
+					char tbuf[8192];
+					g_snprintf(tbuf, sizeof(tbuf), "%s", topic);
+					chat_set_topic(jc->b, p->from->resource, tbuf);
+				}
+				
+
+				g_snprintf(buf, sizeof(buf), "%s", msg);
+				serv_got_chat_in(GJ_GC(j), jc->b->id, p->from->resource, 0, buf, time(NULL));
+			}
+		} else { /* message from the server */
+		   	if(jc->b && topic) {
+			   	char tbuf[8192];
+				g_snprintf(tbuf, sizeof(tbuf), "%s", topic);
+				chat_set_topic(jc->b, "", tbuf);
+			}
+		}
+
+		g_free(msg);
+		g_free(topic);
+
+	} else {
+		debug_printf("unhandled message %s\n", type);
+	}
+}
+
+static void jabber_handlepresence(gjconn j, jpacket p)
+{
+	char *to, *from, *type;
+	struct buddy *b = NULL;
+	jid who;
+	char *buddy;
+	xmlnode y;
+	char *show;
+	int state = UC_NORMAL;
+	GSList *resources;
+	char *res;
+	struct conversation *cnv = NULL;
+	struct jabber_chat *jc = NULL;
+
+	to = xmlnode_get_attrib(p->x, "to");
+	from = xmlnode_get_attrib(p->x, "from");
+	type = xmlnode_get_attrib(p->x, "type");
+
+	if ((y = xmlnode_get_tag(p->x, "show"))) {
+		show = xmlnode_get_data(y);
+		if (!show) {
+			state = UC_NORMAL;
+		} else if (!strcasecmp(show, "away")) {
+			state = UC_AWAY;
+		} else if (!strcasecmp(show, "chat")) {
+			state = UC_CHAT;
+		} else if (!strcasecmp(show, "xa")) {
+			state = UC_XA;
+		} else if (!strcasecmp(show, "dnd")) {
+			state = UC_DND;
+		}
+	} else {
+		state = UC_NORMAL;
+	}
+
+	who = jid_new(j->p, from);
+	if (who->user == NULL) {
+		/* FIXME: transport */
+		return;
+	}
+
+	buddy = g_strdup_printf("%s@%s", who->user, who->server);
+
+	/* um. we're going to check if it's a chat. if it isn't, and there are pending
+	 * chats, create the chat. if there aren't pending chats, add the buddy. */
+	if ((cnv = find_chat(GJ_GC(j), who->user)) == NULL) {
+		static int i = 0x70;
+		struct jabber_data *jd = GJ_GC(j)->proto_data;
+		if ((jc = find_pending_chat(GJ_GC(j), who)) != NULL) {
+			jc->b = cnv = serv_got_joined_chat(GJ_GC(j), i++, who->user);
+			jc->id = jc->b->id;
+			jd->existing_chats = g_slist_append(jd->existing_chats, jc);
+			jd->pending_chats = g_slist_remove(jd->pending_chats, jc);
+		} else if (!(b = find_buddy(GJ_GC(j), buddy))) {
+			b = add_buddy(GJ_GC(j), "Buddies", buddy, buddy);
+			do_export(GJ_GC(j));
+		}
+	}
+
+	if (!cnv) {
+		resources = b->proto_data;
+		res = who->resource;
+		if (res)
+			while (resources) {
+				if (!strcmp(res, resources->data))
+					break;
+				resources = resources->next;
+			}
+
+		if (type && (strcasecmp(type, "unavailable") == 0)) {
+			if (resources) {
+				g_free(resources->data);
+				b->proto_data = g_slist_remove(b->proto_data, resources->data);
+			}
+			if (!b->proto_data) {
+				serv_got_update(GJ_GC(j), buddy, 0, 0, 0, 0, 0, 0);
+			}
+		} else {
+			/* keep track of away msg same as yahoo plugin */
+			struct jabber_data *jd = GJ_GC(j)->proto_data;
+			gpointer val = g_hash_table_lookup(jd->hash, b->name);
+			if (val)
+			   	g_free(val);
+			g_hash_table_insert(jd->hash, g_strdup(b->name), g_strdup(xmlnode_get_tag_data(p->x, "status")));
+
+
+			if (!resources) {
+				b->proto_data = g_slist_append(b->proto_data, g_strdup(res));
+			}
+
+			serv_got_update(GJ_GC(j), buddy, 1, 0, 0, 0, state, 0);
+
+		}
+	} else {
+		if (who->resource) {
+			if (type && !strcasecmp(type, "unavailable")) {
+				struct jabber_data *jd;
+				if (!jc && !(jc = find_existing_chat(GJ_GC(j), who))) {
+					g_free(buddy);
+					return;
+				}
+				jd = jc->gc->proto_data;
+				if (strcmp(who->resource, jc->Jid->resource) && jc->b) {
+					remove_chat_buddy(jc->b, who->resource);
+					return;
+				}
+
+				jd->existing_chats = g_slist_remove(jd->existing_chats, jc);
+				serv_got_chat_left(GJ_GC(j), jc->id);
+				g_free(jc);
+			} else {
+				if ((!jc && !(jc = find_existing_chat(GJ_GC(j), who))) || !jc->b) {
+					g_free(buddy);
+					return;
+				}
+				if (!find_chat_buddy(jc->b, who->resource))
+					add_chat_buddy(jc->b, who->resource);
+				else if ((y = xmlnode_get_tag(p->x, "status"))) {
+					char buf[8192];
+					char *msg = xmlnode_get_data(y);
+					g_snprintf(buf, sizeof(buf), "%s now has status: %s",
+							p->from->resource, msg);
+					write_to_conv(jc->b, buf, WFLAG_SYSTEM, NULL, time(NULL));
+				}
+			}
+		}
+	}
+
+	g_free(buddy);
+
+	return;
+}
+
+static void jabber_handles10n(gjconn j, jpacket p)
+{
+	xmlnode g;
+	char *Jid = xmlnode_get_attrib(p->x, "from");
+	char *ask = xmlnode_get_attrib(p->x, "type");
+
+	g = xmlnode_new_tag("presence");
+	xmlnode_put_attrib(g, "to", Jid);
+	if (!strcmp(ask, "subscribe"))
+		xmlnode_put_attrib(g, "type", "subscribed");
+	else if (!strcmp(ask, "unsubscribe"))
+		xmlnode_put_attrib(g, "type", "unsubscribed");
+	else
+		return;
+
+	gjab_send(j, g);
+}
+
+static void jabber_handleroster(gjconn j, xmlnode querynode)
+{
+	xmlnode x;
+
+	x = xmlnode_get_firstchild(querynode);
+	while (x) {
+		xmlnode g;
+		char *Jid, *name, *sub, *ask;
+		jid who;
+
+		Jid = xmlnode_get_attrib(x, "jid");
+		name = xmlnode_get_attrib(x, "name");
+		sub = xmlnode_get_attrib(x, "subscription");
+		ask = xmlnode_get_attrib(x, "ask");
+		who = jid_new(j->p, Jid);
+
+		if ((g = xmlnode_get_firstchild(x))) {
+			while (g) {
+				if (strncasecmp(xmlnode_get_name(g), "group", 5) == 0) {
+					struct buddy *b = NULL;
+					char *groupname, *buddyname;
+
+					if (who->user == NULL) {
+						/* FIXME: transport */
+						g = xmlnode_get_nextsibling(g);
+						continue;
+					}
+					buddyname = g_strdup_printf("%s@%s", who->user, who->server);
+					groupname = xmlnode_get_data(xmlnode_get_firstchild(g));
+					if (groupname == NULL)
+						groupname = "Buddies";
+					if (strcasecmp(sub, "from") && strcasecmp(sub, "none") &&
+							!(b = find_buddy(GJ_GC(j), buddyname))) {
+						debug_printf("adding buddy: %s\n", buddyname);
+						b = add_buddy(GJ_GC(j), groupname, buddyname,
+							      name ? name : buddyname);
+						do_export(GJ_GC(j));
+					/*
+					} else if (b) {
+						debug_printf("updating buddy: %s/%s\n", buddyname, name);
+						g_snprintf(b->name, sizeof(b->name), "%s", buddyname);
+						g_snprintf(b->show, sizeof(b->show), "%s",
+							   name ? name : buddyname);
+					*/
+					}
+					g_free(buddyname);
+				}
+				g = xmlnode_get_nextsibling(g);
+			}
+		} else {
+			struct buddy *b;
+			char *buddyname;
+
+			if (who->user == NULL) {
+				/* FIXME: transport */
+				x = xmlnode_get_nextsibling(x);
+				continue;
+			}
+			buddyname = g_strdup_printf("%s@%s", who->user, who->server);
+			if (strcasecmp(sub, "from") && strcasecmp(sub, "none") &&
+					!(b = find_buddy(GJ_GC(j), buddyname))) {
+				debug_printf("adding buddy: %s\n", buddyname);
+				b = add_buddy(GJ_GC(j), "Buddies", buddyname, name ? name : Jid);
+				do_export(GJ_GC(j));
+			}
+			g_free(buddyname);
+		}
+
+		x = xmlnode_get_nextsibling(x);
+	}
+
+	x = jutil_presnew(0, NULL, "Online");
+	gjab_send(j, x);
+	xmlnode_free(x);
+}
+
+static void jabber_handlevcard(gjconn j, xmlnode querynode, char *from) {
+	char buf[1024];
+	char *fn, *url, *email, *nickname, *status, *desc;
+	jid who;
+	char *buddy;
+	struct jabber_data *jd = GJ_GC(j)->proto_data;
+	int at = 0;
+
+	who = jid_new(j->p, from);
+	buddy = g_strdup_printf("%s@%s", who->user, who->server);
+	
+	fn = xmlnode_get_tag_data(querynode, "FN");
+	url = xmlnode_get_tag_data(querynode, "URL");
+	email = xmlnode_get_tag_data(querynode, "EMAIL");
+	nickname = xmlnode_get_tag_data(querynode, "NICKNAME");
+	desc = xmlnode_get_tag_data(querynode, "DESC");
+	status = g_hash_table_lookup(jd->hash, buddy);
+	if (!status)
+		status = "Online";
+
+	at = g_snprintf(buf, sizeof buf, "<B>Jabber ID:</B> %s<BR>", buddy);
+	if (fn)
+		at += g_snprintf(buf + at, sizeof(buf) - at, "<B>Full Name:</B> %s<BR>", fn);
+	if (nickname)
+		at += g_snprintf(buf + at, sizeof(buf) - at, "<B>Nickname:</B> %s<BR>", nickname);
+	if (url)
+		at += g_snprintf(buf + at, sizeof(buf) - at, "<B>URL:</B> <A HREF=\"%s\">%s</A><BR>",
+				url, url);
+	if (email)
+		at += g_snprintf(buf + at, sizeof(buf) - at,
+				"<B>Email:</B> <A HREF=\"mailto:%s\">%s</A><BR>", email, email);
+	at += g_snprintf(buf + at, sizeof(buf) - at, "<B>Status:</B> %s\n", status);
+	if (desc)
+		at += g_snprintf(buf + at, sizeof(buf) - at, "<HR>%s<br>\n", desc);
+
+	g_show_info_text(buf);
+	g_free(buddy);
+}
+
+static void jabber_handleauthresp(gjconn j, jpacket p)
+{
+	if (jpacket_subtype(p) == JPACKET__RESULT) {
+		debug_printf("auth success\n");
+
+		account_online(GJ_GC(j));
+		serv_finish_login(GJ_GC(j));
+
+		if (bud_list_cache_exists(GJ_GC(j)))
+			do_import(NULL, GJ_GC(j));
+
+		((struct jabber_data *)GJ_GC(j)->proto_data)->did_import = TRUE;
+
+		gjab_reqroster(j);
+	} else {
+		xmlnode xerr;
+		char *errmsg = NULL;
+		int errcode = 0;
+
+		debug_printf("auth failed\n");
+		xerr = xmlnode_get_tag(p->x, "error");
+		if (xerr) {
+			char msg[BUF_LONG];
+			errmsg = xmlnode_get_data(xerr);
+			if (xmlnode_get_attrib(xerr, "code")) {
+				errcode = atoi(xmlnode_get_attrib(xerr, "code"));
+				g_snprintf(msg, sizeof(msg), "Error %d: %s", errcode, errmsg);
+			} else
+				g_snprintf(msg, sizeof(msg), "%s", errmsg);
+			hide_login_progress(GJ_GC(j), msg);
+		} else {
+			hide_login_progress(GJ_GC(j), "Unknown login error");
+		}
+
+		signoff(GJ_GC(j));
+	}
+}
+
+static void jabber_handleversion(gjconn j, xmlnode iqnode) {
+	xmlnode querynode, x;
+	char *id, *from;
+	char os[1024];
+	struct utsname osinfo;
+
+	uname(&osinfo);
+	g_snprintf(os, sizeof os, "%s %s %s", osinfo.sysname, osinfo.release, osinfo.machine);
+
+	id = xmlnode_get_attrib(iqnode, "id");
+	from = xmlnode_get_attrib(iqnode, "from");
+
+	x = jutil_iqnew(JPACKET__RESULT, NS_VERSION);
+
+	xmlnode_put_attrib(x, "to", from);
+	xmlnode_put_attrib(x, "id", id);
+	querynode = xmlnode_get_tag(x, "query");
+	xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "name"), PACKAGE, -1);
+	xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "version"), VERSION, -1);
+	xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "os"), os, -1);
+
+	gjab_send(j, x);
+
+	xmlnode_free(x);
+}
+
+static void jabber_handletime(gjconn j, xmlnode iqnode) {
+	xmlnode querynode, x;
+	char *id, *from;
+	time_t now_t; 
+	struct tm *now;
+	char buf[1024];
+
+	time(&now_t);
+	now = localtime(&now_t);
+
+	id = xmlnode_get_attrib(iqnode, "id");
+	from = xmlnode_get_attrib(iqnode, "from");
+
+	x = jutil_iqnew(JPACKET__RESULT, NS_TIME);
+
+	xmlnode_put_attrib(x, "to", from);
+	xmlnode_put_attrib(x, "id", id);
+	querynode = xmlnode_get_tag(x, "query");
+
+	strftime(buf, 1024, "%Y%m%dT%T", now);
+	xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "utc"), buf, -1);
+	strftime(buf, 1024, "%Z", now);
+	xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "tz"), buf, -1);
+	strftime(buf, 1024, "%d %b %Y %T", now);
+	xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "display"), buf, -1);
+	
+	gjab_send(j, x);
+
+	xmlnode_free(x);
+}
+
+static void jabber_handlelast(gjconn j, xmlnode iqnode) {
+   	xmlnode x, querytag;
+	char *id, *from;
+	struct jabber_data *jd = GJ_GC(j)->proto_data;
+	char idle_time[32];
+	
+	id = xmlnode_get_attrib(iqnode, "id");
+	from = xmlnode_get_attrib(iqnode, "from");
+
+	x = jutil_iqnew(JPACKET__RESULT, "jabber:iq:last");
+
+	xmlnode_put_attrib(x, "to", from);
+	xmlnode_put_attrib(x, "id", id);
+	querytag = xmlnode_get_tag(x, "query");
+	g_snprintf(idle_time, sizeof idle_time, "%ld", jd->idle ? time(NULL) - jd->idle : 0);
+	xmlnode_put_attrib(querytag, "seconds", idle_time);
+
+	gjab_send(j, x);
+	xmlnode_free(x);
+}
+
+static void jabber_handlepacket(gjconn j, jpacket p)
+{
+	switch (p->type) {
+	case JPACKET_MESSAGE:
+		jabber_handlemessage(j, p);
+		break;
+	case JPACKET_PRESENCE:
+		jabber_handlepresence(j, p);
+		break;
+	case JPACKET_IQ:
+		debug_printf("jpacket_subtype: %d\n", jpacket_subtype(p));
+
+		if (xmlnode_get_attrib(p->x, "id") && (strcmp(xmlnode_get_attrib(p->x, "id"), IQID_AUTH) == 0)) {
+			jabber_handleauthresp(j, p);
+			break; /* I'm not sure if you like this style, Eric. */
+		}
+
+		if (jpacket_subtype(p) == JPACKET__SET) {
+		} else if (jpacket_subtype(p) == JPACKET__GET) {
+		   	xmlnode querynode;
+			querynode = xmlnode_get_tag(p->x, "query");
+		   	if(NSCHECK(querynode, NS_VERSION)) {
+			   	jabber_handleversion(j, p->x);
+			} else if (NSCHECK(querynode, NS_TIME)) {
+			   	jabber_handletime(j, p->x);
+			} else if (NSCHECK(querynode, "jabber:iq:last")) {
+			   	jabber_handlelast(j, p->x);
+			}
+		} else if (jpacket_subtype(p) == JPACKET__RESULT) {
+			xmlnode querynode, vcard;
+			char *xmlns, *from;
+
+			from = xmlnode_get_attrib(p->x, "from");
+			querynode = xmlnode_get_tag(p->x, "query");
+			xmlns = xmlnode_get_attrib(querynode, "xmlns");
+			vcard = xmlnode_get_tag(p->x, "vCard");
+
+			if (NSCHECK(querynode, NS_ROSTER)) {
+				jabber_handleroster(j, querynode);
+			} else if (NSCHECK(querynode, NS_VCARD)) {
+			   	jabber_handlevcard(j, querynode, from);
+			} else if(vcard) {
+				jabber_handlevcard(j, vcard, from);
+			} else {
+				/* debug_printf("jabber:iq:query: %s\n", xmlns); */
+			}
+
+		} else if (jpacket_subtype(p) == JPACKET__ERROR) {
+			xmlnode xerr;
+			char *from, *errmsg = NULL;
+			int errcode = 0;
+
+			from = xmlnode_get_attrib(p->x, "from");
+			xerr = xmlnode_get_tag(p->x, "error");
+			if (xerr) {
+				errmsg = xmlnode_get_data(xerr);
+				if (xmlnode_get_attrib(xerr, "code"))
+					errcode = atoi(xmlnode_get_attrib(xerr, "code"));
+			}
+
+			from = g_strdup_printf("Error %d (%s)", errcode, from);
+			do_error_dialog(errmsg, from);
+			g_free(from);
+
+		}
+
+		break;
+
+	case JPACKET_S10N:
+		jabber_handles10n(j, p);
+		break;
+	default:
+		debug_printf("jabber: packet type %d (%s)\n", p->type, xmlnode2str(p->x));
+	}
+
+	xmlnode_free(p->x);
+
+	return;
+}
+
+static void jabber_handlestate(gjconn j, int state)
+{
+	switch (state) {
+	case JCONN_STATE_OFF:
+		hide_login_progress(GJ_GC(j), "Unable to connect");
+		signoff(GJ_GC(j));
+		break;
+	case JCONN_STATE_CONNECTED:
+		set_login_progress(GJ_GC(j), 3, "Connected");
+		break;
+	case JCONN_STATE_ON:
+		set_login_progress(GJ_GC(j), 5, "Logging in...");
+		gjab_auth(j);
+		break;
+	default:
+		debug_printf("state change: %d\n", state);
+	}
+	return;
+}
+
+static void jabber_login(struct aim_user *user)
+{
+	struct gaim_connection *gc = new_gaim_conn(user);
+	struct jabber_data *jd = gc->proto_data = g_new0(struct jabber_data, 1);
+	char *loginname = create_valid_jid(user->username, DEFAULT_SERVER, "GAIM");
+
+	jd->hash = g_hash_table_new(g_str_hash, g_str_equal);
+
+	set_login_progress(gc, 1, "Connecting");
+
+	if (!(jd->jc = gjab_new(loginname, user->password, gc))) {
+		g_free(loginname);
+		debug_printf("jabber: unable to connect (jab_new failed)\n");
+		hide_login_progress(gc, "Unable to connect");
+		signoff(gc);
+		return;
+	}
+
+	g_free(loginname);
+	gjab_state_handler(jd->jc, jabber_handlestate);
+	gjab_packet_handler(jd->jc, jabber_handlepacket);
+	gjab_start(jd->jc);
+}
+
+static gboolean jabber_destroy_hash(gpointer key, gpointer val, gpointer data) {
+   	g_free(key);
+	g_free(val);
+	return TRUE;
+}
+
+static gboolean jabber_free(gpointer data)
+{
+	gjab_delete(data);
+	return FALSE;
+}
+
+static void jabber_close(struct gaim_connection *gc)
+{
+	struct jabber_data *jd = gc->proto_data;
+	g_hash_table_foreach_remove(jd->hash, jabber_destroy_hash, NULL);
+	g_hash_table_destroy(jd->hash);
+	gdk_input_remove(gc->inpa);
+	close(jd->jc->fd);
+	gtk_timeout_add(50, jabber_free, jd->jc);
+	jd->jc = NULL;
+	g_free(jd);
+	gc->proto_data = NULL;
+}
+
+static void jabber_send_im(struct gaim_connection *gc, char *who, char *message, int away)
+{
+	xmlnode x, y;
+	char *realwho;
+	gjconn j = ((struct jabber_data *)gc->proto_data)->jc;
+
+	if (!who || !message)
+		return;
+
+	x = xmlnode_new_tag("message");
+	if (!strchr(who, '@'))
+		realwho = g_strdup_printf("%s@%s", who, j->user->server);
+	else
+		realwho = g_strdup(who);
+	xmlnode_put_attrib(x, "to", realwho);
+	g_free(realwho);
+
+	xmlnode_put_attrib(x, "type", "chat");
+
+	if (message && strlen(message)) {
+		char *utf8 = str_to_utf8(message);
+		y = xmlnode_insert_tag(x, "body");
+		xmlnode_insert_cdata(y, utf8, -1);
+		g_free(utf8);
+	}
+
+	gjab_send(((struct jabber_data *)gc->proto_data)->jc, x);
+	xmlnode_free(x);
+}
+
+static void jabber_add_buddy(struct gaim_connection *gc, char *name)
+{
+	xmlnode x, y;
+	char *realwho;
+	gjconn j = ((struct jabber_data *)gc->proto_data)->jc;
+
+	if (!((struct jabber_data *)gc->proto_data)->did_import)
+		return;
+
+	if (!name)
+		return;
+
+	if (!strcmp(gc->username, name))
+		return;
+
+	if (!strchr(name, '@'))
+		realwho = g_strdup_printf("%s@%s", name, j->user->server);
+	else {
+		jid who = jid_new(j->p, name);
+		if (who->user == NULL) {
+			/* FIXME: transport */
+			return;
+		}
+		realwho = g_strdup_printf("%s@%s", who->user, who->server);
+	}
+
+	x = jutil_iqnew(JPACKET__SET, NS_ROSTER);
+	y = xmlnode_insert_tag(xmlnode_get_tag(x, "query"), "item");
+	xmlnode_put_attrib(y, "jid", realwho);
+	gjab_send(((struct jabber_data *)gc->proto_data)->jc, x);
+	xmlnode_free(x);
+
+	x = xmlnode_new_tag("presence");
+	xmlnode_put_attrib(x, "to", realwho);
+	xmlnode_put_attrib(x, "type", "subscribe");
+	gjab_send(((struct jabber_data *)gc->proto_data)->jc, x);
+
+	g_free(realwho);
+}
+
+static void jabber_remove_buddy(struct gaim_connection *gc, char *name)
+{
+	xmlnode x, y;
+	char *realwho;
+	gjconn j = ((struct jabber_data *)gc->proto_data)->jc;
+
+	if (!name)
+		return;
+
+	if (!strchr(name, '@'))
+		realwho = g_strdup_printf("%s@%s", name, j->user->server);
+	else
+		realwho = g_strdup(name);
+
+	x = jutil_iqnew(JPACKET__SET, NS_ROSTER);
+	y = xmlnode_insert_tag(xmlnode_get_tag(x, "query"), "item");
+	xmlnode_put_attrib(y, "jid", realwho);
+	xmlnode_put_attrib(y, "subscription", "remove");
+	gjab_send(((struct jabber_data *)gc->proto_data)->jc, x);
+
+	g_free(realwho);
+	xmlnode_free(x);
+}
+
+static char **jabber_list_icon(int uc)
+{
+	switch (uc) {
+	case UC_AWAY:
+		return available_away_xpm;
+	case UC_CHAT:
+		return available_chat_xpm;
+	case UC_XA:
+		return available_xa_xpm;
+	case UC_DND:
+		return available_dnd_xpm;
+	default:
+		return available_xpm;
+	}
+}
+
+static void jabber_join_chat(struct gaim_connection *gc, int exch, char *name)
+{
+	xmlnode x;
+	char *realwho;
+	gjconn j = ((struct jabber_data *)gc->proto_data)->jc;
+	GSList *pc = ((struct jabber_data *)gc->proto_data)->pending_chats;
+	struct jabber_chat *jc;
+
+	if (!name)
+		return;
+
+	jc = g_new0(struct jabber_chat, 1);
+	realwho = create_valid_jid(name, DEFAULT_GROUPCHAT, j->user->user);
+	jc->Jid = jid_new(j->p, realwho);
+	jc->gc = gc;
+	debug_printf("%s\n", realwho);
+
+	x = jutil_presnew(0, realwho, NULL);
+	gjab_send(j, x);
+	xmlnode_free(x);
+	g_free(realwho);
+
+	((struct jabber_data *)gc->proto_data)->pending_chats = g_slist_append(pc, jc);
+}
+
+static void jabber_chat_invite(struct gaim_connection *gc, int id, char *message, char *name)
+{
+	xmlnode x, y;
+	GSList *bcs = gc->buddy_chats;
+	struct conversation *b;
+	struct jabber_data *jd = gc->proto_data;
+	gjconn j = jd->jc;
+	struct jabber_chat *jc;
+	char *realwho, *subject;
+
+	if (!name)
+		return;
+
+	/* find which chat we're inviting to */
+	while (bcs) {
+		b = bcs->data;
+		if (id == b->id)
+			break;
+		bcs = bcs->next;
+	}
+	if (!bcs)
+		return;
+
+	bcs = jd->existing_chats;
+	while (bcs) {
+		jc = bcs->data;
+		if (jc->b == b)
+			break;
+		bcs = bcs->next;
+	}
+	if (!bcs)
+		return;
+
+	x = xmlnode_new_tag("message");
+	if (!strchr(name, '@'))
+		realwho = g_strdup_printf("%s@%s", name, j->user->server);
+	else
+		realwho = g_strdup(name);
+	xmlnode_put_attrib(x, "to", realwho);
+	g_free(realwho);
+
+	y = xmlnode_insert_tag(x, "x");
+	xmlnode_put_attrib(y, "xmlns", "jabber:x:conference");
+	subject = g_strdup_printf("%s@%s", jc->Jid->user, jc->Jid->server);
+	xmlnode_put_attrib(y, "jid", subject);
+	g_free(subject);
+
+	if (message && strlen(message)) {
+		char *utf8 = str_to_utf8(message);
+		y = xmlnode_insert_tag(x, "body");
+		xmlnode_insert_cdata(y, utf8, -1);
+		g_free(utf8);
+	}
+
+	gjab_send(((struct jabber_data *)gc->proto_data)->jc, x);
+	xmlnode_free(x);
+}
+
+static void jabber_chat_leave(struct gaim_connection *gc, int id)
+{
+	GSList *bcs = gc->buddy_chats;
+	struct conversation *b;
+	struct jabber_data *jd = gc->proto_data;
+	gjconn j = jd->jc;
+	struct jabber_chat *jc;
+	char *realwho;
+	xmlnode x;
+
+	while (bcs) {
+		b = bcs->data;
+		if (id == b->id)
+			break;
+		bcs = bcs->next;
+	}
+	if (!bcs)
+		return;
+
+	bcs = jd->existing_chats;
+	while (bcs) {
+		jc = bcs->data;
+		if (jc->b == b)
+			break;
+		bcs = bcs->next;
+	}
+	if (!bcs)
+		return;
+
+	realwho = g_strdup_printf("%s@%s", jc->Jid->user, jc->Jid->server);
+	x = jutil_presnew(0, realwho, NULL);
+	g_free(realwho);
+	xmlnode_put_attrib(x, "type", "unavailable");
+	gjab_send(j, x);
+	xmlnode_free(x);
+	jc->b = NULL;
+}
+
+static void jabber_chat_send(struct gaim_connection *gc, int id, char *message)
+{
+	GSList *bcs = gc->buddy_chats;
+	struct conversation *b;
+	struct jabber_data *jd = gc->proto_data;
+	xmlnode x, y;
+	struct jabber_chat *jc;
+	char *chatname;
+
+	while (bcs) {
+		b = bcs->data;
+		if (id == b->id)
+			break;
+		bcs = bcs->next;
+	}
+	if (!bcs)
+		return;
+
+	bcs = jd->existing_chats;
+	while (bcs) {
+		jc = bcs->data;
+		if (jc->b == b)
+			break;
+		bcs = bcs->next;
+	}
+	if (!bcs)
+		return;
+
+	x = xmlnode_new_tag("message");
+	xmlnode_put_attrib(x, "from", jid_full(jc->Jid));
+	chatname = g_strdup_printf("%s@%s", jc->Jid->user, jc->Jid->server);
+	xmlnode_put_attrib(x, "to", chatname);
+	g_free(chatname);
+	xmlnode_put_attrib(x, "type", "groupchat");
+
+	if (message && strlen(message)) {
+		char *utf8 = str_to_utf8(message);
+		y = xmlnode_insert_tag(x, "body");
+		xmlnode_insert_cdata(y, utf8, -1);
+		g_free(utf8);
+	}
+
+	gjab_send(((struct jabber_data *)gc->proto_data)->jc, x);
+	xmlnode_free(x);
+}
+
+static void jabber_chat_set_topic(struct gaim_connection *gc, int id, char *topic)
+{
+	GSList *bcs = gc->buddy_chats;
+	struct conversation *b;
+	struct jabber_data *jd = gc->proto_data;
+	xmlnode x, y;
+	struct jabber_chat *jc;
+	char *chatname;
+	char buf[8192];
+
+	while (bcs) {
+		b = bcs->data;
+		if (id == b->id)
+			break;
+		bcs = bcs->next;
+	}
+	if (!bcs)
+		return;
+
+	bcs = jd->existing_chats;
+	while (bcs) {
+		jc = bcs->data;
+		if (jc->b == b)
+			break;
+		bcs = bcs->next;
+	}
+	if (!bcs)
+		return;
+
+	x = xmlnode_new_tag("message");
+	xmlnode_put_attrib(x, "from", jid_full(jc->Jid));
+	chatname = g_strdup_printf("%s@%s", jc->Jid->user, jc->Jid->server);
+	xmlnode_put_attrib(x, "to", chatname);
+	g_free(chatname);
+	xmlnode_put_attrib(x, "type", "groupchat");
+
+	if (topic && strlen(topic)) {
+		char *utf8 = str_to_utf8(topic);
+		y = xmlnode_insert_tag(x, "subject");
+		xmlnode_insert_cdata(y, utf8, -1);
+		y = xmlnode_insert_tag(x, "body");
+		g_snprintf(buf, sizeof(buf), "/me has changed the subject to: %s", utf8);
+		xmlnode_insert_cdata(y, buf, -1);
+		g_free(utf8);
+	}
+
+	gjab_send(((struct jabber_data *)gc->proto_data)->jc, x);
+	xmlnode_free(x);
+}
+
+static void jabber_chat_whisper(struct gaim_connection *gc, int id, char *who, char *message)
+{
+	GSList *bcs = gc->buddy_chats;
+	struct conversation *b;
+	struct jabber_data *jd = gc->proto_data;
+	xmlnode x, y;
+	struct jabber_chat *jc;
+	char *chatname;
+
+	while (bcs) {
+		b = bcs->data;
+		if (id == b->id)
+			break;
+		bcs = bcs->next;
+	}
+	if (!bcs)
+		return;
+
+	bcs = jd->existing_chats;
+	while (bcs) {
+		jc = bcs->data;
+		if (jc->b == b)
+			break;
+		bcs = bcs->next;
+	}
+	if (!bcs)
+		return;
+
+	x = xmlnode_new_tag("message");
+	xmlnode_put_attrib(x, "from", jid_full(jc->Jid));
+	chatname = g_strdup_printf("%s@%s/%s", jc->Jid->user, jc->Jid->server, who);
+	xmlnode_put_attrib(x, "to", chatname);
+	g_free(chatname);
+	xmlnode_put_attrib(x, "type", "normal");
+
+	if (message && strlen(message)) {
+		char *utf8 = str_to_utf8(message);
+		y = xmlnode_insert_tag(x, "body");
+		xmlnode_insert_cdata(y, utf8, -1);
+		g_free(utf8);
+	}
+
+	gjab_send(((struct jabber_data *)gc->proto_data)->jc, x);
+	xmlnode_free(x);
+}
+
+static GtkWidget *newname = NULL;
+static GtkWidget *newpass1 = NULL;
+static GtkWidget *newpass2 = NULL;
+static GtkWidget *newserv = NULL;
+static jconn regjconn = NULL;
+static int reginpa = 0;
+
+static void newdes()
+{
+	newname = newpass1 = newpass2 = newserv = NULL;
+}
+
+static void jabber_draw_new_user(GtkWidget *box)
+{
+	GtkWidget *hbox;
+	GtkWidget *label;
+
+	if (newname)
+		return;
+
+	label = gtk_label_new("Enter your name, password, and server to register on. If you "
+				"already have a Jabber account and do not need to register one, "
+				"use the Account Editor to add it to your list of accounts.");
+	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+	gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 5);
+	gtk_widget_show(label);
+
+	hbox = gtk_hbox_new(FALSE, 5);
+	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 5);
+	gtk_widget_show(hbox);
+
+	label = gtk_label_new("Username:");
+	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
+	gtk_widget_show(label);
+
+	newname = gtk_entry_new();
+	gtk_box_pack_end(GTK_BOX(hbox), newname, FALSE, FALSE, 5);
+	gtk_signal_connect(GTK_OBJECT(newname), "destroy", GTK_SIGNAL_FUNC(newdes), NULL);
+	gtk_widget_show(newname);
+
+	hbox = gtk_hbox_new(FALSE, 5);
+	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 5);
+	gtk_widget_show(hbox);
+
+	label = gtk_label_new("Password:");
+	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
+	gtk_widget_show(label);
+
+	newpass1 = gtk_entry_new();
+	gtk_box_pack_end(GTK_BOX(hbox), newpass1, FALSE, FALSE, 5);
+	gtk_entry_set_visibility(GTK_ENTRY(newpass1), FALSE);
+	gtk_widget_show(newpass1);
+
+	hbox = gtk_hbox_new(FALSE, 5);
+	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 5);
+	gtk_widget_show(hbox);
+
+	label = gtk_label_new("Confirm:");
+	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
+	gtk_widget_show(label);
+
+	newpass2 = gtk_entry_new();
+	gtk_box_pack_end(GTK_BOX(hbox), newpass2, FALSE, FALSE, 5);
+	gtk_entry_set_visibility(GTK_ENTRY(newpass2), FALSE);
+	gtk_widget_show(newpass2);
+
+	hbox = gtk_hbox_new(FALSE, 5);
+	gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 5);
+	gtk_widget_show(hbox);
+
+	label = gtk_label_new("Server:");
+	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
+	gtk_widget_show(label);
+
+	newserv = gtk_entry_new();
+	gtk_entry_set_text(GTK_ENTRY(newserv), "jabber.org");
+	gtk_box_pack_end(GTK_BOX(hbox), newserv, FALSE, FALSE, 5);
+	gtk_widget_show(newserv);
+}
+
+static void regstate(jconn j, int state)
+{
+	static int catch = 0;
+	switch (state) {
+		case JCONN_STATE_OFF:
+			gdk_input_remove(reginpa);
+			reginpa = 0;
+			jab_delete(j);
+			break;
+		case JCONN_STATE_CONNECTED:
+			break;
+		case JCONN_STATE_ON:
+			if (catch)
+				break;
+			catch = 1;
+			jab_reg(regjconn);
+			catch = 0;
+			break;
+		case JCONN_STATE_AUTH:
+			break;
+		default:
+			break;
+	}
+}
+
+static void regpacket(jconn j, jpacket p)
+{
+	static int here = 0;
+	switch (p->type) {
+		case JPACKET_MESSAGE:
+			break;
+		case JPACKET_PRESENCE:
+			break;
+		case JPACKET_IQ:
+			if (jpacket_subtype(p) == JPACKET__RESULT) {
+				xmlnode x, y, z;
+				char *user, *id;
+
+				if (here == 2) {
+					struct aim_user *u;
+					user = g_strdup(jid_full(j->user));
+					regjconn = NULL;
+					here = 0;
+					u = new_user(user, PROTO_JABBER, OPT_USR_REM_PASS);
+					g_free(user);
+					g_snprintf(u->password, sizeof(u->password), "%s", j->pass);
+					save_prefs();
+					xmlnode_free(p->x);
+					do_error_dialog("Registration successful! Your account has been"
+							" added to the Account Editor.", "Jabber "
+							"Registration");
+					gtk_entry_set_text(GTK_ENTRY(newname), "");
+					gtk_entry_set_text(GTK_ENTRY(newpass1), "");
+					gtk_entry_set_text(GTK_ENTRY(newpass2), "");
+					return;
+				} else if (here == 1) {
+					x = jutil_iqnew(JPACKET__SET, NS_AUTH);
+					here = 2;
+				} else { /* here == 0 */
+					here = 1;
+					x = jutil_iqnew(JPACKET__GET, NS_AUTH);
+				}
+
+				id = jab_getid(j);
+				xmlnode_put_attrib(x, "id", id);
+				y = xmlnode_get_tag(x, "query");
+
+				user = j->user->user;
+				if (user)
+				{
+					z = xmlnode_insert_tag(y, "username");
+					xmlnode_insert_cdata(z, user, -1);
+				}
+
+				if (here == 2) {
+					z = xmlnode_insert_tag(y, "resource");
+					xmlnode_insert_cdata(z, j->user->resource, -1);
+					z = xmlnode_insert_tag(y, "password");
+					xmlnode_insert_cdata(z, j->pass, -1);
+				}
+
+				jab_send(j, x);
+				xmlnode_free(x);
+			} else if (jpacket_subtype(p) == JPACKET__ERROR) {
+				xmlnode x = xmlnode_get_tag(p->x, "error");
+				if (x) {
+					char buf[8192];
+					g_snprintf(buf, sizeof(buf), "Registration failed: %d %s",
+						     atoi(xmlnode_get_attrib(x, "code")),
+						     xmlnode_get_data(xmlnode_get_firstchild(x)));
+					do_error_dialog(buf, "Jabber Registration");
+				} else {
+					do_error_dialog("Registration failed", "Jabber Registration");
+				}
+				regjconn = NULL;
+				xmlnode_free(p->x);
+				here = 0;
+				return;
+			}
+			break;
+		case JPACKET_S10N:
+			break;
+		default:
+			break;
+	}
+
+	xmlnode_free(p->x);
+}
+
+static void regjcall(gpointer data, gint source, GdkInputCondition cond)
+{
+	gjab_recv((gjconn)regjconn);
+}
+
+static void jabber_do_new_user()
+{
+	const char *name, *pass1, *pass2, *serv;
+	char *tmp;
+	char *user;
+
+	if (!newname || regjconn)
+		return;
+
+	pass1 = gtk_entry_get_text(GTK_ENTRY(newpass1));
+	pass2 = gtk_entry_get_text(GTK_ENTRY(newpass2));
+	if (pass1[0] == 0 || pass2[0] == 0) {
+		do_error_dialog("Please enter the same valid password in both password entry boxes",
+				"Registration error");
+		return;
+	}
+	if (strcmp(pass1, pass2)) {
+		do_error_dialog("Mismatched passwords, please verify that both passwords are the same",
+				"Registration error");
+		return;
+	}
+	name = gtk_entry_get_text(GTK_ENTRY(newname));
+	serv = gtk_entry_get_text(GTK_ENTRY(newserv));
+	if (name[0] == 0 || serv[0] == 0) {
+		do_error_dialog("Please enter a valid username and server", "Registration error");
+		return;
+	}
+
+	user = g_strdup_printf("%s@%s/GAIM", name, serv);
+	tmp = g_strdup(pass1);
+	regjconn = jab_new(user, tmp);
+	g_free(tmp);
+	g_free(user);
+
+	jab_state_handler(regjconn, regstate);
+	jab_packet_handler(regjconn, regpacket);
+
+	jab_start(regjconn);
+	reginpa = gdk_input_add(jab_getfd(regjconn), GDK_INPUT_READ, regjcall, NULL);
+}
+
+static char *jabber_normalize(const char *s)
+{
+	static char buf[BUF_LEN];
+	char *t, *u;
+	int x = 0;
+
+	g_return_val_if_fail((s != NULL), NULL);
+
+	u = t = g_strdup(s);
+
+	g_strdown(t);
+
+	while (*t && (x < BUF_LEN - 1)) {
+		if (*t != ' ')
+			buf[x++] = *t;
+		t++;
+	}
+	buf[x] = '\0';
+	g_free(u);
+
+	if (!strchr(buf, '@')) {
+		strcat(buf, "@jabber.org"); /* this isn't always right, but eh */
+	} else if ((u = strchr(strchr(buf, '@'), '/')) != NULL) {
+		*u = '\0';
+	}
+
+	return buf;
+}
+
+static void jabber_get_info(struct gaim_connection *gc, char *who) {
+	xmlnode x;
+	char *id;
+	struct jabber_data *jd = gc->proto_data;
+	gjconn j = jd->jc;
+
+	x = jutil_iqnew(JPACKET__GET, NS_VCARD);
+	xmlnode_put_attrib(x, "to", who);
+	id = gjab_getid(j);
+	xmlnode_put_attrib(x, "id", id);
+
+	gjab_send(j, x);
+
+	xmlnode_free(x);
+	
+}
+
+static void jabber_info(GtkObject *obj, char *who) {
+   	serv_get_info(gtk_object_get_user_data(obj), who);
+}
+
+static void jabber_buddy_menu(GtkWidget *menu, struct gaim_connection *gc, char *who) {
+	GtkWidget *button;
+	
+	button = gtk_menu_item_new_with_label(_("Get Info"));
+	gtk_signal_connect(GTK_OBJECT(button), "activate",
+	      		   GTK_SIGNAL_FUNC(jabber_info), who);
+	gtk_object_set_user_data(GTK_OBJECT(button), gc);
+	gtk_menu_append(GTK_MENU(menu), button);
+	gtk_widget_show(button);
+}
+
+static GList *jabber_away_states() {
+	GList *m = NULL;
+
+	m = g_list_append(m, "Online");
+	m = g_list_append(m, "Chatty");
+	m = g_list_append(m, "Away");
+	m = g_list_append(m, "Extended Away");
+	m = g_list_append(m, "Do Not Disturb");
+
+	return m;
+}
+
+static void jabber_set_away(struct gaim_connection *gc, char *state, char *message)
+{
+	xmlnode x, y;
+	struct jabber_data *jd = gc->proto_data;
+	gjconn j = jd->jc;
+
+	gc->away = NULL; /* never send an auto-response */
+
+	x = xmlnode_new_tag("presence");
+
+	if (!strcmp(state, GAIM_AWAY_CUSTOM)) {
+		/* oh goody. Gaim is telling us what to do. */
+		if (message) {
+			/* Gaim wants us to be away */
+			y = xmlnode_insert_tag(x, "show");
+			xmlnode_insert_cdata(y, "away", -1);
+			y = xmlnode_insert_tag(x, "status");
+			xmlnode_insert_cdata(y, message, -1);
+			gc->away = "";
+		} else {
+			/* Gaim wants us to not be away */
+			/* but for Jabber, we can just send presence with no other information. */
+		}
+	} else {
+		/* state is one of our own strings. it won't be NULL. */
+		if (!strcmp(state, "Online")) {
+			/* once again, we don't have to put anything here */
+		} else if (!strcmp(state, "Chatty")) {
+			y = xmlnode_insert_tag(x, "show");
+			xmlnode_insert_cdata(y, "chat", -1);
+		} else if (!strcmp(state, "Away")) {
+			y = xmlnode_insert_tag(x, "show");
+			xmlnode_insert_cdata(y, "away", -1);
+			gc->away = "";
+		} else if (!strcmp(state, "Extended Away")) {
+			y = xmlnode_insert_tag(x, "show");
+			xmlnode_insert_cdata(y, "xa", -1);
+			gc->away = "";
+		} else if (!strcmp(state, "Do Not Disturb")) {
+			y = xmlnode_insert_tag(x, "show");
+			xmlnode_insert_cdata(y, "dnd", -1);
+			gc->away = "";
+		}
+	}
+
+	gjab_send(j, x);
+	xmlnode_free(x);
+}
+
+static void jabber_set_idle(struct gaim_connection *gc, int idle) {
+	struct jabber_data *jd = (struct jabber_data *)gc->proto_data;
+	debug_printf("jabber_set_idle: setting idle %i\n", idle);
+   	jd->idle = idle ? time(NULL) - idle : idle;
+}
+
+static void jabber_keepalive(struct gaim_connection *gc) {
+	struct jabber_data *jd = (struct jabber_data *)gc->proto_data;
+	gjab_send_raw(jd->jc, "  \t  ");
+}
+
+static void jabber_print_option(GtkEntry *entry, struct aim_user *user)
+{
+	int entrynum;
+
+	entrynum = (int)gtk_object_get_user_data(GTK_OBJECT(entry));
+
+	if (entrynum == USEROPT_PORT) {
+		g_snprintf(user->proto_opt[USEROPT_PORT],
+			   sizeof(user->proto_opt[USEROPT_PORT]), "%s", gtk_entry_get_text(entry));
+	}
+}
+
+static void jabber_user_opts(GtkWidget *book, struct aim_user *user)
+{
+	GtkWidget *vbox;
+	GtkWidget *hbox;
+	GtkWidget *label;
+	GtkWidget *entry;
+
+	vbox = gtk_vbox_new(FALSE, 5);
+	gtk_container_set_border_width(GTK_CONTAINER(vbox), 5);
+	gtk_notebook_append_page(GTK_NOTEBOOK(book), vbox, gtk_label_new("Jabber Options"));
+	gtk_widget_show(vbox);
+
+	hbox = gtk_hbox_new(FALSE, 5);
+	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+	gtk_widget_show(hbox);
+
+	label = gtk_label_new("Port:");
+	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+	gtk_widget_show(label);
+
+	entry = gtk_entry_new();
+	gtk_box_pack_end(GTK_BOX(hbox), entry, FALSE, FALSE, 0);
+	gtk_object_set_user_data(GTK_OBJECT(entry), (void *)USEROPT_PORT);
+	gtk_signal_connect(GTK_OBJECT(entry), "changed", GTK_SIGNAL_FUNC(jabber_print_option), user);
+	if (isdigit(user->proto_opt[USEROPT_PORT][0])) {
+		debug_printf("setting text %s\n", user->proto_opt[USEROPT_PORT]);
+		gtk_entry_set_text(GTK_ENTRY(entry), user->proto_opt[USEROPT_PORT]);
+	} else
+		gtk_entry_set_text(GTK_ENTRY(entry), "5222");
+
+	label = gtk_label_new("To set the server, make your username be user@server.org.");
+	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+	gtk_widget_show(label);
+
+	gtk_widget_show(entry);
+}
+
+static struct prpl *my_protocol = NULL;
+
+void jabber_init(struct prpl *ret)
+{
+	/* the NULL's aren't required but they're nice to have */
+	ret->protocol = PROTO_JABBER;
+	ret->options = OPT_PROTO_UNIQUE_CHATNAME | OPT_PROTO_CHAT_TOPIC;
+	ret->name = jabber_name;
+	ret->list_icon = jabber_list_icon;
+	ret->away_states = jabber_away_states;
+	ret->buddy_menu = jabber_buddy_menu;
+	ret->user_opts = jabber_user_opts;
+	ret->draw_new_user = jabber_draw_new_user;
+	ret->do_new_user = jabber_do_new_user;
+	ret->login = jabber_login;
+	ret->close = jabber_close;
+	ret->send_im = jabber_send_im;
+	ret->set_info = NULL;
+	ret->get_info = jabber_get_info;
+	ret->set_away = jabber_set_away;
+	ret->get_away_msg = NULL;
+	ret->set_dir = NULL;
+	ret->get_dir = NULL;
+	ret->dir_search = NULL;
+	ret->set_idle = jabber_set_idle;
+	ret->change_passwd = NULL;
+	ret->add_buddy = jabber_add_buddy;
+	ret->add_buddies = NULL;
+	ret->remove_buddy = jabber_remove_buddy;
+	ret->add_permit = NULL;
+	ret->add_deny = NULL;
+	ret->rem_permit = NULL;
+	ret->rem_deny = NULL;
+	ret->set_permit_deny = NULL;
+	ret->warn = NULL;
+	ret->accept_chat = NULL;
+	ret->join_chat = jabber_join_chat;
+	ret->chat_invite = jabber_chat_invite;
+	ret->chat_leave = jabber_chat_leave;
+	ret->chat_whisper = jabber_chat_whisper;
+	ret->chat_set_topic = jabber_chat_set_topic;
+	ret->chat_send = jabber_chat_send;
+	ret->keepalive = jabber_keepalive;
+	ret->normalize = jabber_normalize;
+
+	my_protocol = ret;
+}
+
+#ifndef STATIC
+
+char *gaim_plugin_init(GModule *handle)
+{
+	load_protocol(jabber_init, sizeof(struct prpl));
+	return NULL;
+}
+
+void gaim_plugin_remove()
+{
+	struct prpl *p = find_prpl(PROTO_JABBER);
+	if (p == my_protocol)
+		unload_protocol(p);
+}
+
+char *name()
+{
+	return "Jabber";
+}
+
+char *description()
+{
+	return "Allows gaim to use the Jabber protocol";
+}
+
+#endif