changeset 25770:af95409021e0

propagate from branch 'im.pidgin.pidgin' (head c5b982597812ab3c0fd2dbca9be31f173fda67bb) to branch 'im.pidgin.cpw.malu.xmpp.ibb_ft' (head b14d9102960332bef86e0e45b9e3e977ea509a72)
author Marcus Lundblad <ml@update.uu.se>
date Thu, 22 Jan 2009 20:37:54 +0000
parents 5456120de480 (diff) c814641afcf2 (current diff)
children f4b9d56dba80
files
diffstat 9 files changed, 1133 insertions(+), 57 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/protocols/jabber/Makefile.am	Thu Jan 22 01:04:56 2009 +0000
+++ b/libpurple/protocols/jabber/Makefile.am	Thu Jan 22 20:37:54 2009 +0000
@@ -17,6 +17,8 @@
 			  disco.h \
 			  google.c \
 			  google.h \
+			  ibb.c \
+			  ibb.h \
 			  iq.c \
 			  iq.h \
 			  jabber.c \
--- a/libpurple/protocols/jabber/Makefile.mingw	Thu Jan 22 01:04:56 2009 +0000
+++ b/libpurple/protocols/jabber/Makefile.mingw	Thu Jan 22 20:37:54 2009 +0000
@@ -51,6 +51,7 @@
 			data.c \
 			disco.c \
 			google.c \
+			ibb.c \
 			iq.c \
 			jabber.c \
 			jutil.c \
--- a/libpurple/protocols/jabber/disco.c	Thu Jan 22 01:04:56 2009 +0000
+++ b/libpurple/protocols/jabber/disco.c	Thu Jan 22 20:37:54 2009 +0000
@@ -132,9 +132,7 @@
 			SUPPORT_FEATURE("http://jabber.org/protocol/bytestreams")
 			SUPPORT_FEATURE("http://jabber.org/protocol/disco#info")
 			SUPPORT_FEATURE("http://jabber.org/protocol/disco#items")
-#if 0
-				SUPPORT_FEATURE("http://jabber.org/protocol/ibb")
-#endif
+			SUPPORT_FEATURE("http://jabber.org/protocol/ibb");
 			SUPPORT_FEATURE("http://jabber.org/protocol/muc")
 			SUPPORT_FEATURE("http://jabber.org/protocol/muc#user")
 			SUPPORT_FEATURE("http://jabber.org/protocol/si")
@@ -273,6 +271,10 @@
 				else if(!strcmp(var, "http://jabber.org/protocol/commands")) {
 					capabilities |= JABBER_CAP_ADHOC;
 				}
