Index: protocols/ft.h
===================================================================
--- protocols/ft.h	(revision 1ba7e8f1d28c4876ea0b787f1e4ebb5607f48895)
+++ protocols/ft.h	(revision 1ba7e8f1d28c4876ea0b787f1e4ebb5607f48895)
@@ -0,0 +1,173 @@
+/********************************************************************\
+* BitlBee -- An IRC to other IM-networks gateway                     *
+*                                                                    *
+* Copyright 2006 Marijn Kruisselbrink and others                     *
+\********************************************************************/
+
+/* Generic file transfer header                                     */
+
+/*
+  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 with
+  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+  Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#ifndef _FT_H
+#define _FT_H
+
+/*
+ * One buffer is needed for each transfer. The receiver stores a message
+ * in it and gives it to the sender. The sender will stall the receiver
+ * till the buffer has been sent out.
+ */
+#define FT_BUFFER_SIZE 2048
+
+typedef enum {
+	FT_STATUS_LISTENING	= 1,
+	FT_STATUS_TRANSFERRING	= 2,
+	FT_STATUS_FINISHED	= 4,
+	FT_STATUS_CANCELED	= 8,
+	FT_STATUS_CONNECTING	= 16
+} file_status_t;
+
+/*
+ * This structure holds all irc specific information regarding an incoming (from the point of view of
+ * the irc client) file transfer. New instances of this struct should only be created by calling the
+ * imcb_file_send_start() method, which will initialize most of the fields. The data field and the various
+ * methods are zero-initialized. Instances will automatically be deleted once the transfer is completed,
+ * canceled, or the connection to the irc client has been lost (note that also if only the irc connection
+ * and not the dcc connection is lost, the file transfer will still be canceled and freed).
+ *
+ * The following (poor ascii-art) diagram illustrates what methods are called for which status-changes:
+ *
+ *	                        /-----------\                    /----------\
+ *	               -------> | LISTENING | -----------------> | CANCELED |
+ *	                        \-----------/  [canceled,]free   \----------/
+ *	                              |
+ *	                              | accept
+ *	                              V
+ *	               /------ /-------------\                    /------------------------\
+ *	   out_of_data |       | TRANSFERING | -----------------> | TRANSFERING | CANCELED |
+ *	               \-----> \-------------/  [canceled,]free   \------------------------/
+ *	                              |
+ *	                              | finished,free
+ *	                              V
+ *	                 /------------------------\
+ *	                 | TRANSFERING | FINISHED |
+ *	                 \------------------------/
+ */
+typedef struct file_transfer {
+
+	/* Are we sending something? */
+	int sending;
+
+	/*
+	 * The current status of this file transfer.
+	 */ 
+	file_status_t status;
+	
+	/*
+	 * file size
+	 */
+	size_t file_size;
+	
+	/*
+	 * Number of bytes that have been successfully transferred.
+	 */
+	size_t bytes_transferred;
+
+	/*
+	 * Time started. Used to calculate kb/s.
+	 */
+	time_t started;
+
+	/*
+	 * file name
+	 */
+	char *file_name;
+
+	/*
+	 * A unique local ID for this file transfer.
+	 */
+	unsigned int local_id;
+
+	/*
+	 * IM-protocol specific data associated with this file transfer.
+	 */
+	gpointer data;
+	
+	/*
+	 * Private data.
+	 */
+	gpointer priv;
+	
+	/*
+	 * If set, called after succesful connection setup.
+	 */
+	void (*accept) ( struct file_transfer *file );
+	
+	/*
+	 * If set, called when the transfer is canceled or finished.
+	 * Subsequently, this structure will be freed.
+	 *
+	 */
+	void (*free) ( struct file_transfer *file );
+	
+	/*
+	 * If set, called when the transfer is finished and successful.
+	 */
+	void (*finished) ( struct file_transfer *file );
+	
+	/*
+	 * If set, called when the transfer is canceled.
+	 * ( canceled either by the transfer implementation or by
+	 *  a call to imcb_file_canceled )
+	 */
+	void (*canceled) ( struct file_transfer *file, char *reason );
+	
+	/*
+	 * called by the sending side to indicate that it is writable.
+	 * The callee should check if data is available and call the 
+	 * function(as seen below) if that is the case.
+	 */
+	gboolean (*write_request) ( struct file_transfer *file );
+
+	/*
+	 * When sending files, protocols register this function to receive data.
+	 * This should only be called once after write_request is called. The caller
+	 * should not read more data until write_request is called again. This technique
+	 * avoids buffering.
+	 */
+	gboolean (*write) (struct file_transfer *file, char *buffer, unsigned int len );
+
+	/* The send buffer associated with this transfer.
+	 * Since receivers always wait for a write_request call one is enough.
+	 */
+	char buffer[FT_BUFFER_SIZE];
+
+} file_transfer_t;
+
+/*
+ * This starts a file transfer from bitlbee to the user (currently via DCC).
+ */
+file_transfer_t *imcb_file_send_start( struct im_connection *ic, char *user_nick, char *file_name, size_t file_size );
+
+/*
+ * This should be called by a protocol when the transfer is canceled. Note that
+ * the canceled() and free() callbacks given in file will be called by this function.
+ */
+void imcb_file_canceled( file_transfer_t *file, char *reason );
+
+gboolean imcb_file_recv_start( file_transfer_t *ft );
+#endif
Index: protocols/jabber/Makefile
===================================================================
--- protocols/jabber/Makefile	(revision 3038e476a93b2be057581b831749eac931a06559)
+++ protocols/jabber/Makefile	(revision 1ba7e8f1d28c4876ea0b787f1e4ebb5607f48895)
@@ -10,5 +10,5 @@
 
 # [SH] Program variables
-objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o sasl.o xmltree.o
+objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o sasl.o xmltree.o si.o s5bytestream.o
 
 CFLAGS += -Wall
Index: protocols/jabber/iq.c
===================================================================
--- protocols/jabber/iq.c	(revision 3038e476a93b2be057581b831749eac931a06559)
+++ protocols/jabber/iq.c	(revision 1ba7e8f1d28c4876ea0b787f1e4ebb5607f48895)
@@ -90,7 +90,7 @@
 			pack = 0;
 		}