+				else if(!strcmp(var, "http://jabber.org/protocol/ibb")) {
+					purple_debug_info("jabber", "remote supports IBB\n");
+					capabilities |= JABBER_CAP_IBB;
+				}
 			}
 		}
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/ibb.c	Thu Jan 22 20:37:54 2009 +0000
@@ -0,0 +1,521 @@
+/*
+ * 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 Library General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301,  USA
+ */
+ 
+#include "internal.h"
+#include "ibb.h"
+#include "debug.h"
+#include "xmlnode.h"
+
+#define JABBER_IBB_SESSION_DEFAULT_BLOCK_SIZE 4096
+
+static GHashTable *jabber_ibb_sessions = NULL;
+static GList *open_handlers = NULL;
+
+JabberIBBSession *
+jabber_ibb_session_create(JabberStream *js, const gchar *sid, const gchar *who, 
+	gpointer user_data)
+{
+	JabberIBBSession *sess = g_new0(JabberIBBSession, 1);
+	sess->js = js;
+	if (sid) {
+		sess->sid = g_strdup(sid);
+	} else {
+		sess->sid = jabber_get_next_id(js);
+	}
+	sess->who = g_strdup(who);
+	sess->block_size = JABBER_IBB_SESSION_DEFAULT_BLOCK_SIZE;
+	sess->state = JABBER_IBB_SESSION_NOT_OPENED;
+	sess->user_data = user_data;
+	
+	g_hash_table_insert(jabber_ibb_sessions, sess->sid, sess);
+	
+	return sess;
+}
+
+JabberIBBSession *
+jabber_ibb_session_create_from_xmlnode(JabberStream *js, xmlnode *packet,
+	gpointer user_data)
+{
+	JabberIBBSession *sess = NULL;
+	xmlnode *open = xmlnode_get_child_with_namespace(packet, "open",
+		XEP_0047_NAMESPACE);
+	const gchar *sid = xmlnode_get_attrib(open, "sid");
+	const gchar *block_size = xmlnode_get_attrib(open, "block-size");
+	
+	if (!open) {
+		return NULL;
+	}
+
+	if (!sid || !block_size) {
+		purple_debug_error("jabber", 
+			"IBB session open tag requires sid and block-size attributes\n");
+		g_free(sess);
+		return NULL;
+	}
+
+	sess = jabber_ibb_session_create(js, sid,
+			xmlnode_get_attrib(packet, "from"), user_data);
+	sess->id = g_strdup(xmlnode_get_attrib(packet, "id"));
+	sess->block_size = atoi(block_size);
+	/* if we create a session from an incoming <open/> request, it means the
+	  session is immediatly open... */
+	sess->state = JABBER_IBB_SESSION_OPENED;
+	
+	return sess;
+}
+
+void
+jabber_ibb_session_destroy(JabberIBBSession *sess)
+{
+	purple_debug_info("jabber", "IBB: destroying session %p %s\n", sess, 
+		sess->sid);
+	
+	if (jabber_ibb_session_get_state(sess) == JABBER_IBB_SESSION_OPENED) {
+		jabber_ibb_session_close(sess);
+	}
+	
+	if (sess->last_iq_id) {
+		purple_debug_info("jabber", "IBB: removing callback for <iq/> %s\n",
+			sess->last_iq_id);
+		jabber_iq_remove_callback_by_id(jabber_ibb_session_get_js(sess), 
+			sess->last_iq_id);
+		g_free(sess->last_iq_id);
+		sess->last_iq_id = NULL;
+	}
+	
+	g_hash_table_remove(jabber_ibb_sessions, sess->sid);
+	g_free(sess->id);
+	g_free(sess->sid);
+	g_free(sess->who);
+	g_free(sess);
+}
+
+const gchar *
+jabber_ibb_session_get_sid(const JabberIBBSession *sess)
+{
+	return sess->sid;
+}
+
+JabberStream *
+jabber_ibb_session_get_js(JabberIBBSession *sess)
+{
+	return sess->js;
+}
+
+const gchar *
+jabber_ibb_session_get_who(const JabberIBBSession *sess)
+{
+	return sess->who;
+}
+
+guint16
+jabber_ibb_session_get_send_seq(const JabberIBBSession *sess)
+{
+	return sess->send_seq;
+}
+
+guint16
+jabber_ibb_session_get_recv_seq(const JabberIBBSession *sess)
+{
+	return sess->recv_seq;
+}
+
+JabberIBBSessionState 
+jabber_ibb_session_get_state(const JabberIBBSession *sess)
+{
+	return sess->state;
+}
+
+gsize
+jabber_ibb_session_get_block_size(const JabberIBBSession *sess)
+{
+	return sess->block_size;
+}
+
+void
+jabber_ibb_session_set_block_size(JabberIBBSession *sess, gsize size)
+{
+	if (jabber_ibb_session_get_state(sess) == JABBER_IBB_SESSION_NOT_OPENED) {
+		sess->block_size = size;
+	} else {
+		purple_debug_error("jabber", 
+			"Can't set block size on an open IBB session\n");
+	}
+}
+
+gpointer
+jabber_ibb_session_get_user_data(JabberIBBSession *sess)
+{
+	return sess->user_data;
+}
+
+void
+jabber_ibb_session_set_opened_callback(JabberIBBSession *sess,
+	JabberIBBOpenedCallback *cb)
+{
+	sess->opened_cb = cb;
+}
+	
+void 
+jabber_ibb_session_set_data_sent_callback(JabberIBBSession *sess,
+	JabberIBBSentCallback *cb)
+{
+	sess->data_sent_cb = cb;
+}
+	
+void 
+jabber_ibb_session_set_closed_callback(JabberIBBSession *sess,
+	JabberIBBClosedCallback *cb)
+{
+	sess->closed_cb = cb;
+}
+	
+void 
+jabber_ibb_session_set_data_received_callback(JabberIBBSession *sess,
+	JabberIBBDataCallback *cb)
+{
+	sess->data_received_cb = cb;
+}
+
+void 
+jabber_ibb_session_set_error_callback(JabberIBBSession *sess, 
+	JabberIBBErrorCallback *cb)
+{
+	sess->error_cb = cb;
+}
+
+static void
+jabber_ibb_session_opened_cb(JabberStream *js, xmlnode *packet, gpointer data)
+{
+	JabberIBBSession *sess = (JabberIBBSession *) data;
+
+	if (strcmp(xmlnode_get_attrib(packet, "type"), "error") == 0) {
+		sess->state = JABBER_IBB_SESSION_ERROR;
+	} else {
+		sess->state = JABBER_IBB_SESSION_OPENED;
+	}
+		
+	if (sess->opened_cb) {
+		sess->opened_cb(sess);
+	}
+}
+
+void
+jabber_ibb_session_open(JabberIBBSession *sess)
+{
+	if (jabber_ibb_session_get_state(sess) != JABBER_IBB_SESSION_NOT_OPENED) {
+		purple_debug_error("jabber", 
+			"jabber_ibb_session called on an already open stream\n");
+	} else {
+		JabberIq *set = jabber_iq_new(sess->js, JABBER_IQ_SET);
+		xmlnode *open = xmlnode_new("open");
+		gchar block_size[10];
+		
+		xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess));
+		xmlnode_set_namespace(open, XEP_0047_NAMESPACE);
+		xmlnode_set_attrib(open, "sid", jabber_ibb_session_get_sid(sess));
+		g_snprintf(block_size, sizeof(block_size), "%" G_GSIZE_FORMAT, 
+			jabber_ibb_session_get_block_size(sess));
+		xmlnode_set_attrib(open, "block-size", block_size);
+		xmlnode_insert_child(set->node, open);
+		
+		jabber_iq_set_callback(set, jabber_ibb_session_opened_cb, sess);
+		
+		jabber_iq_send(set);
+	}
+}
+
+void
+jabber_ibb_session_close(JabberIBBSession *sess)
+{
+	JabberIBBSessionState state = jabber_ibb_session_get_state(sess);
+	
+	if (state != JABBER_IBB_SESSION_OPENED && state != JABBER_IBB_SESSION_ERROR) {
+		purple_debug_error("jabber",
+			"jabber_ibb_session_close called on a session that has not been"
+			"opened\n");
+	} else {
+		JabberIq *set = jabber_iq_new(jabber_ibb_session_get_js(sess),
+			JABBER_IQ_SET);
+		xmlnode *close = xmlnode_new("close");
+		
+		xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess));
+		xmlnode_set_namespace(close, XEP_0047_NAMESPACE);
+		xmlnode_set_attrib(close, "sid", jabber_ibb_session_get_sid(sess));
+		xmlnode_insert_child(set->node, close);
+		jabber_iq_send(set);
+		sess->state = JABBER_IBB_SESSION_CLOSED;
+	}
+}
+
+void
+jabber_ibb_session_accept(JabberIBBSession *sess)
+{
+	JabberIq *result = jabber_iq_new(jabber_ibb_session_get_js(sess),
+		JABBER_IQ_RESULT);
+	
+	xmlnode_set_attrib(result->node, "to", jabber_ibb_session_get_who(sess));
+	jabber_iq_set_id(result, sess->id);
+	jabber_iq_send(result);
+	sess->state = JABBER_IBB_SESSION_OPENED;
+}
+
+static void
+jabber_ibb_session_send_acknowledge_cb(JabberStream *js, xmlnode *packet, gpointer data)
+{
+	JabberIBBSession *sess = (JabberIBBSession *) data;
+	xmlnode *error = xmlnode_get_child(packet, "error");
+	
+	if (sess) {
+		/* reset callback */
+		if (sess->last_iq_id) {
+			g_free(sess->last_iq_id);
+			sess->last_iq_id = NULL;
+		}
+		
+		if (error) {
+			jabber_ibb_session_close(sess);
+			sess->state = JABBER_IBB_SESSION_ERROR;
+		
+			if (sess->error_cb) {
+				sess->error_cb(sess);
+			}
+		} else {
+			if (sess->data_sent_cb) {
+				sess->data_sent_cb(sess);
+			}
+		}
+	} else {
+		/* the session has gone away, it was probably cancelled */
+		purple_debug_info("jabber", 
+			"got response from send data, but IBB session is no longer active\n");
+	}
+}
+
+void
+jabber_ibb_session_send_data(JabberIBBSession *sess, gconstpointer data,
+                             gsize size)
+{
+	JabberIBBSessionState state = jabber_ibb_session_get_state(sess);
+	
+	purple_debug_info("jabber", "sending data block of %" G_GSIZE_FORMAT " bytes on IBB stream\n",
+		size);
+	
+	if (state != JABBER_IBB_SESSION_OPENED) {
+		purple_debug_error("jabber", 
+			"trying to send data on a non-open IBB session\n");
+	} else if (size > jabber_ibb_session_get_block_size(sess)) {
+		purple_debug_error("jabber", 
+			"trying to send a too large packet in the IBB session\n");
+	} else {
+		JabberIq *set = jabber_iq_new(jabber_ibb_session_get_js(sess), 
+			JABBER_IQ_SET);
+		xmlnode *data_element = xmlnode_new("data");
+		char *base64 = purple_base64_encode(data, size);
+		char seq[10];
+		g_snprintf(seq, sizeof(seq), "%u", jabber_ibb_session_get_send_seq(sess));
+		
+		xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess));
+		xmlnode_set_namespace(data_element, XEP_0047_NAMESPACE);
+		xmlnode_set_attrib(data_element, "sid", jabber_ibb_session_get_sid(sess));
+		xmlnode_set_attrib(data_element, "seq", seq);
+		xmlnode_insert_data(data_element, base64, -1);
+		
+		xmlnode_insert_child(set->node, data_element);
+	
+		purple_debug_info("jabber", 
+			"IBB: setting send <iq/> callback for session %p %s\n", sess,
+			sess->sid);
+		jabber_iq_set_callback(set, jabber_ibb_session_send_acknowledge_cb, sess);
+		sess->last_iq_id = g_strdup(xmlnode_get_attrib(set->node, "id"));
+		purple_debug_info("jabber", "IBB: set sess->last_iq_id: %s\n",
+			sess->last_iq_id);
+		jabber_iq_send(set);
+		
+		g_free(base64);
+		(sess->send_seq)++;
+	}
+}
+
+static void
+jabber_ibb_send_error_response(JabberStream *js, xmlnode *packet)
+{
+	JabberIq *result = jabber_iq_new(js, JABBER_IQ_ERROR);
+	xmlnode *error = xmlnode_new("error");
+	xmlnode *item_not_found = xmlnode_new("item-not-found");
+	
+	xmlnode_set_namespace(item_not_found, 
+		"urn:ietf:params:xml:ns:xmpp-stanzas");
+	xmlnode_set_attrib(error, "code", "440");
+	xmlnode_set_attrib(error, "type", "cancel");
+	jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id"));
+	xmlnode_set_attrib(result->node, "to", 
+		xmlnode_get_attrib(packet, "from"));
+	xmlnode_insert_child(error, item_not_found);
+	xmlnode_insert_child(result->node, error);
+	
+	jabber_iq_send(result);
+}
+
+void
+jabber_ibb_parse(JabberStream *js, xmlnode *packet)
+{
+	xmlnode *data = xmlnode_get_child_with_namespace(packet, "data",
+		XEP_0047_NAMESPACE);
+	xmlnode *close = xmlnode_get_child_with_namespace(packet, "close", 
+		XEP_0047_NAMESPACE);
+	xmlnode *open = xmlnode_get_child_with_namespace(packet, "open",
+		XEP_0047_NAMESPACE);
+	const gchar *sid = 
+		data ? xmlnode_get_attrib(data, "sid") : 
+			close ? xmlnode_get_attrib(close, "sid") : NULL;
+	JabberIBBSession *sess = 
+		sid ? g_hash_table_lookup(jabber_ibb_sessions, sid) : NULL;
+	const gchar *who = xmlnode_get_attrib(packet, "from");
+	
+	if (sess) {
+		
+		if (strcmp(who, jabber_ibb_session_get_who(sess)) != 0) {
+			/* the iq comes from a different JID than the remote JID of the
+			  session, ignore it */
+			purple_debug_error("jabber", 
+				"Got IBB iq from wrong JID, ignoring\n");
+		} else if (data) {
+			const gchar *seq_attr = xmlnode_get_attrib(data, "seq");
+			guint16 seq = (seq_attr ? atoi(seq_attr) : 0);
+
+			/* reject the data, and set the session in error if we get an
+			  out-of-order packet */
+			if (seq_attr && seq == jabber_ibb_session_get_recv_seq(sess)) {
+				/* sequence # is the expected... */
+				JabberIq *result = jabber_iq_new(js, JABBER_IQ_RESULT);
+				
+				jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id"));
+				xmlnode_set_attrib(result->node, "to", 
+					xmlnode_get_attrib(packet, "from"));
+				
+				if (sess->data_received_cb) {
+					gchar *base64 = xmlnode_get_data(data);
+					gsize size;
+					gpointer rawdata = purple_base64_decode(base64, &size);
+					
+					g_free(base64);
+					
+					if (rawdata) {
+						purple_debug_info("jabber", 
+							"got %" G_GSIZE_FORMAT " bytes of data on IBB stream\n",
+							size);
+						if (size > jabber_ibb_session_get_block_size(sess)) {
+							purple_debug_error("jabber",
+								"IBB: received a too large packet\n");
+							if (sess->error_cb)
+								sess->error_cb(sess);
+							g_free(rawdata);
+							return;
+						} else {
+							purple_debug_info("jabber", 
+								"calling IBB callback for received data\n");
+							sess->data_received_cb(sess, rawdata, size);
+						}
+						g_free(rawdata);
+					} else {
+						purple_debug_error("jabber", 
+							"IBB: invalid BASE64 data received\n");
+						if (sess->error_cb)
+							sess->error_cb(sess);
+						return;
+
+					}
+				}
+				
+				(sess->recv_seq)++;
+				jabber_iq_send(result);
+				
+			} else {
+				purple_debug_error("jabber", 
+					"Received an out-of-order/invalid IBB packet\n");
+				sess->state = JABBER_IBB_SESSION_ERROR;
+				
+				if (sess->error_cb) {
+					sess->error_cb(sess);
+				}
+			}
+		} else if (close) {
+			sess->state = JABBER_IBB_SESSION_CLOSED;
+			purple_debug_info("jabber", "IBB: received close\n");
+			
+			if (sess->closed_cb) {
+				purple_debug_info("jabber", "IBB: calling closed handler\n");
+				sess->closed_cb(sess);
+			}
+		
+		} else {
+			/* this should never happen */
+			purple_debug_error("jabber", "Received bogus iq for IBB session\n");
+		}
+	} else if (open) {
+		JabberIq *result;
+		const GList *iterator;
+		
+		/* run all open handlers registered until one returns true */
+		for (iterator = open_handlers ; iterator ; 
+			 iterator = g_list_next(iterator)) {
+			JabberIBBOpenHandler *handler = iterator->data;
+
+			if (handler(js, packet)) {
+				result = jabber_iq_new(js, JABBER_IQ_RESULT);
+				xmlnode_set_attrib(result->node, "to", 
+					xmlnode_get_attrib(packet, "from"));
+				jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id"));
+				jabber_iq_send(result);
+				return;
+			}
+		}
+		/* no open callback returned success, reject */
+		jabber_ibb_send_error_response(js, packet);
+	} else {
+		/* send error reply */
+		jabber_ibb_send_error_response(js, packet);
+	}
+}
+
+void
+jabber_ibb_register_open_handler(JabberIBBOpenHandler *cb)
+{
+	open_handlers = g_list_append(open_handlers, cb);
+}
+
+void
+jabber_ibb_unregister_open_handler(JabberIBBOpenHandler *cb)
+{
+	open_handlers = g_list_remove(open_handlers, cb);
+}
+
+void
+jabber_ibb_init(void)
+{
+	jabber_ibb_sessions = g_hash_table_new(g_str_hash, g_str_equal);
+}
+
+void
+jabber_ibb_uninit(void)
+{
+	g_hash_table_destroy(jabber_ibb_sessions);
+	g_list_free(open_handlers);
+	jabber_ibb_sessions = NULL;
+	open_handlers = NULL;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/ibb.h	Thu Jan 22 20:37:54 2009 +0000
@@ -0,0 +1,119 @@
+/*
+ * 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 Library General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301,  USA
+ */
+ 
+#ifndef _PURPLE_JABBER_IBB_H_
+#define _PURPLE_JABBER_IBB_H_
+
+#include "jabber.h"
+#include "iq.h"
+
+#define XEP_0047_NAMESPACE "http://jabber.org/protocol/ibb"
+
+typedef struct _JabberIBBSession JabberIBBSession;
+
+typedef void 
+(JabberIBBDataCallback)(JabberIBBSession *, const gpointer data, gsize size);
+
+typedef void (JabberIBBOpenedCallback)(JabberIBBSession *);
+typedef void (JabberIBBClosedCallback)(JabberIBBSession *);
+typedef void (JabberIBBErrorCallback)(JabberIBBSession *);
+typedef void (JabberIBBSentCallback)(JabberIBBSession *);
+
+typedef gboolean (JabberIBBOpenHandler)(JabberStream *js, xmlnode *packet);
+
+typedef enum {
+	JABBER_IBB_SESSION_NOT_OPENED,
+	JABBER_IBB_SESSION_OPENED,
+	JABBER_IBB_SESSION_CLOSED,
+	JABBER_IBB_SESSION_ERROR
+} JabberIBBSessionState;
+
+struct _JabberIBBSession {
+	JabberStream *js;
+	gchar *who;
+	gchar *sid;
+	gchar *id;
+	guint16 send_seq;
+	guint16 recv_seq;
+	gsize block_size;
+	
+	/* session state */
+	JabberIBBSessionState state;
+	
+	/* user data (f.ex. a handle to a PurpleXfer) */
+	gpointer user_data;
+	
+	/* callbacks */
+	JabberIBBOpenedCallback *opened_cb;
+	JabberIBBSentCallback *data_sent_cb;
+	JabberIBBClosedCallback *closed_cb;
+	/* callback for receiving data */
+	JabberIBBDataCallback *data_received_cb;
+	JabberIBBErrorCallback *error_cb;
+	
+	/* store the last sent IQ (to permit cancel of callback) */
+	gchar *last_iq_id;
+};
+
+JabberIBBSession *jabber_ibb_session_create(JabberStream *js, const gchar *sid,
+	const gchar *who, gpointer user_data);
+JabberIBBSession *jabber_ibb_session_create_from_xmlnode(JabberStream *js,
+	xmlnode *packet, gpointer user_data);
+
+void jabber_ibb_session_destroy(JabberIBBSession *sess);
+
+void jabber_ibb_session_set_opened_callback(JabberIBBSession *sess,
+	JabberIBBOpenedCallback *cb);
+void jabber_ibb_session_set_data_sent_callback(JabberIBBSession *sess,
+	JabberIBBSentCallback *cb);
+void jabber_ibb_session_set_closed_callback(JabberIBBSession *sess,
+	JabberIBBClosedCallback *cb);
+void jabber_ibb_session_set_data_received_callback(JabberIBBSession *sess,
+	JabberIBBDataCallback *cb);
+void jabber_ibb_session_set_error_callback(JabberIBBSession *sess,
+	JabberIBBErrorCallback *cb);
+
+void jabber_ibb_session_open(JabberIBBSession *sess);
+void jabber_ibb_session_close(JabberIBBSession *sess);
+void jabber_ibb_session_accept(JabberIBBSession *sess);
+void jabber_ibb_session_send_data(JabberIBBSession *sess, gconstpointer data,
+	gsize size);
+
+const gchar *jabber_ibb_session_get_sid(const JabberIBBSession *sess);
+JabberStream *jabber_ibb_session_get_js(JabberIBBSession *sess);
+const gchar *jabber_ibb_session_get_who(const JabberIBBSession *sess);
+
+guint16 jabber_ibb_session_get_send_seq(const JabberIBBSession *sess);
+guint16 jabber_ibb_session_get_recv_seq(const JabberIBBSession *sess);
+
+JabberIBBSessionState jabber_ibb_session_get_state(const JabberIBBSession *sess);
+
+gsize jabber_ibb_session_get_block_size(const JabberIBBSession *sess);
+void jabber_ibb_session_set_block_size(JabberIBBSession *sess, gsize size);
+
+gpointer jabber_ibb_session_get_user_data(JabberIBBSession *sess);
+
+/* handle incoming packet */
+void jabber_ibb_parse(JabberStream *js, xmlnode *packet);
+
+/* add a handler for open session */
+void jabber_ibb_register_open_handler(JabberIBBOpenHandler *cb);
+void jabber_ibb_unregister_open_handler(JabberIBBOpenHandler *cb);
+
+void jabber_ibb_init(void);
+void jabber_ibb_uninit(void);
+
+#endif /* _PURPLE_JABBER_IBB_H_ */
--- a/libpurple/protocols/jabber/iq.c	Thu Jan 22 01:04:56 2009 +0000
+++ b/libpurple/protocols/jabber/iq.c	Thu Jan 22 20:37:54 2009 +0000
@@ -34,6 +34,7 @@
 #include "ping.h"
 #include "adhoccommands.h"
 #include "data.h"
+#include "ibb.h"
 
 #ifdef _WIN32
 #include "utsname.h"
@@ -393,6 +394,13 @@
 		return;
 	}
 
+	if (xmlnode_get_child_with_namespace(packet, "data", XEP_0047_NAMESPACE)
+		|| xmlnode_get_child_with_namespace(packet, "close", XEP_0047_NAMESPACE)
+		|| xmlnode_get_child_with_namespace(packet, "open", XEP_0047_NAMESPACE)) {
+		jabber_ibb_parse(js, packet);
+		return;
+	}
+	
 	/* If we get here, send the default error reply mandated by XMPP-CORE */
 	if(!strcmp(type, "set") || !strcmp(type, "get")) {
 		JabberIq *iq = jabber_iq_new(js, JABBER_IQ_ERROR);
--- a/libpurple/protocols/jabber/libxmpp.c	Thu Jan 22 01:04:56 2009 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Thu Jan 22 20:37:54 2009 +0000
@@ -44,6 +44,7 @@
 #include "usertune.h"
 #include "caps.h"
 #include "data.h"
+#include "ibb.h"
 
 static PurplePluginProtocolInfo prpl_info =
 {
@@ -150,6 +151,8 @@
 	purple_signal_unregister(plugin, "jabber-sending-text");
 	
 	jabber_data_uninit();
+	jabber_si_uninit();
+	jabber_ibb_uninit();
 	
 	return TRUE;
 }
@@ -281,12 +284,17 @@
 	
 	jabber_data_init();
 	
+	
+	jabber_ibb_init();
+	jabber_si_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_add_feature("bob", XEP_0231_NAMESPACE,
 					   jabber_custom_smileys_isenabled);
+	jabber_add_feature("ibb", XEP_0047_NAMESPACE, NULL);
 
 	jabber_pep_register_handler("avatar", AVATARNAMESPACEMETA, jabber_buddy_avatar_update_metadata);
 }