-		else if( strcmp( s, XMLNS_DISCOVER ) == 0 )
-		{
-			const char *features[] = { XMLNS_DISCOVER,
+		else if( strcmp( s, XMLNS_DISCO_INFO ) == 0 )
+		{
+			const char *features[] = { XMLNS_DISCO_INFO,
 			                           XMLNS_VERSION,
 			                           XMLNS_TIME,
@@ -98,4 +98,7 @@
 			                           XMLNS_MUC,
 			                           XMLNS_PING,
+						   XMLNS_SI,
+						   XMLNS_BYTESTREAMS,
+						   XMLNS_FILETRANSFER,
 			                           NULL };
 			const char **f;
@@ -117,5 +120,5 @@
 		{
 			xt_free_node( reply );
-			reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel" );
+			reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel", NULL );
 			pack = 0;
 		}
@@ -123,16 +126,18 @@
 	else if( strcmp( type, "set" ) == 0 )
 	{
-		if( !( c = xt_find_node( node->children, "query" ) ) ||
+		if(  ( c = xt_find_node( node->children, "si" ) ) &&
+		     ( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_SI ) == 0 ) )
+		{
+			return jabber_si_handle_request( ic, node, c );
+		} else if( !( c = xt_find_node( node->children, "query" ) ) ||
 		    !( s = xt_find_attr( c, "xmlns" ) ) )
 		{
 			imcb_log( ic, "Warning: Received incomplete IQ-%s packet", type );
 			return XT_HANDLED;
-		}
-		
+		} else if( strcmp( s, XMLNS_ROSTER ) == 0 )
+		{
 		/* This is a roster push. XMPP servers send this when someone
 		   was added to (or removed from) the buddy list. AFAIK they're
 		   sent even if we added this buddy in our own session. */
-		if( strcmp( s, XMLNS_ROSTER ) == 0 )
-		{
 			int bare_len = strlen( ic->acc->user );
 			
@@ -151,12 +156,15 @@
 				
 				xt_free_node( reply );
-				reply = jabber_make_error_packet( node, "not-allowed", "cancel" );
+				reply = jabber_make_error_packet( node, "not-allowed", "cancel", NULL );
 				pack = 0;
 			}
-		}
-		else
+		} else if( strcmp( s, XMLNS_BYTESTREAMS ) == 0 )
+		{
+		     	/* Bytestream Request (stage 2 of file transfer) */
+			return jabber_bs_recv_request( ic, node, c );
+		} else
 		{
 			xt_free_node( reply );
-			reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel" );
+			reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel", NULL );
 			pack = 0;
 		}
@@ -568,2 +576,162 @@
 	return st;
 }
+
+xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig );
+
+xt_status jabber_iq_query_features( struct im_connection *ic, char *bare_jid )
+{
+	struct xt_node *node, *query;
+	struct jabber_buddy *bud;
+	
+	if( ( bud = jabber_buddy_by_jid( ic, bare_jid , 0 ) ) == NULL )
+	{
+		/* Who cares about the unknown... */
+		imcb_log( ic, "Couldnt find the man: %s", bare_jid);
+		return 0;
+	}
+	
+	if( bud->features ) /* been here already */
+		return XT_HANDLED;
+	
+	node = xt_new_node( "query", NULL, NULL );
+	xt_add_attr( node, "xmlns", XMLNS_DISCO_INFO );
+	
+	if( !( query = jabber_make_packet( "iq", "get", bare_jid, node ) ) )
+	{
+		imcb_log( ic, "WARNING: Couldn't generate feature query" );
+		xt_free_node( node );
+	}
+
+	jabber_cache_add( ic, query, jabber_iq_parse_features );
+
+	return jabber_write_packet( ic, query );
+}
+
+xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
+{
+	struct xt_node *c;
+	struct jabber_buddy *bud;
+	char *feature;
+
+	if( !( c = xt_find_node( node->children, "query" ) ) ||
+	    !( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_DISCO_INFO ) == 0 ) )
+	{
+		imcb_log( ic, "WARNING: Received incomplete IQ-result packet for discover" );
+		return XT_HANDLED;
+	}
+	if( ( bud = jabber_buddy_by_jid( ic, xt_find_attr( node, "from") , 0 ) ) == NULL )
+	{
+		/* Who cares about the unknown... */
+		imcb_log( ic, "Couldnt find the man: %s", xt_find_attr( node, "from"));
+		return 0;
+	}
+	
+	c = c->children;
+	while( ( c = xt_find_node( c, "feature" ) ) ) {
+		feature = xt_find_attr( c, "var" );
+		bud->features = g_slist_append(bud->features, g_strdup(feature) );
+		c = c->next;
+	}
+
+	return XT_HANDLED;
+}
+
+xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig );
+
+xt_status jabber_iq_query_server( struct im_connection *ic, char *jid, char *xmlns )
+{
+	struct xt_node *node, *query;
+	struct jabber_data *jd = ic->proto_data;
+	
+	node = xt_new_node( "query", NULL, NULL );
+	xt_add_attr( node, "xmlns", xmlns );
+	
+	if( !( query = jabber_make_packet( "iq", "get", jid, node ) ) )
+	{
+		imcb_log( ic, "WARNING: Couldn't generate server query" );
+		xt_free_node( node );
+	}
+
+	jd->have_streamhosts--;
+	jabber_cache_add( ic, query, jabber_iq_parse_server_features );
+
+	return jabber_write_packet( ic, query );
+}
+
+/*
+ * Query the server for "items", query each "item" for identities, query each "item" that's a proxy for it's bytestream info
+ */
+xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
+{
+	struct xt_node *c;
+	struct jabber_data *jd = ic->proto_data;
+
+	if( !( c = xt_find_node( node->children, "query" ) ) ||
+	    !xt_find_attr( node, "from" ) )
+	{
+		imcb_log( ic, "WARNING: Received incomplete IQ-result packet for discover" );
+		return XT_HANDLED;
+	}
+
+	jd->have_streamhosts++;
+
+	if( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_DISCO_ITEMS ) == 0 )
+	{
+		char *item, *itemjid;
+
+		/* answer from server */
+	
+		c = c->children;
+		while( ( c = xt_find_node( c, "item" ) ) )
+		{
+			item = xt_find_attr( c, "name" );
+			itemjid = xt_find_attr( c, "jid" );
+
+			jabber_iq_query_server( ic, itemjid, XMLNS_DISCO_INFO );
+
+			c = c->next;
+		}
+	} else if( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_DISCO_INFO ) == 0 )
+	{
+		char *category, *type;
+
+		/* answer from potential proxy */
+
+		c = c->children;
+		while( ( c = xt_find_node( c, "identity" ) ) )
+		{
+			category = xt_find_attr( c, "category" );
+			type = xt_find_attr( c, "type" );
+
+			if( type && ( strcmp( type, "bytestreams" ) == 0 ) &&
+			    category && ( strcmp( category, "proxy" ) == 0 ) )
+				jabber_iq_query_server( ic, xt_find_attr( node, "from" ), XMLNS_BYTESTREAMS );
+
+			c = c->next;
+		}
+	} else if( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_BYTESTREAMS ) == 0 )
+	{
+		char *host, *jid;
+		int port;
+
+		/* answer from proxy */
+
+		if( ( c = xt_find_node( c->children, "streamhost" ) ) &&
+		    ( host = xt_find_attr( c, "host" ) ) &&
+		    ( port = atoi( xt_find_attr( c, "port" ) ) ) &&
+		    ( jid = xt_find_attr( c, "jid" ) ) )
+		{
+			jabber_streamhost_t *sh = g_new0( jabber_streamhost_t, 1 );
+			sh->jid = g_strdup( jid );
+			sh->host = g_strdup( host );
+			sprintf( sh->port, "%u", port );
+
+			imcb_log( ic, "Proxy found: jid %s host %s port %u", jid, host, port );
+			jd->streamhosts = g_slist_append( jd->streamhosts, sh );
+		}
+	}
+
+	if( jd->have_streamhosts == 0 )
+		jd->have_streamhosts++;
+	return XT_HANDLED;
+}
Index: protocols/jabber/jabber.c
===================================================================
--- protocols/jabber/jabber.c	(revision 3038e476a93b2be057581b831749eac931a06559)
+++ protocols/jabber/jabber.c	(revision 1ba7e8f1d28c4876ea0b787f1e4ebb5607f48895)
@@ -529,4 +529,5 @@
 	ret->send_typing = jabber_send_typing;
 	ret->handle_cmp = g_strcasecmp;
+	ret->transfer_request = jabber_si_transfer_request;
 
 	register_protocol( ret );
Index: protocols/jabber/jabber.h
===================================================================
--- protocols/jabber/jabber.h	(revision 3038e476a93b2be057581b831749eac931a06559)
+++ protocols/jabber/jabber.h	(revision 1ba7e8f1d28c4876ea0b787f1e4ebb5607f48895)
@@ -59,4 +59,12 @@
 } jabber_buddy_flags_t;
 
+/* Stores a streamhost's(a.k.a. proxy) data */
+typedef struct
+{
+	char *jid;
+	char *host;
+	char port[6];
+} jabber_streamhost_t;
+
 typedef enum
 {
@@ -89,4 +97,8 @@
 	GHashTable *node_cache;
 	GHashTable *buddies;
+
+	GSList *filetransfers;
+	GSList *streamhosts;
+	int have_streamhosts;
 };
 
@@ -118,4 +130,5 @@
 	struct jabber_away_state *away_state;
 	char *away_message;
+	GSList *features;
 	
 	time_t last_act;
@@ -131,4 +144,28 @@
 	char *my_full_jid; /* Separate copy because of case sensitivity. */
 	struct jabber_buddy *me;
+};
+
+struct jabber_transfer
+{
+	/* bitlbee's handle for this transfer */
+	file_transfer_t *ft;
+
+	/* the stream's private handle */
+	gpointer streamhandle;
+
+	struct im_connection *ic;
+
+	int watch_in;
+	int watch_out;
+
+	char *ini_jid;
+	char *tgt_jid;
+	char *iq_id;
+	char *sid;
+	int accepted;
+
+	size_t bytesread, byteswritten;
+	int fd;
+	struct sockaddr_storage saddr;
 };
 
@@ -162,15 +199,22 @@
 
 /* Some supported extensions/legacy stuff */
-#define XMLNS_AUTH         "jabber:iq:auth"                     /* XEP-0078 */
-#define XMLNS_VERSION      "jabber:iq:version"                  /* XEP-0092 */
-#define XMLNS_TIME         "jabber:iq:time"                     /* XEP-0090 */
-#define XMLNS_PING         "urn:xmpp:ping"                      /* XEP-0199 */
-#define XMLNS_VCARD        "vcard-temp"                         /* XEP-0054 */
-#define XMLNS_DELAY        "jabber:x:delay"                     /* XEP-0091 */
-#define XMLNS_CHATSTATES   "http://jabber.org/protocol/chatstates"  /* 0085 */
-#define XMLNS_DISCOVER     "http://jabber.org/protocol/disco#info"  /* 0030 */
-#define XMLNS_MUC          "http://jabber.org/protocol/muc"     /* XEP-0045 */
-#define XMLNS_MUC_USER     "http://jabber.org/protocol/muc#user"/* XEP-0045 */
-#define XMLNS_CAPS         "http://jabber.org/protocol/caps"    /* XEP-0115 */
+#define XMLNS_AUTH         "jabber:iq:auth"                                      /* XEP-0078 */
+#define XMLNS_VERSION      "jabber:iq:version"                                   /* XEP-0092 */
+#define XMLNS_TIME         "jabber:iq:time"                                      /* XEP-0090 */
+#define XMLNS_PING         "urn:xmpp:ping"                                       /* XEP-0199 */
+#define XMLNS_VCARD        "vcard-temp"                                          /* XEP-0054 */
+#define XMLNS_DELAY        "jabber:x:delay"                                      /* XEP-0091 */
+#define XMLNS_XDATA        "jabber:x:data"                                       /* XEP-0004 */
+#define XMLNS_CHATSTATES   "http://jabber.org/protocol/chatstates"               /* XEP-0085 */
+#define XMLNS_DISCO_INFO   "http://jabber.org/protocol/disco#info"               /* XEP-0030 */
+#define XMLNS_DISCO_ITEMS  "http://jabber.org/protocol/disco#items"              /* XEP-0030 */
+#define XMLNS_MUC          "http://jabber.org/protocol/muc"                      /* XEP-0045 */
+#define XMLNS_MUC_USER     "http://jabber.org/protocol/muc#user"                 /* XEP-0045 */
+#define XMLNS_CAPS         "http://jabber.org/protocol/caps"                     /* XEP-0115 */
+#define XMLNS_FEATURE      "http://jabber.org/protocol/feature-neg"              /* XEP-0020 */
+#define XMLNS_SI           "http://jabber.org/protocol/si"                       /* XEP-0095 */
+#define XMLNS_FILETRANSFER "http://jabber.org/protocol/si/profile/file-transfer" /* XEP-0096 */
+#define XMLNS_BYTESTREAMS  "http://jabber.org/protocol/bytestreams"              /* XEP-0065 */
+#define XMLNS_IBB          "http://jabber.org/protocol/ibb"                      /* XEP-0047 */
 
 /* iq.c */
@@ -182,4 +226,16 @@
 int jabber_add_to_roster( struct im_connection *ic, char *handle, char *name );
 int jabber_remove_from_roster( struct im_connection *ic, char *handle );
+xt_status jabber_iq_query_features( struct im_connection *ic, char *bare_jid );
+xt_status jabber_iq_query_server( struct im_connection *ic, char *jid, char *xmlns );
+
+/* si.c */
+int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, struct xt_node *sinode );
+void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who );
+void jabber_si_free_transfer( file_transfer_t *ft);
+
+/* s5bytestream.c */
+int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode);
+gboolean jabber_bs_send_start( struct jabber_transfer *tf );
+gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, unsigned int len );
 
 /* message.c */
@@ -195,5 +251,5 @@
 char *set_eval_tls( set_t *set, char *value );
 struct xt_node *jabber_make_packet( char *name, char *type, char *to, struct xt_node *children );
-struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type );
+struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type, char *err_code );
 void jabber_cache_add( struct im_connection *ic, struct xt_node *node, jabber_cache_event func );
 struct xt_node *jabber_cache_get( struct im_connection *ic, char *id );
Index: protocols/jabber/jabber_util.c
===================================================================
--- protocols/jabber/jabber_util.c	(revision 3038e476a93b2be057581b831749eac931a06559)
+++ protocols/jabber/jabber_util.c	(revision 1ba7e8f1d28c4876ea0b787f1e4ebb5607f48895)
@@ -97,5 +97,5 @@
 }
 
-struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type )
+struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type, char *err_code )
 {
 	struct xt_node *node, *c;
@@ -109,4 +109,8 @@
 	c = xt_new_node( "error", NULL, c );
 	xt_add_attr( c, "type", err_type );
+	
+	/* Add the error code, if present */
+	if (err_code)
+		xt_add_attr( c, "code", err_code );
 	
 	/* To make the actual error packet, we copy the original packet and
@@ -287,5 +291,12 @@
 	new = g_new( char, len + 1 );
 	for( i = 0; i < len; i ++ )
+	{
+		/* don't normalize the resource */
+		if( orig[i] == '/' )
+			break;
 		new[i] = tolower( orig[i] );
+	}
+	for( ; i < len; i ++ )
+		new[i] = orig[i];
 	
 	new[i] = 0;