--- a/libpurple/protocols/jabber/si.c	Thu Jan 22 01:04:56 2009 +0000
+++ b/libpurple/protocols/jabber/si.c	Thu Jan 22 20:37:54 2009 +0000
@@ -32,6 +32,7 @@
 #include "buddy.h"
 #include "disco.h"
 #include "jabber.h"
+#include "ibb.h"
 #include "iq.h"
 #include "si.h"
 
@@ -63,8 +64,15 @@
 	size_t rxlen;
 	gsize rxmaxlen;
 	int local_streamhost_fd;
+	
+	JabberIBBSession *ibb_session;
+	guint ibb_timeout_handle;
+	FILE *fp;
 } JabberSIXfer;
 
+/* some forward declarations */
+static void jabber_si_xfer_ibb_send_init(JabberStream *js, PurpleXfer *xfer);
+
 static PurpleXfer*
 jabber_si_xfer_find(JabberStream *js, const char *sid, const char *from)
 {
@@ -178,6 +186,32 @@
 	return FALSE;
 }
 
+static void
+jabber_si_bytestreams_ibb_timeout_remove(JabberSIXfer *jsx)
+{
+	if (jsx->ibb_timeout_handle) {
+		purple_timeout_remove(jsx->ibb_timeout_handle);
+		jsx->ibb_timeout_handle = 0;
+	}
+}
+
+static gboolean
+jabber_si_bytestreams_ibb_timeout_cb(gpointer data)
+{
+	PurpleXfer *xfer = (PurpleXfer *) data;
+	JabberSIXfer *jsx = xfer->data;
+	
+	if (jsx && !jsx->ibb_session) {
+		purple_debug_info("jabber", 
+			"jabber_si_bytestreams_ibb_timeout called and IBB session not set "
+			" up yet, cancel transfer");
+		jabber_si_bytestreams_ibb_timeout_remove(jsx);
+		purple_xfer_cancel_local(xfer);
+	}
+	
+	return FALSE;
+}
+
 static void jabber_si_bytestreams_attempt_connect(PurpleXfer *xfer)
 {
 	JabberSIXfer *jsx = xfer->data;
@@ -200,8 +234,29 @@
 
 		jabber_iq_send(iq);
 
-		purple_xfer_cancel_local(xfer);
-
+		/* if IBB is available, revert to that before giving up... */
+		if (jsx->stream_method & STREAM_METHOD_IBB) {
+			/* if we are the initializer, init IBB */
+			purple_debug_info("jabber", 
+				"jabber_si_bytestreams_attempt_connect: "
+				"no streamhosts found, trying IBB\n");
+			/* if we are the sender, open an IBB session, but not if we already
+			  did it, since we could have received the error <iq/> from the
+			  receiver already... */
+			if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND
+				&& !jsx->ibb_session) {
+				jabber_si_xfer_ibb_send_init(jsx->js, xfer);
+			} else {
+				/* setup a timeout to cancel waiting for IBB open */
+				jsx->ibb_timeout_handle = purple_timeout_add_seconds(30, 
+					jabber_si_bytestreams_ibb_timeout_cb, xfer);
+			}
+			/* if we are the receiver, just wait for IBB open, callback is
+			  already set up... */
+		} else {
+			purple_xfer_cancel_local(xfer);
+		}
+		
 		return;
 	}
 