Index: protocols/jabber/s5bytestream.c
===================================================================
--- protocols/jabber/s5bytestream.c	(revision 1ba7e8f1d28c4876ea0b787f1e4ebb5607f48895)
+++ protocols/jabber/s5bytestream.c	(revision 1ba7e8f1d28c4876ea0b787f1e4ebb5607f48895)
@@ -0,0 +1,1069 @@
+/***************************************************************************\
+*                                                                           *
+*  BitlBee - An IRC to IM gateway                                           *
+*  Jabber module - SOCKS5 Bytestreams ( XEP-0065 )                          *
+*                                                                           *
+*  Copyright 2007 Uli Meis <a.sporto+bee@gmail.com>                         *
+*                                                                           *
+*  This program is free software; you can redistribute it and/or modify     *
+*  it under the terms of the GNU General Public License as published by     *
+*  the Free Software Foundation; either version 2 of the License, or        *
+*  (at your option) any later version.                                      *
+*                                                                           *
+*  This program is distributed in the hope that it will be useful,          *
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
+*  GNU General Public License for more details.                             *
+*                                                                           *
+*  You should have received a copy of the GNU General Public License along  *
+*  with this program; if not, write to the Free Software Foundation, Inc.,  *
+*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
+*                                                                           *
+\***************************************************************************/
+
+#include "jabber.h"
+#include "sha1.h"
+#include <poll.h>
+
+struct bs_transfer {
+
+	struct jabber_transfer *tf;
+
+	jabber_streamhost_t *sh;
+	GSList *streamhosts;
+
+	enum 
+	{ 
+		BS_PHASE_CONNECT, 
+		BS_PHASE_CONNECTED, 
+		BS_PHASE_REQUEST, 
+		BS_PHASE_REPLY
+	} phase;
+
+	/* SHA1( SID + Initiator JID + Target JID) */
+	char *pseudoadr;
+
+	gint connect_timeout;
+};
+
+struct socks5_message
+{
+	unsigned char ver;
+	union
+	{
+		unsigned char cmd;
+		unsigned char rep;
+	} cmdrep;
+	unsigned char rsv;
+	unsigned char atyp;
+	unsigned char addrlen;
+	unsigned char address[40];
+	in_port_t port;
+} __attribute__ ((packed)); 
+
+/* connect() timeout in seconds. */
+#define JABBER_BS_CONTIMEOUT 15
+/* listen timeout */
+#define JABBER_BS_LISTEN_TIMEOUT  90
+
+/* very useful */
+#define ASSERTSOCKOP(op, msg) \
+	if( (op) == -1 ) \
+		return jabber_bs_abort( bt , msg ": %s", strerror( errno ) );
+
+gboolean jabber_bs_abort( struct bs_transfer *bt, char *format, ... );
+void jabber_bs_canceled( file_transfer_t *ft , char *reason );
+void jabber_bs_free_transfer( file_transfer_t *ft );
+gboolean jabber_bs_connect_timeout( gpointer data, gint fd, b_input_condition cond );
+gboolean jabber_bs_poll( struct bs_transfer *bt, int fd, short *revents );
+gboolean jabber_bs_peek( struct bs_transfer *bt, void *buffer, int buflen );
+
+void jabber_bs_recv_answer_request( struct bs_transfer *bt );
+gboolean jabber_bs_recv_read( gpointer data, gint fd, b_input_condition cond );
+gboolean jabber_bs_recv_write_request( file_transfer_t *ft );
+gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition cond );
+gboolean jabber_bs_recv_handshake_abort( struct bs_transfer *bt, char *error );
+int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode );
+
+gboolean jabber_bs_send_handshake_abort( struct bs_transfer *bt, char *error );
+gboolean jabber_bs_send_request( struct jabber_transfer *tf, GSList *streamhosts );
+gboolean jabber_bs_send_handshake( gpointer data, gint fd, b_input_condition cond );
+gboolean jabber_bs_send_listen( struct bs_transfer *bt, struct sockaddr_storage *saddr, char *host, char *port );
+static xt_status jabber_bs_send_handle_activate( struct im_connection *ic, struct xt_node *node, struct xt_node *orig );
+void jabber_bs_send_activate( struct bs_transfer *bt );
+
+/*
+ * Frees a bs_transfer struct and calls the SI free function
+ */
+void jabber_bs_free_transfer( file_transfer_t *ft) {
+	struct jabber_transfer *tf = ft->data;
+	struct bs_transfer *bt = tf->streamhandle;
+	jabber_streamhost_t *sh;
+
+	if ( tf->watch_in )
+		b_event_remove( tf->watch_in );
+	
+	if( tf->watch_out )
+		b_event_remove( tf->watch_out );
+	
+	g_free( bt->pseudoadr );
+
+	while( bt->streamhosts )
+	{
+		sh = bt->streamhosts->data;
+		bt->streamhosts = g_slist_remove( bt->streamhosts, sh );
+		g_free( sh->jid );
+		g_free( sh->host );
+		g_free( sh );
+	}
+	
+	g_free( bt );
+
+	jabber_si_free_transfer( ft );
+}
+
+/*
+ * Checks if buflen data is available on the socket and
+ * writes it to buffer if that's the case.
+ */
+gboolean jabber_bs_peek( struct bs_transfer *bt, void *buffer, int buflen )
+{
+	int ret;
+	int fd = bt->tf->fd;
+
+	ASSERTSOCKOP( ret = recv( fd, buffer, buflen, MSG_PEEK ), "MSG_PEEK'ing" );
+
+	if( ret == 0 )
+		return jabber_bs_abort( bt, "Remote end closed connection" );
+		
+	if( ret < buflen )
+		return ret;
+
+	ASSERTSOCKOP( ret = recv( fd, buffer, buflen, 0 ), "Dequeuing after MSG_PEEK" );
+
+	if( ret != buflen )
+		return jabber_bs_abort( bt, "recv returned less than previous recv with MSG_PEEK" );
+	
+	return ret;
+}
+
+
+/* 
+ * This function is scheduled in bs_handshake via b_timeout_add after a (non-blocking) connect().
+ */
+gboolean jabber_bs_connect_timeout( gpointer data, gint fd, b_input_condition cond )
+{
+	struct bs_transfer *bt = data;
+
+	bt->connect_timeout = 0;
+
+	jabber_bs_abort( bt, "no connection after %d seconds", bt->tf->ft->sending ? JABBER_BS_LISTEN_TIMEOUT : JABBER_BS_CONTIMEOUT );
+
+	return FALSE;
+}
+
+/* 
+ * Polls the socket, checks for errors and removes a connect timer
+ * if there is one.
+ */
+gboolean jabber_bs_poll( struct bs_transfer *bt, int fd, short *revents )
+{
+	struct pollfd pfd = { .fd = fd, .events = POLLHUP|POLLERR };
+	
+	if ( bt->connect_timeout )
+	{
+		b_event_remove( bt->connect_timeout );
+		bt->connect_timeout = 0;
+	}
+
+	ASSERTSOCKOP( poll( &pfd, 1, 0 ), "poll()" )
+
+	if( pfd.revents & POLLERR )
+	{
+		int sockerror;
+		socklen_t errlen = sizeof( sockerror );
+
+		if ( getsockopt( fd, SOL_SOCKET, SO_ERROR, &sockerror, &errlen ) )
+			return jabber_bs_abort( bt, "getsockopt() failed, unknown socket error during SOCKS5 handshake (weird!)" );
+
+		if ( bt->phase == BS_PHASE_CONNECTED )
+			return jabber_bs_abort( bt, "connect failed: %s", strerror( sockerror ) );
+
+		return jabber_bs_abort( bt, "Socket error during SOCKS5 handshake(weird!): %s", strerror( sockerror ) );
+	}
+
+	if( pfd.revents & POLLHUP )
+		return jabber_bs_abort( bt, "Remote end closed connection" );
+	
+	*revents = pfd.revents;
+	
+	return TRUE;
+}
+
+/*
+ * Used for receive and send path.
+ */
+gboolean jabber_bs_abort( struct bs_transfer *bt, char *format, ... )
+{
+	va_list params;
+	va_start( params, format );
+	char error[128];
+
+	if( vsnprintf( error, 128, format, params ) < 0 )
+		sprintf( error, "internal error parsing error string (BUG)" );
+	va_end( params );
+	if( bt->tf->ft->sending )
+		return jabber_bs_send_handshake_abort( bt, error );
+	else
+		return jabber_bs_recv_handshake_abort( bt, error );
+}
+
+/* Bad luck */
+void jabber_bs_canceled( file_transfer_t *ft , char *reason )
+{
+	struct jabber_transfer *tf = ft->data;
+
+	imcb_log( tf->ic, "File transfer aborted: %s", reason );
+}
+
+/*
+ * Parses an incoming bytestream request and calls jabber_bs_handshake on success.
+ */
+int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode)
+{
+	char *sid, *ini_jid, *tgt_jid, *mode, *iq_id;
+	struct jabber_data *jd = ic->proto_data;
+	struct jabber_transfer *tf = NULL;
+	GSList *tflist;
+	struct bs_transfer *bt;
+	GSList *shlist=NULL;
+	struct xt_node *shnode;
+
+	sha1_state_t sha;
+	char hash_hex[41];
+	unsigned char hash[20];
+	int i;
+	
+	if( !(iq_id   = xt_find_attr( node, "id" ) ) ||
+	    !(ini_jid = xt_find_attr( node, "from" ) ) ||
+	    !(tgt_jid = xt_find_attr( node, "to" ) ) ||
+	    !(sid     = xt_find_attr( qnode, "sid" ) ) )
+	{
+		imcb_log( ic, "WARNING: Received incomplete SI bytestream request");
+		return XT_HANDLED;
+	}
+
+	if( ( mode = xt_find_attr( qnode, "mode" ) ) &&
+	      ( strcmp( mode, "tcp" ) != 0 ) ) 
+	{
+		imcb_log( ic, "WARNING: Received SI Request for unsupported bytestream mode %s", xt_find_attr( qnode, "mode" ) );
+		return XT_HANDLED;
+	}
+
+	shnode = qnode->children;
+	while( ( shnode = xt_find_node( shnode, "streamhost" ) ) )
+	{
+		char *jid, *host;
+		int port;
+		if( ( jid = xt_find_attr( shnode, "jid" ) ) &&
+		    ( host = xt_find_attr( shnode, "host" ) ) &&
+		    ( ( port = atoi( xt_find_attr( shnode, "port" ) ) ) ) )
+		{
+			jabber_streamhost_t *sh = g_new0( jabber_streamhost_t, 1 );
+			sh->jid = g_strdup(jid);
+			sh->host = g_strdup(host);
+			sprintf( sh->port, "%u", port );
+			shlist = g_slist_append( shlist, sh );
+		}
+		shnode = shnode->next;
+	}
+	
+	if( !shlist )
+	{
+		imcb_log( ic, "WARNING: Received incomplete SI bytestream request, no parseable streamhost entries");
+		return XT_HANDLED;
+	}
+
+	/* Let's see if we can find out what this bytestream should be for... */
+
+	for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) )
+	{
+		struct jabber_transfer *tft = tflist->data;
+		if( ( strcmp( tft->sid, sid ) == 0 ) &&
+		    ( strcmp( tft->ini_jid, ini_jid ) == 0 ) &&
+		    ( strcmp( tft->tgt_jid, tgt_jid ) == 0 ) )
+		{
+		    	tf = tft;
+			break;
+		}
+	}
+
+	if (!tf) 
+	{
+		imcb_log( ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid );
+		return XT_HANDLED;
+	}
+
+	/* iq_id and canceled can be reused since SI is done */
+	g_free( tf->iq_id );
+	tf->iq_id = g_strdup( iq_id );
+
+	tf->ft->canceled = jabber_bs_canceled;
+
+	/* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */
+	sha1_init( &sha );
+	sha1_append( &sha, (unsigned char*) sid, strlen( sid ) );
+	sha1_append( &sha, (unsigned char*) ini_jid, strlen( ini_jid ) );
+	sha1_append( &sha, (unsigned char*) tgt_jid, strlen( tgt_jid ) );
+	sha1_finish( &sha, hash );
+	
+	for( i = 0; i < 20; i ++ )
+		sprintf( hash_hex + i * 2, "%02x", hash[i] );
+		
+	bt = g_new0( struct bs_transfer, 1 );
+	bt->tf = tf;
+	bt->streamhosts = shlist;
+	bt->sh = shlist->data;
+	bt->phase = BS_PHASE_CONNECT;
+	bt->pseudoadr = g_strdup( hash_hex );
+	tf->streamhandle = bt;
+	tf->ft->free = jabber_bs_free_transfer;
+
+	jabber_bs_recv_handshake( bt, 0, 0 ); 
+
+	return XT_HANDLED;
+}
+
+/*
+ * This is what a protocol handshake can look like in cooperative multitasking :)
+ * Might be confusing at first because it's called from different places and is recursing.
+ * (places being the event thread, bs_request, bs_handshake_abort, and itself)
+ *
+ * All in all, it turned out quite nice :)
+ */
+gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition cond )
+{
+
+	struct bs_transfer *bt = data;
+	short revents;
+
+	if ( !jabber_bs_poll( bt, fd, &revents ) )
+		return FALSE;
+	
+	switch( bt->phase ) 
+	{
+	case BS_PHASE_CONNECT:
+		{
+			struct addrinfo hints, *rp;
+
+			memset( &hints, 0, sizeof( struct addrinfo ) );
+			hints.ai_socktype = SOCK_STREAM;
+
+			if ( getaddrinfo( bt->sh->host, bt->sh->port, &hints, &rp ) != 0 )
+				return jabber_bs_abort( bt, "getaddrinfo() failed: %s", strerror( errno ) );
+
+			ASSERTSOCKOP( bt->tf->fd = fd = socket( rp->ai_family, rp->ai_socktype, 0 ), "Opening socket" );
+
+			sock_make_nonblocking( fd );
+
+			imcb_log( bt->tf->ic, "File %s: Connecting to streamhost %s:%s", bt->tf->ft->file_name, bt->sh->host, bt->sh->port );
+
+			if( ( connect( fd, rp->ai_addr, rp->ai_addrlen ) == -1 ) &&
+			    ( errno != EINPROGRESS ) )
+				return jabber_bs_abort( bt , "connect() failed: %s", strerror( errno ) );
+
+			freeaddrinfo( rp );
+
+			bt->phase = BS_PHASE_CONNECTED;
+			
+			bt->tf->watch_out = b_input_add( fd, GAIM_INPUT_WRITE, jabber_bs_recv_handshake, bt );
+
+			/* since it takes forever(3mins?) till connect() fails on itself we schedule a timeout */
+			bt->connect_timeout = b_timeout_add( JABBER_BS_CONTIMEOUT * 1000, jabber_bs_connect_timeout, bt );
+
+			bt->tf->watch_in = 0;
+			return FALSE;
+		}
+	case BS_PHASE_CONNECTED:
+		{
+			struct {
+				unsigned char ver;
+				unsigned char nmethods;
+				unsigned char method;
+			} socks5_hello = {
+				.ver = 5,
+				.nmethods = 1,
+				.method = 0x00 /* no auth */
+				/* one could also implement username/password. If you know
+				 * a jabber client or proxy that actually does it, tell me.
+				 */
+			};
+			
+			ASSERTSOCKOP( send( fd, &socks5_hello, sizeof( socks5_hello ) , 0 ), "Sending auth request" );
+
+			bt->phase = BS_PHASE_REQUEST;
+
+			bt->tf->watch_in = b_input_add( fd, GAIM_INPUT_READ, jabber_bs_recv_handshake, bt );
+
+			bt->tf->watch_out = 0;
+			return FALSE;
+		}
+	case BS_PHASE_REQUEST:
+		{
+			struct socks5_message socks5_connect = 
+			{
+				.ver = 5,
+				.cmdrep.cmd = 0x01,
+				.rsv = 0,
+				.atyp = 0x03,
+				.addrlen = strlen( bt->pseudoadr ),
+				.port = 0
+			};
+			int ret;
+			char buf[2];
+
+			/* If someone's trying to be funny and sends only one byte at a time we'll fail :) */
+			ASSERTSOCKOP( ret = recv( fd, buf, 2, 0 ) , "Receiving auth reply" );
+
+			if( !( ret == 2 ) ||
+			    !( buf[0] == 5 ) ||
+			    !( buf[1] == 0 ) )
+				return jabber_bs_abort( bt, "Auth not accepted by streamhost (reply: len=%d, ver=%d, status=%d)",
+									ret, buf[0], buf[1] );
+
+			/* copy hash into connect message */
+			memcpy( socks5_connect.address, bt->pseudoadr, socks5_connect.addrlen );
+
+			ASSERTSOCKOP( send( fd, &socks5_connect, sizeof( struct socks5_message ), 0 ) , "Sending SOCKS5 Connect" );
+
+			bt->phase = BS_PHASE_REPLY;
+
+			return TRUE;
+		}
+	case BS_PHASE_REPLY:
+		{
+			struct socks5_message socks5_reply;
+			int ret;
+
+			if ( !( ret = jabber_bs_peek( bt, &socks5_reply, sizeof( struct socks5_message ) ) ) )
+				return FALSE;
+
+			if ( ret < sizeof( socks5_reply ) )
+				return TRUE;
+
+			if( !( socks5_reply.ver == 5 ) ||
+			    !( socks5_reply.cmdrep.rep == 0 ) ||
+			    !( socks5_reply.atyp == 3 ) ||
+			    !( socks5_reply.addrlen == 40 ) )
+				return jabber_bs_abort( bt, "SOCKS5 CONNECT failed (reply: ver=%d, rep=%d, atyp=%d, addrlen=%d", 
+					socks5_reply.ver,
+					socks5_reply.cmdrep.rep,
+					socks5_reply.atyp,
+					socks5_reply.addrlen);
+
+			if( bt->tf->ft->sending )
+				jabber_bs_send_activate( bt );
+			else
+				jabber_bs_recv_answer_request( bt );
+
+			return FALSE;
+		}
+	default:
+		/* BUG */
+		imcb_log( bt->tf->ic, "BUG in file transfer code: undefined handshake phase" );
+
+		bt->tf->watch_in = 0;
+		return FALSE;
+	}
+}
+
+/*
+ * If the handshake failed we can try the next streamhost, if there is one.
+ * An intelligent sender would probably specify himself as the first streamhost and
+ * a proxy as the second (Kopete and PSI are examples here). That way, a (potentially) 
+ * slow proxy is only used if neccessary. This of course also means, that the timeout
+ * per streamhost should be kept short. If one or two firewalled adresses are specified,
+ * they have to timeout first before a proxy is tried.
+ */
+gboolean jabber_bs_recv_handshake_abort( struct bs_transfer *bt, char *error )
+{
+	struct jabber_transfer *tf = bt->tf;
+	struct xt_node *reply, *iqnode;
+	GSList *shlist;
+
+	imcb_log( tf->ic, "Transferring file %s: connection to streamhost %s:%s failed (%s)", 
+		  tf->ft->file_name, 
+		  bt->sh->host,
+		  bt->sh->port,
+		  error );
+
+	/* Alright, this streamhost failed, let's try the next... */
+	bt->phase = BS_PHASE_CONNECT;
+	shlist = g_slist_find( bt->streamhosts, bt->sh );
+	if( shlist && shlist->next )
+	{
+		bt->sh = shlist->next->data;
+		return jabber_bs_recv_handshake( bt, 0, 0 );
+	}
+
+
+	/* out of stream hosts */
+
+	iqnode = jabber_make_packet( "iq", "result", tf->ini_jid, NULL );
+	reply = jabber_make_error_packet( iqnode, "item-not-found", "cancel" , "404" );
+	xt_free_node( iqnode );
+
+	xt_add_attr( reply, "id", tf->iq_id );
+		
+	if( !jabber_write_packet( tf->ic, reply ) )
+		imcb_log( tf->ic, "WARNING: Error transmitting bytestream response" );
+	xt_free_node( reply );
+
+	imcb_file_canceled( tf->ft, "couldn't connect to any streamhosts" );
+
+	bt->tf->watch_in = 0;
+	return FALSE;
+}
+
+/* 
+ * After the SOCKS5 handshake succeeds we need to inform the initiator which streamhost we chose.
+ * If he is the streamhost himself, he might already know that. However, if it's a proxy,
+ * the initiator will have to make a connection himself.
+ */
+void jabber_bs_recv_answer_request( struct bs_transfer *bt )
+{
+	struct jabber_transfer *tf = bt->tf;
+	struct xt_node *reply;
+
+	imcb_log( tf->ic, "File %s: established SOCKS5 connection to %s:%s", 
+		  tf->ft->file_name, 
+		  bt->sh->host,
+		  bt->sh->port );
+
+	tf->ft->data = tf;
+	tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_recv_read, bt );
+	tf->ft->write_request = jabber_bs_recv_write_request;
+
+	reply = xt_new_node( "streamhost-used", NULL, NULL );
+	xt_add_attr( reply, "jid", bt->sh->jid );
+
+	reply = xt_new_node( "query", NULL, reply );
+	xt_add_attr( reply, "xmlns", XMLNS_BYTESTREAMS );
+
+	reply = jabber_make_packet( "iq", "result", tf->ini_jid, reply );
+
+	xt_add_attr( reply, "id", tf->iq_id );
+		
+	if( !jabber_write_packet( tf->ic, reply ) )
+		imcb_file_canceled( tf->ft, "Error transmitting bytestream response" );
+	xt_free_node( reply );
+}
+
+/* 
+ * This function is called from write_request directly. If no data is available, it will install itself
+ * as a watcher for input on fd and once that happens, deliver the data and unschedule itself again.
+ */
+gboolean jabber_bs_recv_read( gpointer data, gint fd, b_input_condition cond )
+{
+	int ret;
+	struct bs_transfer *bt = data;
+	struct jabber_transfer *tf = bt->tf;
+
+	if( fd != 0 ) /* called via event thread */
+	{
+		tf->watch_in = 0;
+		ASSERTSOCKOP( ret = recv( fd, tf->ft->buffer, sizeof( tf->ft->buffer ), 0 ) , "Receiving" );
+	}
+	else
+	{
+		/* called directly. There might not be any data available. */
+		if( ( ( ret = recv( tf->fd, tf->ft->buffer, sizeof( tf->ft->buffer ), 0 ) ) == -1 ) &&
+		    ( errno != EAGAIN ) )
+		    return jabber_bs_abort( bt, "Receiving: %s", strerror( errno ) );
+
+		if( ( ret == -1 ) && ( errno == EAGAIN ) )
+		{
+			tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_recv_read, bt );
+			return FALSE;
+		}
+	}
+
+	/* shouldn't happen since we know the file size */
+	if( ret == 0 )
+		return jabber_bs_abort( bt, "Remote end closed connection" );
+	
+	if( tf->bytesread == 0 )
+		tf->ft->started = time( NULL );
+
+	tf->bytesread += ret;
+
+	tf->ft->write( tf->ft, tf->ft->buffer, ret );	
+
+	return FALSE;
+}
+
+/* 
+ * imc callback that is invoked when it is ready to receive some data.
+ */
+gboolean jabber_bs_recv_write_request( file_transfer_t *ft )
+{
+	struct jabber_transfer *tf = ft->data;
+
+	if( tf->watch_in )
+	{
+		imcb_file_canceled( ft, "BUG in jabber file transfer: write_request called when already watching for input" );
+		return FALSE;
+	}
+	
+	jabber_bs_recv_read( tf->streamhandle, 0 , 0 );
+
+	return TRUE;
+}
+
+/* 
+ * Issues a write_request to imc.
+ * */
+gboolean jabber_bs_send_can_write( gpointer data, gint fd, b_input_condition cond )
+{
+	struct bs_transfer *bt = data;
+
+	bt->tf->watch_out = 0;
+
+	bt->tf->ft->write_request( bt->tf->ft );
+
+	return FALSE;
+}
+
+/*
+ * This should only be called if we can write, so just do it.
+ * Add a write watch so we can write more during the next cycle (if possible).
+ */
+gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, unsigned int len )
+{
+	struct jabber_transfer *tf = ft->data;
+	struct bs_transfer *bt = tf->streamhandle;
+	int ret;
+
+	if( tf->watch_out )
+		return jabber_bs_abort( bt, "BUG: write() called while watching " );
+	
+	/* TODO: catch broken pipe */
+	ASSERTSOCKOP( ret = send( tf->fd, buffer, len, 0 ), "Sending" );
+
+	if( tf->byteswritten == 0 )
+		tf->ft->started = time( NULL );
+
+	tf->byteswritten += ret;
+	
+	/* TODO: this should really not be fatal */
+	if( ret < len )
+		return jabber_bs_abort( bt, "send() sent %d instead of %d (send buffer too big!)", ret, len );
+
+	bt->tf->watch_out = b_input_add( tf->fd, GAIM_INPUT_WRITE, jabber_bs_send_can_write, bt );
+		
+	return TRUE;
+}
+
+/*
+ * Handles the reply by the receiver containing the used streamhost.
+ */
+static xt_status jabber_bs_send_handle_reply(struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) {
+	struct jabber_transfer *tf = NULL;
+	struct jabber_data *jd = ic->proto_data;
+	struct bs_transfer *bt;
+	GSList *tflist;
+	struct xt_node *c;
+	char *sid, *jid;
+
+	if( !( c = xt_find_node( node->children, "query" ) ) ||
+	    !( c = xt_find_node( c->children, "streamhost-used" ) ) ||
+	    !( jid = xt_find_attr( c, "jid" ) ) )
+
+	{
+		imcb_log( ic, "WARNING: Received incomplete bytestream reply" );
+		return XT_HANDLED;
+	}
+	
+	if( !( c = xt_find_node( orig->children, "query" ) ) ||
+	    !( sid = xt_find_attr( c, "sid" ) ) )
+	{
+		imcb_log( ic, "WARNING: Error parsing request corresponding to the incoming bytestream reply" );
+		return XT_HANDLED;
+	}
+
+	/* Let's see if we can find out what this bytestream should be for... */
+
+	for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) )
+	{
+		struct jabber_transfer *tft = tflist->data;
+		if( ( strcmp( tft->sid, sid ) == 0 ) )
+		{
+		    	tf = tft;
+			break;
+		}
+	}
+
+	if( !tf )
+	{
+		imcb_log( ic, "WARNING: Received SOCKS5 bytestream reply to unknown request" );
+		return XT_HANDLED;
+	}
+
+	bt = tf->streamhandle;
+
+	tf->accepted = TRUE;
+
+	if( strcmp( jid, tf->ini_jid ) == 0 )
+	{
+		/* we're streamhost and target */
+		if( bt->phase == BS_PHASE_REPLY )
+		{
+			/* handshake went through, let's start transferring */
+			tf->ft->write_request( tf->ft );
+		}
+	} else
+	{
+		/* using a proxy */
+		GSList *shlist;
+		for( shlist = jd->streamhosts ; shlist ; shlist = g_slist_next( shlist ) )
+		{
+			jabber_streamhost_t *sh = shlist->data;
+			if( strcmp( sh->jid, jid ) == 0 )
+			{
+				bt->sh = sh;
+				jabber_bs_recv_handshake( bt, 0, 0 );
+				return XT_HANDLED;
+			}
+		}
+
+		imcb_log( ic, "WARNING: Received SOCKS5 bytestream reply with unknown streamhost %s", jid );
+	}
+
+	return XT_HANDLED;
+}
+
+/* 
+ * Tell the proxy to activate the stream. Looks like this:
+ *
+ * <iq type=set>
+ * 	<query xmlns=bs sid=sid>
+ * 		<activate>tgt_jid</activate>
+ * 	</query>
+ * </iq>
+ */
+void jabber_bs_send_activate( struct bs_transfer *bt )
+{
+	struct xt_node *node;
+
+	node = xt_new_node( "activate", bt->tf->tgt_jid, NULL );
+	node = xt_new_node( "query", NULL, node );
+	xt_add_attr( node, "xmlns", XMLNS_BYTESTREAMS );
+	xt_add_attr( node, "sid", bt->tf->sid );
+	node = jabber_make_packet( "iq", "set", bt->sh->jid, node );
+
+	jabber_cache_add( bt->tf->ic, node, jabber_bs_send_handle_activate );
+
+	jabber_write_packet( bt->tf->ic, node );
+}
+
+/*
+ * The proxy has activated the bytestream.
+ * We can finally start pushing some data out.
+ */
+static xt_status jabber_bs_send_handle_activate( struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
+{
+	char *sid;
+	GSList *tflist;
+	struct jabber_transfer *tf;
+	struct xt_node *query;
+	struct jabber_data *jd = ic->proto_data;
+
+	query = xt_find_node( orig->children, "query" );
+	sid = xt_find_attr( query, "sid" );
+
+	for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) )
+	{
+		struct jabber_transfer *tft = tflist->data;
+		if( ( strcmp( tft->sid, sid ) == 0 ) )
+		{
+		    	tf = tft;
+			break;
+		}
+	}
+
+	if( !tf )
+	{
+		imcb_log( ic, "WARNING: Received SOCKS5 bytestream activation for unknown stream" );
+		return XT_HANDLED;
+	}
+
+	/* handshake went through, let's start transferring */
+	tf->ft->write_request( tf->ft );
+
+	return XT_HANDLED;
+}
+
+/*
+ * Starts a bytestream.
+ */
+gboolean jabber_bs_send_start( struct jabber_transfer *tf )
+{
+	char host[INET6_ADDRSTRLEN];
+	struct bs_transfer *bt;
+	sha1_state_t sha;
+	char hash_hex[41];
+	unsigned char hash[20];
+	int i,ret;
+	struct jabber_data *jd = tf->ic->proto_data;
+	jabber_streamhost_t sh;
+	GSList *streamhosts = jd->streamhosts;
+
+	/* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */
+	sha1_init( &sha );
+	sha1_append( &sha, (unsigned char*) tf->sid, strlen( tf->sid ) );
+	sha1_append( &sha, (unsigned char*) tf->ini_jid, strlen( tf->ini_jid ) );
+	sha1_append( &sha, (unsigned char*) tf->tgt_jid, strlen( tf->tgt_jid ) );
+	sha1_finish( &sha, hash );
+	
+	for( i = 0; i < 20; i ++ )
+		sprintf( hash_hex + i * 2, "%02x", hash[i] );
+		
+	bt = g_new0( struct bs_transfer, 1 );
+	bt->tf = tf;
+	bt->phase = BS_PHASE_CONNECT;
+	bt->pseudoadr = g_strdup( hash_hex );
+	tf->streamhandle = bt;
+	tf->ft->free = jabber_bs_free_transfer;
+	tf->ft->canceled = jabber_bs_canceled;
+
+	if ( !jabber_bs_send_listen( bt, &tf->saddr, sh.host = host, sh.port ) )
+		return FALSE;
+
+	bt->tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_send_handshake, bt );
+	bt->connect_timeout = b_timeout_add( JABBER_BS_LISTEN_TIMEOUT * 1000, jabber_bs_connect_timeout, bt );
+
+	sh.jid = tf->ini_jid;
+
+	/* temporarily add listen address to streamhosts, send the request and remove it */
+	streamhosts = g_slist_prepend( streamhosts, &sh );
+	ret = jabber_bs_send_request( tf, streamhosts);
+	streamhosts = g_slist_remove( streamhosts, &sh );
+
+	return ret;
+}
+
+gboolean jabber_bs_send_request( struct jabber_transfer *tf, GSList *streamhosts )
+{
+	struct xt_node *shnode, *query, *iq;
+
+	query = xt_new_node( "query", NULL, NULL );
+	xt_add_attr( query, "xmlns", XMLNS_BYTESTREAMS );
+	xt_add_attr( query, "sid", tf->sid );
+	xt_add_attr( query, "mode", "tcp" );
+
+	while( streamhosts ) {
+		jabber_streamhost_t *sh = streamhosts->data;
+		shnode = xt_new_node( "streamhost", NULL, NULL );
+		xt_add_attr( shnode, "jid", sh->jid );
+		xt_add_attr( shnode, "host", sh->host );
+		xt_add_attr( shnode, "port", sh->port );
+
+		xt_add_child( query, shnode );
+
+		streamhosts = g_slist_next( streamhosts );
+	}
+
+
+	iq = jabber_make_packet( "iq", "set", tf->tgt_jid, query );
+	xt_add_attr( iq, "from", tf->ini_jid );
+
+	jabber_cache_add( tf->ic, iq, jabber_bs_send_handle_reply );
+
+	if( !jabber_write_packet( tf->ic, iq ) )
+		imcb_file_canceled( tf->ft, "Error transmitting bytestream request" );
+	return TRUE;
+}
+
+gboolean jabber_bs_send_handshake_abort(struct bs_transfer *bt, char *error )
+{
+	struct jabber_transfer *tf = bt->tf;
+
+	/* TODO: did the receiver get here somehow??? */
+	imcb_log( tf->ic, "Transferring file %s: SOCKS5 handshake failed: %s", 
+		  tf->ft->file_name, 
+		  error );
+
+	imcb_file_canceled( tf->ft, error );
+
+	return FALSE;
+}
+
+/*
+ * Creates a listening socket and returns it in saddr_ptr.
+ */
+gboolean jabber_bs_send_listen( struct bs_transfer *bt, struct sockaddr_storage *saddr, char *host, char *port )
+{
+	struct jabber_transfer *tf = bt->tf;
+	int fd;
+	char hostname[ HOST_NAME_MAX + 1 ];
+	struct addrinfo hints, *rp;
+	socklen_t ssize = sizeof( struct sockaddr_storage );
+
+	/* won't be long till someone asks for this to be configurable :) */
+
+	ASSERTSOCKOP( gethostname( hostname, sizeof( hostname ) ), "gethostname()" );
+
+	memset( &hints, 0, sizeof( struct addrinfo ) );
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_flags = AI_NUMERICSERV;
+
+	if ( getaddrinfo( hostname, "0", &hints, &rp ) != 0 )
+		return jabber_bs_abort( bt, "getaddrinfo()" );
+
+	memcpy( saddr, rp->ai_addr, rp->ai_addrlen );
+
+	ASSERTSOCKOP( fd = tf->fd = socket( saddr->ss_family, SOCK_STREAM, 0 ), "Opening socket" );
+
+	ASSERTSOCKOP( bind( fd, ( struct sockaddr *)saddr, rp->ai_addrlen ), "Binding socket" );
+	
+	freeaddrinfo( rp );
+
+	ASSERTSOCKOP( listen( fd, 1 ), "Making socket listen" );
+
+	if ( !inet_ntop( saddr->ss_family, saddr->ss_family == AF_INET ?
+			( void * )&( ( struct sockaddr_in * ) saddr )->sin_addr.s_addr : ( void * )&( ( struct sockaddr_in6 * ) saddr )->sin6_addr.s6_addr
+			, host, INET6_ADDRSTRLEN ) )
+		return jabber_bs_abort( bt, "inet_ntop failed on listening socket" );
+
+	ASSERTSOCKOP( getsockname( fd, ( struct sockaddr *)saddr, &ssize ), "Getting socket name" );
+
+	if( saddr->ss_family == AF_INET )
+		sprintf( port, "%d", ntohs( ( ( struct sockaddr_in *) saddr )->sin_port ) );
+	else
+		sprintf( port, "%d", ntohs( ( ( struct sockaddr_in6 *) saddr )->sin6_port ) );
+
+	return TRUE;
+}
+
+/*
+ * SOCKS5BYTESTREAM protocol for the sender
+ */
+gboolean jabber_bs_send_handshake( gpointer data, gint fd, b_input_condition cond )
+{
+	struct bs_transfer *bt = data;
+	struct jabber_transfer *tf = bt->tf;
+	short revents;
+
+	if ( !jabber_bs_poll( bt, fd, &revents ) )
+		return FALSE;
+	
+	switch( bt->phase ) 
+	{
+	case BS_PHASE_CONNECT:
+		{
+			struct sockaddr_storage clt_addr;
+			socklen_t ssize = sizeof( clt_addr );
+			
+			/* Connect */
+
+			ASSERTSOCKOP( tf->fd = accept( fd, (struct sockaddr *) &clt_addr, &ssize ), "Accepting connection" );
+
+			closesocket( fd );
+			fd = tf->fd;
+			sock_make_nonblocking( fd );
+			
+			bt->phase = BS_PHASE_CONNECTED;
+
+			bt->tf->watch_in = b_input_add( fd, GAIM_INPUT_READ, jabber_bs_send_handshake, bt );
+			return FALSE;
+		}
+	case BS_PHASE_CONNECTED:
+		{
+			int ret, have_noauth=FALSE;
+			struct {
+				unsigned char ver;
+				unsigned char method;
+			} socks5_auth_reply = { .ver = 5, .method = 0 };
+			struct {
+				unsigned char ver;
+				unsigned char nmethods;
+				unsigned char method;
+			} socks5_hello;
+
+			if( !( ret = jabber_bs_peek( bt, &socks5_hello, sizeof( socks5_hello ) ) ) )
+				return FALSE;
+
+			if( ret < sizeof( socks5_hello ) )
+				return TRUE;
+
+			if( !( socks5_hello.ver == 5 ) ||
+			    !( socks5_hello.nmethods >= 1 ) ||
+			    !( socks5_hello.nmethods < 32 ) )
+				return jabber_bs_abort( bt, "Invalid auth request ver=%d nmethods=%d method=%d", socks5_hello.ver, socks5_hello.nmethods, socks5_hello.method );
+
+			have_noauth = socks5_hello.method == 0;
+
+			if( socks5_hello.nmethods > 1 )
+			{
+				char mbuf[32];
+				int i;
+				ASSERTSOCKOP( ret = recv( fd, mbuf, socks5_hello.nmethods - 1, 0 ) , "Receiving auth methods" );
+				if( ret < ( socks5_hello.nmethods - 1 ) )
+					return jabber_bs_abort( bt, "Partial auth request");
+				for( i = 0 ; !have_noauth && ( i < socks5_hello.nmethods - 1 ) ; i ++ )
+					if( mbuf[i] == 0 )
+						have_noauth = TRUE;
+			}
+			
+			if( !have_noauth )
+				return jabber_bs_abort( bt, "Auth request didn't include no authentication" );
+
+			ASSERTSOCKOP( send( fd, &socks5_auth_reply, sizeof( socks5_auth_reply ) , 0 ), "Sending auth reply" );
+
+			bt->phase = BS_PHASE_REQUEST;
+
+			return TRUE;
+		}
+	case BS_PHASE_REQUEST:
+		{
+			struct socks5_message socks5_connect;
+			int msgsize = sizeof( struct socks5_message );
+
+			if( !jabber_bs_peek( bt, &socks5_connect, msgsize ) )
+				return FALSE;
+
+			if( !( socks5_connect.ver == 5) ||
+			    !( socks5_connect.cmdrep.cmd == 1 ) ||
+			    !( socks5_connect.atyp == 3 ) ||
+			    !(socks5_connect.addrlen == 40 ) )
+				return jabber_bs_abort( bt, "Invalid SOCKS5 Connect message (addrlen=%d, ver=%d, cmd=%d, atyp=%d)", socks5_connect.addrlen, socks5_connect.ver, socks5_connect.cmdrep.cmd, socks5_connect.atyp );
+			if( !( memcmp( socks5_connect.address, bt->pseudoadr, 40 ) == 0 ) )
+				return jabber_bs_abort( bt, "SOCKS5 Connect message contained wrong digest");
+
+			socks5_connect.cmdrep.rep = 0;
+
+			ASSERTSOCKOP( send( fd, &socks5_connect, msgsize, 0 ), "Sending connect reply" );
+
+			bt->phase = BS_PHASE_REPLY;
+
+			imcb_log( tf->ic, "File %s: SOCKS5 handshake successful! Transfer about to start...", tf->ft->file_name );
+
+			if( tf->accepted )
+			{
+				/* streamhost-used message came already in(possible?), let's start sending */
+				tf->ft->write_request( tf->ft );
+			}
+
+			tf->watch_in = 0;
+			return FALSE;
+
+		}
+	default:
+		/* BUG */
+		imcb_log( bt->tf->ic, "BUG in file transfer code: undefined handshake phase" );
+
+		bt->tf->watch_in = 0;
+		return FALSE;
+	}
+}
+#undef ASSERTSOCKOP
Index: protocols/jabber/si.c
===================================================================
--- protocols/jabber/si.c	(revision 1ba7e8f1d28c4876ea0b787f1e4ebb5607f48895)
+++ protocols/jabber/si.c	(revision 1ba7e8f1d28c4876ea0b787f1e4ebb5607f48895)
@@ -0,0 +1,438 @@
+/***************************************************************************\
+*                                                                           *
+*  BitlBee - An IRC to IM gateway                                           *
+*  Jabber module - SI packets                                               *
+*                                                                           *
+*  Copyright 2007 Uli Meis <a.sporto+bee@gmail.com>                         *
+*                                                                           *
+*  This program is free software; you can redistribute it and/or modify     *
+*  it under the terms of the GNU General Public License as published by     *
+*  the Free Software Foundation; either version 2 of the License, or        *
+*  (at your option) any later version.                                      *
+*                                                                           *
+*  This program is distributed in the hope that it will be useful,          *
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
+*  GNU General Public License for more details.                             *
+*                                                                           *
+*  You should have received a copy of the GNU General Public License along  *
+*  with this program; if not, write to the Free Software Foundation, Inc.,  *
+*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
+*                                                                           *
+\***************************************************************************/
+
+#include "jabber.h"
+#include "sha1.h"
+
+void jabber_si_answer_request( file_transfer_t *ft );
+int jabber_si_send_request(struct im_connection *ic, char *who, struct jabber_transfer *tf );
+
+/* file_transfer free() callback */
+void jabber_si_free_transfer( file_transfer_t *ft)
+{
+	struct jabber_transfer *tf = ft->data;
+	struct jabber_data *jd = tf->ic->proto_data;
+
+	if ( tf->watch_in )
+		b_event_remove( tf->watch_in );
+
+	jd->filetransfers = g_slist_remove( jd->filetransfers, tf );
+
+	if( tf->fd )
+	{
+		close( tf->fd );
+		tf->fd = 0;
+	}
+
+	g_free( tf->ini_jid );
+	g_free( tf->tgt_jid );
+	g_free( tf->iq_id );
+	g_free( tf->sid );
+}
+
+/* file_transfer finished() callback */
+void jabber_si_finished( file_transfer_t *ft )
+{
+	struct jabber_transfer *tf = ft->data;
+
+	imcb_log( tf->ic, "File %s transferred successfully!" , ft->file_name );
+}
+
+/* file_transfer canceled() callback */
+void jabber_si_canceled( file_transfer_t *ft, char *reason )
+{
+	struct jabber_transfer *tf = ft->data;
+	struct xt_node *reply, *iqnode;
+
+	if( tf->accepted )
+		return;
+	
+	iqnode = jabber_make_packet( "iq", "error", tf->ini_jid, NULL );
+	xt_add_attr( iqnode, "id", tf->iq_id );
+	reply = jabber_make_error_packet( iqnode, "forbidden", "cancel", "403" );
+	xt_free_node( iqnode );
+	
+	if( !jabber_write_packet( tf->ic, reply ) )
+		imcb_log( tf->ic, "WARNING: Error generating reply to file transfer request" );
+	xt_free_node( reply );
+
+}
+
+void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who ) 
+{
+	struct jabber_transfer *tf;
+	struct jabber_data *jd = ic->proto_data;
+	char *server  = jd->server;
+
+	imcb_log( ic, "Trying to send %s(%zd bytes) to %s", ft->file_name, ft->file_size, who );
+
+	tf = g_new0( struct jabber_transfer, 1 );
+
+	tf->ic = ic;
+	tf->ft = ft;
+	tf->ft->data = tf;
+	tf->ft->free = jabber_si_free_transfer;
+	tf->ft->finished = jabber_si_finished;
+	ft->write = jabber_bs_send_write;
+
+	jd->filetransfers = g_slist_prepend( jd->filetransfers, tf );
+
+	/* query the buddy's features */
+	jabber_iq_query_features( ic, who );
+
+	/* query proxies from the server */
+	if( !jd->have_streamhosts )
+		jabber_iq_query_server( ic, server, XMLNS_DISCO_ITEMS );
+
+	/* send the request to our buddy */
+	jabber_si_send_request( ic, who, tf );
+
+	/* and start the receive logic */
+	imcb_file_recv_start( ft );
+}
+
+/*
+ * First function that gets called when a file transfer request comes in.
+ * A lot to parse.
+ *
+ * We choose a stream type from the options given by the initiator.
+ * Then we wait for imcb to call the accept or cancel callbacks.
+ */
+int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, struct xt_node *sinode)
+{
+	struct xt_node *c, *d, *reply;
+	char *sid, *ini_jid, *tgt_jid, *iq_id, *s, *ext_jid;
+	struct jabber_buddy *bud;
+	int requestok = FALSE;
+	char *name;
+	size_t size;
+	struct jabber_transfer *tf;
+	struct jabber_data *jd = ic->proto_data;
+	file_transfer_t *ft;
+	
+	/* All this means we expect something like this: ( I think )
+	 * <iq from=... to=... id=...>
+	 * 	<si id=id xmlns=si profile=ft>
+	 * 		<file xmlns=ft/>
+	 * 		<feature xmlns=feature>
+	 * 			<x xmlns=xdata type=submit>
+	 * 				<field var=stream-method>
+	 *
+	 */
+	if( !( ini_jid 		= xt_find_attr(   node, "from" ) 			) ||
+	    !( tgt_jid 		= xt_find_attr(   node, "to" ) 				) ||
+	    !( iq_id 		= xt_find_attr(   node, "id" ) 				) ||
+	    !( sid 		= xt_find_attr( sinode, "id" ) 				) ||
+	    !( strcmp( xt_find_attr( sinode, "profile" ), XMLNS_FILETRANSFER ) == 0	) ||
+	    !( d 		= xt_find_node( sinode->children, "file" ) 		) ||
+	    !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_FILETRANSFER ) == 0 		) ||
+	    !( name 		= xt_find_attr( d, "name" ) 				) ||
+	    !( size 		= (size_t) atoll( xt_find_attr( d, "size" ) ) 		) ||
+	    !( d 		= xt_find_node( sinode->children, "feature" ) 		) ||
+	    !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_FEATURE ) == 0 		) ||
+	    !( d 		= xt_find_node( d->children, "x" ) 			) ||
+	    !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_XDATA ) == 0 			) ||
+	    !( strcmp( xt_find_attr( d, "type" ), "form" ) == 0 			) ||
+	    !( d 		= xt_find_node( d->children, "field" ) 			) ||
+	    !( strcmp( xt_find_attr( d, "var" ), "stream-method" ) == 0 		) )
+	{
+		imcb_log( ic, "WARNING: Received incomplete Stream Initiation request" );
+	} else
+	{
+		/* Check if we support one of the options */
+
+		c = d->children;
+		while( ( c = xt_find_node( c, "option" ) ) )
+			if( 	( d = xt_find_node( c->children, "value" ) ) &&
+				( strcmp( d->text, XMLNS_BYTESTREAMS ) == 0 ) )
+			{
+				requestok = TRUE;
+				break;
+			}
+
+		if ( !requestok )
+			imcb_log( ic, "WARNING: Unsupported file transfer request from %s", ini_jid);
+	}
+	
+	if ( requestok )
+	{
+		/* Figure out who the transfer should come frome... */
+
+		if( ( s = strchr( ini_jid, '/' ) ) )
+		{
+			if( ( bud = jabber_buddy_by_jid( ic, ini_jid, GET_BUDDY_EXACT ) ) )
+			{
+				bud->last_act = time( NULL );
+				ext_jid = bud->ext_jid ? : bud->bare_jid;
+			}
+			else
+				*s = 0; /* We need to generate a bare JID now. */
+		}
+
+		if( !( ft = imcb_file_send_start( ic, ext_jid, name, size ) ) )
+		{ 
+			imcb_log( ic, "WARNING: Error handling transfer request from %s", ini_jid);
+			requestok = FALSE;
+		}
+
+		*s = '/';
+	}
+
+	if ( !requestok )
+	{ 
+		reply = jabber_make_error_packet( node, "item-not-found", "cancel", NULL );
+		if (!jabber_write_packet( ic, reply ))
+			imcb_log( ic, "WARNING: Error generating reply to file transfer request" );
+		xt_free_node( reply );
+		return XT_HANDLED;
+	}
+
+	/* Request is fine. */
+
+	imcb_log( ic, "File transfer request from %s for %s (%zd kb). ", xt_find_attr( node, "from" ), name, size/1024 );
+
+	imcb_log( ic, "Accept the DCC transfer if you'd like the file. If you don't, issue the 'transfers reject' command.");
+
+	tf = g_new0( struct jabber_transfer, 1 );
+
+	tf->ini_jid = g_strdup( ini_jid );
+	tf->tgt_jid = g_strdup( tgt_jid );
+	tf->iq_id = g_strdup( iq_id );
+	tf->sid = g_strdup( sid );
+	tf->ic = ic;
+	tf->ft = ft;
+	tf->ft->data = tf;
+	tf->ft->accept = jabber_si_answer_request;
+	tf->ft->free = jabber_si_free_transfer;
+	tf->ft->finished = jabber_si_finished;
+	tf->ft->canceled = jabber_si_canceled;
+
+	jd->filetransfers = g_slist_prepend( jd->filetransfers, tf );
+
+	return XT_HANDLED;
+}
+
+/*
+ * imc called the accept callback which probably means that the user accepted this file transfer.
+ * We send our response to the initiator.
+ * In the next step, the initiator will send us a request for the given stream type.
+ * (currently that can only be a SOCKS5 bytestream)
+ */
+void jabber_si_answer_request( file_transfer_t *ft ) {
+	struct jabber_transfer *tf = ft->data;
+	struct xt_node *node, *sinode, *reply;
+
+	/* generate response, start with the SI tag */
+	sinode = xt_new_node( "si", NULL, NULL );
+	xt_add_attr( sinode, "xmlns", XMLNS_SI );
+	xt_add_attr( sinode, "profile", XMLNS_FILETRANSFER );
+	xt_add_attr( sinode, "id", tf->sid );
+
+	/* now the file tag */
+	node = xt_new_node( "file", NULL, NULL );
+	xt_add_attr( node, "xmlns", XMLNS_FILETRANSFER );
+
+	xt_add_child( sinode, node );
+
+	/* and finally the feature tag */
+	node = xt_new_node( "field", NULL, NULL );
+	xt_add_attr( node, "var", "stream-method" );
+	xt_add_attr( node, "type", "list-single" );
+
+	/* Currently all we can do. One could also implement in-band (IBB) */
+	xt_add_child( node, xt_new_node( "value", XMLNS_BYTESTREAMS, NULL ) );
+
+	node = xt_new_node( "x", NULL, node );
+	xt_add_attr( node, "xmlns", XMLNS_XDATA );
+	xt_add_attr( node, "type", "submit" );
+
+	node = xt_new_node( "feature", NULL, node );
+	xt_add_attr( node, "xmlns", XMLNS_FEATURE );
+
+	xt_add_child( sinode, node );
+
+	reply = jabber_make_packet( "iq", "result", tf->ini_jid, sinode );
+	xt_add_attr( reply, "id", tf->iq_id );
+	
+	if( !jabber_write_packet( tf->ic, reply ) )
+		imcb_log( tf->ic, "WARNING: Error generating reply to file transfer request" );
+	else
+		tf->accepted = TRUE;
+	xt_free_node( reply );
+}
+
+static xt_status jabber_si_handle_response(struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
+{
+	struct xt_node *c, *d;
+	char *ini_jid, *tgt_jid, *iq_id;
+	GSList *tflist;
+	struct jabber_transfer *tf=NULL;
+	struct jabber_data *jd = ic->proto_data;
+
+	if( !( tgt_jid = xt_find_attr( node, "from" ) ) ||
+	    !( ini_jid = xt_find_attr( node, "to" ) ) )
+	{
+		imcb_log( ic, "Invalid SI response from=%s to=%s", tgt_jid, ini_jid );
+		return XT_HANDLED;
+	}
+	
+	/* All this means we expect something like this: ( I think )
+	 * <iq from=... to=... id=...>
+	 * 	<si xmlns=si>
+	 * 	[	<file xmlns=ft/>    ] <-- not neccessary
+	 * 		<feature xmlns=feature>
+	 * 			<x xmlns=xdata type=submit>
+	 * 				<field var=stream-method>
+	 * 					<value>
+	 */
+	if( !( tgt_jid = xt_find_attr( node, "from" ) ) ||
+	    !( ini_jid = xt_find_attr( node, "to" ) ) ||
+	    !( iq_id   = xt_find_attr( node, "id" ) ) ||
+	    !( c = xt_find_node( node->children, "si" ) ) ||
+	    !( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_SI ) == 0 ) ||
+/*	    !( d = xt_find_node( c->children, "file" ) ) ||
+	    !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_FILETRANSFER ) == 0 ) || */
+	    !( d = xt_find_node( c->children, "feature" ) ) ||
+	    !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_FEATURE ) == 0 ) ||
+	    !( d = xt_find_node( d->children, "x" ) ) ||
+	    !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_XDATA ) == 0 ) ||
+	    !( strcmp( xt_find_attr( d, "type" ), "submit" ) == 0 ) ||
+	    !( d = xt_find_node( d->children, "field" ) ) ||
+	    !( strcmp( xt_find_attr( d, "var" ), "stream-method" ) == 0 ) ||
+	    !( d = xt_find_node( d->children, "value" ) ) )
+	{
+		imcb_log( ic, "WARNING: Received incomplete Stream Initiation response" );
+		return XT_HANDLED;
+	}
+
+	if( !( strcmp( d->text, XMLNS_BYTESTREAMS ) == 0 ) ) { 
+		/* since we should only have advertised what we can do and the peer should
+		 * only have chosen what we offered, this should never happen */
+		imcb_log( ic, "WARNING: Received invalid Stream Initiation response, method %s", d->text );
+			
+		return XT_HANDLED;
+	}
+	
+	/* Let's see if we can find out what this bytestream should be for... */
+
+	for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) )
+	{
+		struct jabber_transfer *tft = tflist->data;
+		if( ( strcmp( tft->iq_id, iq_id ) == 0 ) )
+		{
+		    	tf = tft;
+			break;
+		}
+	}
+
+	if (!tf) 
+	{
+		imcb_log( ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid );
+		return XT_HANDLED;
+	}
+
+	tf->ini_jid = g_strdup( ini_jid );
+	tf->tgt_jid = g_strdup( tgt_jid );
+
+	imcb_log( ic, "File %s: %s accepted the transfer!", tf->ft->file_name, tgt_jid );
+
+	jabber_bs_send_start( tf );
+
+	return XT_HANDLED;
+}
+
+int jabber_si_send_request(struct im_connection *ic, char *who, struct jabber_transfer *tf )
+{
+	struct xt_node *node, *sinode;
+	struct jabber_buddy *bud;
+
+	/* who knows how many bits the future holds :) */
+	char filesizestr[ 1 + ( int ) ( 0.301029995663981198f * sizeof( size_t ) * 8 ) ];
+
+	const char *methods[] = 
+	{  	
+		XMLNS_BYTESTREAMS,
+		//XMLNS_IBB,
+		NULL 
+	};
+	const char **m;
+	char *s;
+
+	/* Maybe we should hash this? */
+	tf->sid = g_strdup_printf( "BitlBeeJabberSID%d", tf->ft->local_id );
+	
+	if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) )
+		bud = jabber_buddy_by_ext_jid( ic, who, 0 );
+	else
+		bud = jabber_buddy_by_jid( ic, who, 0 );
+
+	/* start with the SI tag */
+	sinode = xt_new_node( "si", NULL, NULL );
+	xt_add_attr( sinode, "xmlns", XMLNS_SI );
+	xt_add_attr( sinode, "profile", XMLNS_FILETRANSFER );
+	xt_add_attr( sinode, "id", tf->sid );
+
+/*	if( mimetype ) 
+		xt_add_attr( node, "mime-type", mimetype ); */
+
+	/* now the file tag */
+/*	if( desc )
+ 		node = xt_new_node( "desc", descr, NULL ); */
+	node = xt_new_node( "range", NULL, NULL );
+
+	sprintf( filesizestr, "%zd", tf->ft->file_size );
+	node = xt_new_node( "file", NULL, node );
+	xt_add_attr( node, "xmlns", XMLNS_FILETRANSFER );
+	xt_add_attr( node, "name", tf->ft->file_name );
+	xt_add_attr( node, "size", filesizestr );
+/*	if (hash)
+		xt_add_attr( node, "hash", hash );
+	if (date)
+		xt_add_attr( node, "date", date ); */
+
+	xt_add_child( sinode, node );
+
+	/* and finally the feature tag */
+	node = xt_new_node( "field", NULL, NULL );
+	xt_add_attr( node, "var", "stream-method" );
+	xt_add_attr( node, "type", "list-single" );
+
+	for ( m = methods ; *m ; m ++ )
+		xt_add_child( node, xt_new_node( "option", NULL, xt_new_node( "value", (char *)*m, NULL ) ) );
+
+	node = xt_new_node( "x", NULL, node );
+	xt_add_attr( node, "xmlns", XMLNS_XDATA );
+	xt_add_attr( node, "type", "form" );
+
+	node = xt_new_node( "feature", NULL, node );
+	xt_add_attr( node, "xmlns", XMLNS_FEATURE );
+
+	xt_add_child( sinode, node );
+
+	/* and we are there... */
+	node = jabber_make_packet( "iq", "set", bud ? bud->full_jid : who, sinode );
+	jabber_cache_add( ic, node, jabber_si_handle_response );
+	tf->iq_id = g_strdup( xt_find_attr( node, "id" ) );
+	
+	return jabber_write_packet( ic, node );
+}
Index: protocols/nogaim.h
===================================================================
--- protocols/nogaim.h	(revision 3038e476a93b2be057581b831749eac931a06559)
+++ protocols/nogaim.h	(revision 1ba7e8f1d28c4876ea0b787f1e4ebb5607f48895)
@@ -43,4 +43,5 @@
 #include "proxy.h"
 #include "md5.h"
+#include "ft.h"
 
 #define BUF_LEN MSG_LEN
@@ -228,4 +229,7 @@
 	 * - Most protocols will just want to set this to g_strcasecmp().*/
 	int (* handle_cmp) (const char *who1, const char *who2);
+
+	/* Incoming transfer request */
+	void (* transfer_request) (struct im_connection *, file_transfer_t *ft, char *handle );
 };
 