@@ -654,8 +709,32 @@
 	jsx = xfer->data;
 
 	if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "result")) {
-		if (type && !strcmp(type, "error"))
-			purple_xfer_cancel_remote(xfer);
+	  purple_debug_info("jabber", 
+			    "jabber_si_xfer_connect_proxy_cb: type = %s\n",
+			    type);
+		if (type && !strcmp(type, "error")) {
+			/* if IBB is available, open IBB session */
+			purple_debug_info("jabber", 
+				"jabber_si_xfer_connect_proxy_cb: got error, method: %d\n",
+				jsx->stream_method);
+			if (jsx->stream_method & STREAM_METHOD_IBB) {
+				purple_debug_info("jabber", "IBB is possible, try it\n");
+				/* if we are the sender and haven't already opened an IBB
+				  session, do so now (we might already have failed to open
+				  the bytestream proxy ourselves when receiving this <iq/> */
+				if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND
+					&& !jsx->ibb_session) {
+					jabber_si_xfer_ibb_send_init(js, xfer);
+				} else {
+					jsx->ibb_timeout_handle = purple_timeout_add_seconds(30,
+						jabber_si_bytestreams_ibb_timeout_cb, xfer);
+				}
+				/* if we are receiver, just wait for IBB open stanza, callback
+				  is already set up */
+			} else {
+				purple_xfer_cancel_remote(xfer);
+			}
+		}
 		return;
 	}
 
@@ -682,8 +761,22 @@
 			purple_debug_info("jabber", "Got local SOCKS5 streamhost-used.\n");
 			purple_xfer_start(xfer, xfer->fd, NULL, -1);
 		} else {
-			purple_debug_info("jabber", "streamhost-used does not match any proxy that was offered to target\n");
-			purple_xfer_cancel_local(xfer);
+			/* if available, try to revert to IBB... */
+			if (jsx->stream_method & STREAM_METHOD_IBB) {
+				purple_debug_info("jabber", 
+					"jabber_si_connect_proxy_cb: trying to revert to IBB\n");
+				if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) {
+					jabber_si_xfer_ibb_send_init(jsx->js, xfer);
+				} else {
+					jsx->ibb_timeout_handle = purple_timeout_add_seconds(30,
+						jabber_si_bytestreams_ibb_timeout_cb, xfer);
+				}
+				/* if we are the receiver, we are already set up...*/
+			} else {
+				purple_debug_info("jabber", 
+					"streamhost-used does not match any proxy that was offered to target\n");
+				purple_xfer_cancel_local(xfer);
+			}
 		}
 		g_free(my_jid);
 		return;
@@ -810,8 +903,26 @@
 	/* We have no way of transferring, cancel the transfer */
 	if (streamhost_count == 0) {
 		jabber_iq_free(iq);
-		/* We should probably notify the target, but this really shouldn't ever happen */
-		purple_xfer_cancel_local(xfer);
+		
+		/* if available, revert to IBB */
+		if (jsx->stream_method & STREAM_METHOD_IBB) {
+			purple_debug_info("jabber",
+				"jabber_si_xfer_bytestreams_listen_cb: trying to revert to IBB\n");
+			if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) {
+				/* if we are the sender, init the IBB session... */
+				jabber_si_xfer_ibb_send_init(jsx->js, xfer);
+			} else {
+				jsx->ibb_timeout_handle = purple_timeout_add_seconds(30,
+					jabber_si_bytestreams_ibb_timeout_cb, xfer);
+			}
+			/* if we are the receiver, we should just wait... the IBB open
+			  handler has already been set up... */
+		} else {
+			/* We should probably notify the target, 
+			  but this really shouldn't ever happen */
+			purple_xfer_cancel_local(xfer);
+		}
+		
 		return;
 	}
 
@@ -841,11 +952,242 @@
 
 }
 
+static void
+jabber_si_xfer_ibb_error_cb(JabberIBBSession *sess)
+{
+	PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess);
+	JabberStream *js = jabber_ibb_session_get_js(sess);
+	PurpleConnection *gc = js->gc;
+	PurpleAccount *account = purple_connection_get_account(gc);
+	
+	purple_debug_error("jabber", "an error occured during IBB file transfer\n");
+	purple_xfer_error(purple_xfer_get_type(xfer), account,
+		jabber_ibb_session_get_who(sess), 
+			_("An error occured on the in-band bytestream transfer\n"));
+	purple_xfer_cancel_remote(xfer);
+}
+
+static void
+jabber_si_xfer_ibb_closed_cb(JabberIBBSession *sess)
+{
+	PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess);
+	JabberStream *js = jabber_ibb_session_get_js(sess);
+	PurpleConnection *gc = js->gc;
+	PurpleAccount *account = purple_connection_get_account(gc);
+	
+	purple_debug_info("jabber", "the remote user closed the transfer\n");
+	if (purple_xfer_get_bytes_remaining(xfer) > 0) {
+		purple_xfer_error(purple_xfer_get_type(xfer), account,
+			jabber_ibb_session_get_who(sess), _("Transfer was closed."));
+		purple_xfer_cancel_remote(xfer);
+	} else {
+		purple_xfer_set_completed(xfer, TRUE);
+		purple_xfer_end(xfer);
+	}
+}
+
+static void
+jabber_si_xfer_ibb_recv_data_cb(JabberIBBSession *sess, gpointer data,
+	gsize size)
+{
+	PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess);
+	JabberSIXfer *jsx = (JabberSIXfer *) xfer->data;
+	
+	if (size <= purple_xfer_get_bytes_remaining(xfer)) {
+		purple_debug_info("jabber", "about to write %" G_GSIZE_FORMAT " bytes from IBB stream\n",
+			size);
+		if(!fwrite(data, size, 1, jsx->fp)) {
+			purple_debug_error("jabber", "error writing to file\n");
+			purple_xfer_cancel_remote(xfer);
+			return;
+		}
+		purple_xfer_set_bytes_sent(xfer, purple_xfer_get_bytes_sent(xfer) + size);
+		purple_xfer_update_progress(xfer);
+		
+		if (purple_xfer_get_bytes_remaining(xfer) == 0) {
+			purple_xfer_set_completed(xfer, TRUE);
+			purple_xfer_end(xfer);
+		}
+	} else {
+		/* trying to write past size of file transfers negotiated size,
+		  reject transfer to protect against malicious behaviour */
+		purple_debug_error("jabber", 
+			"IBB file transfer send more data than expected\n");
+		purple_xfer_cancel_remote(xfer);
+	}
+	
+}
+
+static gboolean
+jabber_si_xfer_ibb_open_cb(JabberStream *js, xmlnode *packet)
+{
+	const gchar *who = xmlnode_get_attrib(packet, "from");
+	xmlnode *open = xmlnode_get_child(packet, "open");
+	const gchar *sid = xmlnode_get_attrib(open, "sid");
+	PurpleXfer *xfer = jabber_si_xfer_find(js, sid, who);
+	if (xfer) {
+		JabberSIXfer *jsx = (JabberSIXfer *) xfer->data;
+		JabberIBBSession *sess = 
+			jabber_ibb_session_create_from_xmlnode(js, packet, xfer);
+		const char *filename;
+
+		jabber_si_bytestreams_ibb_timeout_remove(jsx);
+
+		if (sess) {
+			/* open the file to write to */
+			filename = purple_xfer_get_local_filename(xfer);
+			jsx->fp = g_fopen(filename, "wb");
+			if (jsx->fp == NULL) {
+				purple_debug_error("jabber", "failed to open file %s for writing: %s\n",
+					filename, g_strerror(errno));
+				purple_xfer_cancel_remote(xfer);
+				return FALSE;
+			}
+
+			/* setup callbacks here...*/
+			jabber_ibb_session_set_data_received_callback(sess,
+				jabber_si_xfer_ibb_recv_data_cb);
+			jabber_ibb_session_set_closed_callback(sess, 
+				jabber_si_xfer_ibb_closed_cb);
+			jabber_ibb_session_set_error_callback(sess,
+				jabber_si_xfer_ibb_error_cb);
+			
+			jsx->ibb_session = sess;
+			
+			/* start the transfer */
+			purple_xfer_start(xfer, 0, NULL, 0);
+			return TRUE;
+		} else {
+			/* failed to create IBB session */
+			purple_debug_error("jabber", "failed to create IBB session\n");
+			purple_xfer_cancel_remote(xfer);
+			return FALSE;
+		}
+	} else {
+		/* we got an IBB <open/> for an unknown file transfer, pass along... */
+		purple_debug_info("jabber", 
+			"IBB open did not match any SI file transfer\n");
+		return FALSE;
+	}
+}
+															
+static void
+jabber_si_xfer_ibb_send_data(JabberIBBSession *sess)
+{
+	PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess);
+	JabberSIXfer *jsx = (JabberSIXfer *) xfer->data;
+	gsize remaining = purple_xfer_get_bytes_remaining(xfer);
+	gsize packet_size = remaining < jabber_ibb_session_get_block_size(sess) ?
+		remaining : jabber_ibb_session_get_block_size(sess);
+	gpointer data = g_malloc(packet_size);
+	int res;
+	
+	purple_debug_info("jabber", "IBB: about to read %" G_GSIZE_FORMAT " bytes from file %p\n",
+		packet_size, jsx->fp);
+	res = fread(data, packet_size, 1, jsx->fp);
+	
+	if (res == 1) {
+		jabber_ibb_session_send_data(sess, data, packet_size);
+		purple_xfer_set_bytes_sent(xfer, 
+			purple_xfer_get_bytes_sent(xfer) + packet_size);
+		purple_xfer_update_progress(xfer);
+	} else {
+		purple_debug_error("jabber", 
+			"jabber_si_xfer_ibb_send_data: error reading from file\n");
+		purple_xfer_cancel_local(xfer);
+	}
+}
+
+static void
+jabber_si_xfer_ibb_sent_cb(JabberIBBSession *sess)
+{
+	PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess);
+	gsize remaining = purple_xfer_get_bytes_remaining(xfer);
+	
+	if (remaining == 0) {
+		/* close the session */
+		jabber_ibb_session_close(sess);
+		purple_xfer_set_completed(xfer, TRUE);
+		purple_xfer_end(xfer);
+	} else {
+		/* send more... */
+		jabber_si_xfer_ibb_send_data(sess);
+	}
+}
+
+static void
+jabber_si_xfer_ibb_opened_cb(JabberIBBSession *sess)
+{
+	PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess);
+	JabberSIXfer *jsx = (JabberSIXfer *) xfer->data;
+	JabberStream *js = jabber_ibb_session_get_js(sess);
+	PurpleConnection *gc = js->gc;
+	PurpleAccount *account = purple_connection_get_account(gc);
+
+	if (jabber_ibb_session_get_state(sess) == JABBER_IBB_SESSION_OPENED) {
+		const char *filename = purple_xfer_get_local_filename(xfer);
+		jsx->fp = g_fopen(filename, "rb");
+		if (jsx->fp == NULL) {
+			purple_debug_error("jabber", "Failed to open file %s for reading: %s\n",
+				filename, g_strerror(errno));
+			purple_xfer_error(purple_xfer_get_type(xfer), account,
+				jabber_ibb_session_get_who(sess),
+				_("Failed to open the file"));
+			purple_xfer_cancel_local(xfer);
+			return;
+		}
+
+		purple_xfer_start(xfer, 0, NULL, 0);
+		purple_xfer_set_bytes_sent(xfer, 0);
+		purple_xfer_update_progress(xfer);
+		jabber_si_xfer_ibb_send_data(sess);
+	} else {
+		/* error */
+		purple_xfer_error(purple_xfer_get_type(xfer), account,
+			jabber_ibb_session_get_who(sess), 
+			_("Failed to open in-band bytestream"));
+		purple_xfer_end(xfer);
+	}
+}
+
+static void
+jabber_si_xfer_ibb_send_init(JabberStream *js, PurpleXfer *xfer)
+{
+	JabberSIXfer *jsx = (JabberSIXfer *) xfer->data;
+	
+	purple_xfer_ref(xfer);
+	
+	jsx->ibb_session = jabber_ibb_session_create(js, jsx->stream_id,
+		purple_xfer_get_remote_user(xfer), xfer);
+	
+	if (jsx->ibb_session) {
+		/* should set callbacks here... */
+		jabber_ibb_session_set_opened_callback(jsx->ibb_session,
+			jabber_si_xfer_ibb_opened_cb);
+		jabber_ibb_session_set_data_sent_callback(jsx->ibb_session,
+			jabber_si_xfer_ibb_sent_cb);
+		jabber_ibb_session_set_closed_callback(jsx->ibb_session,
+			jabber_si_xfer_ibb_closed_cb);
+		jabber_ibb_session_set_error_callback(jsx->ibb_session,
+			jabber_si_xfer_ibb_error_cb);
+		
+		/* open the IBB session */
+		jabber_ibb_session_open(jsx->ibb_session);
+		
+	} else {
+		/* failed to create IBB session */
+		purple_debug_error("jabber", 
+			"failed to initiate IBB session for file transfer\n");
+		purple_xfer_cancel_local(xfer);
+	}
+}
+	
 static void jabber_si_xfer_send_method_cb(JabberStream *js, xmlnode *packet,
 		gpointer data)
 {
 	PurpleXfer *xfer = data;
 	xmlnode *si, *feature, *x, *field, *value;
+	gboolean found_method = FALSE;
 
 	if(!(si = xmlnode_get_child_with_namespace(packet, "si", "http://jabber.org/protocol/si"))) {
 		purple_xfer_cancel_remote(xfer);
@@ -864,20 +1206,33 @@
 
 	for(field = xmlnode_get_child(x, "field"); field; field = xmlnode_get_next_twin(field)) {
 		const char *var = xmlnode_get_attrib(field, "var");
-
+		JabberSIXfer *jsx = (JabberSIXfer *) xfer->data;
+		
 		if(var && !strcmp(var, "stream-method")) {
 			if((value = xmlnode_get_child(field, "value"))) {
 				char *val = xmlnode_get_data(value);
 				if(val && !strcmp(val, "http://jabber.org/protocol/bytestreams")) {
 					jabber_si_xfer_bytestreams_send_init(xfer);
-					g_free(val);
-					return;
+					jsx->stream_method |= STREAM_METHOD_BYTESTREAMS;
+					found_method = TRUE;
+				} else if (val && !strcmp(val, XEP_0047_NAMESPACE)) {
+					jsx->stream_method |= STREAM_METHOD_IBB;
+					if (!found_method) {
+						/* we haven't tried to init a bytestream session, yet
+						  start IBB right away... */
+						jabber_si_xfer_ibb_send_init(js, xfer);
+						found_method = TRUE;
+					}
 				}
 				g_free(val);
 			}
 		}
 	}
-	purple_xfer_cancel_remote(xfer);
+	
+	if (!found_method) {
+		purple_xfer_cancel_remote(xfer);
+	}
+	
 }
 
 static void jabber_si_xfer_send_request(PurpleXfer *xfer)
@@ -914,14 +1269,14 @@
 	field = xmlnode_new_child(x, "field");
 	xmlnode_set_attrib(field, "var", "stream-method");
 	xmlnode_set_attrib(field, "type", "list-single");
+	/* maybe we should add an option to always skip bytestreams for people
+		behind troublesome firewalls */
 	option = xmlnode_new_child(field, "option");
 	value = xmlnode_new_child(option, "value");
 	xmlnode_insert_data(value, "http://jabber.org/protocol/bytestreams", -1);
-	/*
 	option = xmlnode_new_child(field, "option");
 	value = xmlnode_new_child(option, "value");
 	xmlnode_insert_data(value, "http://jabber.org/protocol/ibb", -1);
-	*/
 
 	jabber_iq_set_callback(iq, jabber_si_xfer_send_method_cb, xfer);
 
@@ -935,38 +1290,66 @@
 static void jabber_si_xfer_free(PurpleXfer *xfer)
 {
 	JabberSIXfer *jsx = xfer->data;
-	JabberStream *js = jsx->js;
-
-	js->file_transfers = g_list_remove(js->file_transfers, xfer);
-
-	if (jsx->connect_data != NULL)
-		purple_proxy_connect_cancel(jsx->connect_data);
-	if (jsx->listen_data != NULL)
-		purple_network_listen_cancel(jsx->listen_data);
-	if (jsx->iq_id != NULL)
-		jabber_iq_remove_callback_by_id(js, jsx->iq_id);
-	if (jsx->local_streamhost_fd >= 0)
-		close(jsx->local_streamhost_fd);
-	if (jsx->connect_timeout > 0)
-		purple_timeout_remove(jsx->connect_timeout);
+	
+	if (jsx) {
+		JabberStream *js = jsx->js;
+		
+		js->file_transfers = g_list_remove(js->file_transfers, xfer);
+	
+		if (jsx->connect_data != NULL)
+			purple_proxy_connect_cancel(jsx->connect_data);
+		if (jsx->listen_data != NULL)
+			purple_network_listen_cancel(jsx->listen_data);
+		if (jsx->iq_id != NULL)
+			jabber_iq_remove_callback_by_id(js, jsx->iq_id);
+		if (jsx->local_streamhost_fd >= 0)
+			close(jsx->local_streamhost_fd);
+		if (jsx->connect_timeout > 0)
+			purple_timeout_remove(jsx->connect_timeout);
+		if (jsx->ibb_timeout_handle > 0)
+			purple_timeout_remove(jsx->ibb_timeout_handle);
+		
+		if (jsx->streamhosts) {
+			g_list_foreach(jsx->streamhosts, jabber_si_free_streamhost, NULL);
+			g_list_free(jsx->streamhosts);
+		}
+	
+		if (jsx->ibb_session) {
+			purple_debug_info("jabber", 
+				"jabber_si_xfer_free: destroying IBB session\n");
+			jabber_ibb_session_destroy(jsx->ibb_session);
+		}
+		
+		if (jsx->fp) {
+			purple_debug_info("jabber", 
+				"jabber_si_xfer_free: closing file for IBB transfer\n");
+			fclose(jsx->fp);
+		}
+	
+		g_free(jsx->stream_id);
+		g_free(jsx->iq_id);
+		/* XXX: free other stuff */
+		g_free(jsx->rxqueue);
+		g_free(jsx);
+		xfer->data = NULL;
 
-	if (jsx->streamhosts) {
-		g_list_foreach(jsx->streamhosts, jabber_si_free_streamhost, NULL);
-		g_list_free(jsx->streamhosts);
+		purple_debug_info("jabber", "jabber_si_xfer_free(): freeing jsx %p\n", jsx);
 	}
-
-	g_free(jsx->stream_id);
-	g_free(jsx->iq_id);
-	/* XXX: free other stuff */
-	g_free(jsx->rxqueue);
-	g_free(jsx);
-	xfer->data = NULL;
-
-	purple_debug_info("jabber", "jabber_si_xfer_free(): freeing jsx %p", jsx);
 }
-
+		
+/*
+ * These four functions should only be called from the PurpleXfer functions
+ * (typically purple_xfer_cancel_(remote|local), purple_xfer_end, or
+ * purple_xfer_request_denied.
+ */
 static void jabber_si_xfer_cancel_send(PurpleXfer *xfer)
 {
+	JabberSIXfer *jsx = (JabberSIXfer *) xfer->data;
+	
+	/* if there is an IBB session active, send close on that */
+	if (jsx->ibb_session) {
+		jabber_ibb_session_close(jsx->ibb_session);
+	}
 	jabber_si_xfer_free(xfer);
 	purple_debug(PURPLE_DEBUG_INFO, "jabber", "in jabber_si_xfer_cancel_send\n");
 }
@@ -981,6 +1364,11 @@
 
 static void jabber_si_xfer_cancel_recv(PurpleXfer *xfer)
 {
+	JabberSIXfer *jsx = (JabberSIXfer *) xfer->data;
+	/* if there is an IBB session active, send close */
+	if (jsx->ibb_session) {
+		jabber_ibb_session_close(jsx->ibb_session);
+	}
 	jabber_si_xfer_free(xfer);
 	purple_debug(PURPLE_DEBUG_INFO, "jabber", "in jabber_si_xfer_cancel_recv\n");
 }
@@ -995,9 +1383,16 @@
 static void jabber_si_xfer_send_disco_cb(JabberStream *js, const char *who,
 		JabberCapabilities capabilities, gpointer data)
 {
-	PurpleXfer *xfer = data;
+	PurpleXfer *xfer = (PurpleXfer *) data;
+	JabberSIXfer *jsx = (JabberSIXfer *) xfer->data;
+	
+	if (capabilities & JABBER_CAP_IBB) {
+		purple_debug_info("jabber", 
+			"jabber_si_xfer_send_disco_cb: remote JID supports IBB\n");
+		jsx->stream_method |= STREAM_METHOD_IBB;
+	}
 
-	if(capabilities & JABBER_CAP_SI_FILE_XFER) {
+	if (capabilities & JABBER_CAP_SI_FILE_XFER) {
 		jabber_si_xfer_send_request(xfer);
 	} else {
 		char *msg = g_strdup_printf(_("Unable to send file to %s, user does not support file transfers"), who);
@@ -1124,18 +1519,22 @@
 		x = xmlnode_new_child(feature, "x");
 		xmlnode_set_namespace(x, "jabber:x:data");
 		xmlnode_set_attrib(x, "type", "submit");
-
 		field = xmlnode_new_child(x, "field");
 		xmlnode_set_attrib(field, "var", "stream-method");
-
-		value = xmlnode_new_child(field, "value");
-		if(jsx->stream_method & STREAM_METHOD_BYTESTREAMS)
+					
+		/* we should maybe "remember" if bytestreams has failed before (in the
+			same session) with this JID, and only present IBB as an option to
+			avoid unnessesary timeout */
+		/* maybe we should have an account option to always just try IBB
+			for people who know their firewalls are very restrictive */
+		if (jsx->stream_method & STREAM_METHOD_BYTESTREAMS) {
+			value = xmlnode_new_child(field, "value");
 			xmlnode_insert_data(value, "http://jabber.org/protocol/bytestreams", -1);
-		/*
-		else if(jsx->stream_method & STREAM_METHOD_IBB)
-		xmlnode_insert_data(value, "http://jabber.org/protocol/ibb", -1);
-		*/
-
+		} else if(jsx->stream_method & STREAM_METHOD_IBB) {
+			value = xmlnode_new_child(field, "value");
+			xmlnode_insert_data(value, "http://jabber.org/protocol/ibb", -1);
+		}
+							  
 		jabber_iq_send(iq);
 	}
 }
@@ -1155,6 +1554,9 @@
 		xfer->data = jsx = g_new0(JabberSIXfer, 1);
 		jsx->js = js;
 		jsx->local_streamhost_fd = -1;
+		
+		jsx->ibb_session = NULL;
+		jsx->fp = NULL;
 
 		purple_xfer_set_init_fnc(xfer, jabber_si_xfer_init);
 		purple_xfer_set_cancel_send_fnc(xfer, jabber_si_xfer_cancel_send);
@@ -1226,6 +1628,8 @@
 
 	jsx = g_new0(JabberSIXfer, 1);
 	jsx->local_streamhost_fd = -1;
+				   
+	jsx->ibb_session = NULL;
 
 	for(field = xmlnode_get_child(x, "field"); field; field = xmlnode_get_next_twin(field)) {
 		const char *var = xmlnode_get_attrib(field, "var");
@@ -1237,10 +1641,8 @@
 					if((val = xmlnode_get_data(value))) {
 						if(!strcmp(val, "http://jabber.org/protocol/bytestreams")) {
 							jsx->stream_method |= STREAM_METHOD_BYTESTREAMS;
-							/*
 						} else if(!strcmp(val, "http://jabber.org/protocol/ibb")) {
 							jsx->stream_method |= STREAM_METHOD_IBB;
-							*/
 						}
 						g_free(val);
 					}
@@ -1278,4 +1680,15 @@
 	}
 }
 
+void
+jabber_si_init(void)
+{
+	jabber_ibb_register_open_handler(jabber_si_xfer_ibb_open_cb);
+}
 
+void
+jabber_si_uninit(void)
+{
+	jabber_ibb_unregister_open_handler(jabber_si_xfer_ibb_open_cb);
+}
+
--- a/libpurple/protocols/jabber/si.h	Thu Jan 22 01:04:56 2009 +0000
+++ b/libpurple/protocols/jabber/si.h	Thu Jan 22 20:37:54 2009 +0000
@@ -30,5 +30,7 @@
 void jabber_si_parse(JabberStream *js, xmlnode *packet);
 PurpleXfer *jabber_si_new_xfer(PurpleConnection *gc, const char *who);
 void jabber_si_xfer_send(PurpleConnection *gc, const char *who, const char *file);
+void jabber_si_init(void);
+void jabber_si_uninit(void);
 
 #endif /* _PURPLE_JABBER_SI_H_ */