aboutsummaryrefslogtreecommitdiffstats
path: root/protocols
diff options
context:
space:
mode:
Diffstat (limited to 'protocols')
-rw-r--r--protocols/ft.h153
-rw-r--r--protocols/jabber/Makefile2
-rw-r--r--protocols/jabber/iq.c28
-rw-r--r--protocols/jabber/jabber.h58
-rw-r--r--protocols/jabber/jabber_util.c6
-rw-r--r--protocols/jabber/si.c246
-rw-r--r--protocols/jabber/stream.c593
-rw-r--r--protocols/nogaim.h1
8 files changed, 1065 insertions, 22 deletions
diff --git a/protocols/ft.h b/protocols/ft.h
new file mode 100644
index 00000000..0ff44873
--- /dev/null
+++ b/protocols/ft.h
@@ -0,0 +1,153 @@
+/********************************************************************\
+* 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
+
+typedef enum {
+ FT_STATUS_LISTENING = 1,
+ FT_STATUS_TRANSFERING = 2,
+ FT_STATUS_FINISHED = 4,
+ FT_STATUS_CANCELED = 8
+} 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 {
+ /*
+ * 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 );
+
+ /*
+ * If set, called when the transfer queue is running empty and
+ * more data can be added.
+ */
+ void (*out_of_data) ( struct file_transfer *file );
+
+} 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 );
+
+/*
+ * The given buffer is queued for transfer and MUST NOT be freed by the caller.
+ * When the method returns false the caller should not invoke this method again
+ * until out_of_data has been called.
+ */
+gboolean imcb_file_write( file_transfer_t *file, gpointer data, size_t data_size );
+
+#endif
diff --git a/protocols/jabber/Makefile b/protocols/jabber/Makefile
index e042f812..47c832ae 100644
--- a/protocols/jabber/Makefile
+++ b/protocols/jabber/Makefile
@@ -9,7 +9,7 @@
-include ../../Makefile.settings
# [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 stream.o
CFLAGS += -Wall
LFLAGS += -r
diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c
index 595718fb..df0102b8 100644
--- a/protocols/jabber/iq.c
+++ b/protocols/jabber/iq.c
@@ -103,6 +103,9 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data )
XMLNS_TIME,
XMLNS_CHATSTATES,
XMLNS_MUC,
+ XMLNS_SI,
+ XMLNS_BYTESTREAMS,
+ XMLNS_FILETRANSFER,
NULL };
const char **f;
@@ -122,24 +125,26 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data )
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;
}
}
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 );
if( ( s = xt_find_attr( node, "from" ) ) == NULL ||
@@ -156,14 +161,17 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data )
imcb_log( ic, "WARNING: %s tried to fake a roster push!", s ? s : "(unknown)" );
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_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;
}
}
diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h
index fc9d2fc4..0cb2b733 100644
--- a/protocols/jabber/jabber.h
+++ b/protocols/jabber/jabber.h
@@ -80,6 +80,8 @@ struct jabber_data
char *cached_id_prefix;
GHashTable *node_cache;
GHashTable *buddies;
+
+ GSList *filetransfers;
};
struct jabber_away_state
@@ -123,6 +125,30 @@ struct jabber_chat
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 receiver_overflow;
+ int fd;
+};
+
#define JABBER_XMLCONSOLE_HANDLE "xmlconsole"
#define JABBER_PORT_DEFAULT "5222"
@@ -148,15 +174,21 @@ struct jabber_chat
#define XMLNS_ROSTER "jabber:iq:roster"
/* 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_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_AUTH "jabber:iq:auth" /* XEP-0078 */
+#define XMLNS_VERSION "jabber:iq:version" /* XEP-0092 */
+#define XMLNS_TIME "jabber:iq:time" /* XEP-0090 */
+#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_DISCOVER "http://jabber.org/protocol/disco#info" /* 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_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 */
xt_status jabber_pkt_iq( struct xt_node *node, gpointer data );
@@ -167,6 +199,12 @@ int jabber_get_vcard( struct im_connection *ic, char *bare_jid );
int jabber_add_to_roster( struct im_connection *ic, char *handle, char *name );
int jabber_remove_from_roster( struct im_connection *ic, char *handle );
+/* si.c */
+int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, struct xt_node *sinode);
+
+/* stream.c */
+int jabber_bs_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode);
+
/* message.c */
xt_status jabber_pkt_message( struct xt_node *node, gpointer data );
@@ -179,7 +217,7 @@ int presence_send_request( struct im_connection *ic, char *handle, char *request
char *set_eval_priority( set_t *set, char *value );
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 );
void jabber_cache_entry_free( gpointer entry );
diff --git a/protocols/jabber/jabber_util.c b/protocols/jabber/jabber_util.c
index 43b91fe3..0c5b813e 100644
--- a/protocols/jabber/jabber_util.c
+++ b/protocols/jabber/jabber_util.c
@@ -96,7 +96,7 @@ struct xt_node *jabber_make_packet( char *name, char *type, char *to, struct xt_
return node;
}
-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;
char *to;
@@ -109,6 +109,10 @@ struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond,
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
add our <error>/type="error" tag. Including the original packet
is recommended, so let's just do it. */
diff --git a/protocols/jabber/si.c b/protocols/jabber/si.c
new file mode 100644
index 00000000..d16f723a
--- /dev/null
+++ b/protocols/jabber/si.c
@@ -0,0 +1,246 @@
+/***************************************************************************\
+* *
+* 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 );
+
+/* imcb 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 );
+}
+
+/* imcb 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 );
+}
+
+/* imcb 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 );
+
+}
+
+/*
+ * 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 )
+ {
+ /* 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 = '/';
+ } else
+ imcb_log( ic, "WARNING: Unsupported file transfer request from %s", ini_jid);
+
+ 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;
+}
+
+/*
+ * imcb 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 );
+}
diff --git a/protocols/jabber/stream.c b/protocols/jabber/stream.c
new file mode 100644
index 00000000..c88a72fd
--- /dev/null
+++ b/protocols/jabber/stream.c
@@ -0,0 +1,593 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Jabber module - stream handling *
+* *
+* 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>
+
+/* Some structs for the SOCKS5 handshake */
+
+struct bs_handshake_data {
+
+ struct jabber_transfer *tf;
+
+ /* <query> element and <streamhost> elements */
+ struct xt_node *qnode, *shnode;
+
+ enum
+ {
+ BS_PHASE_CONNECT,
+ BS_PHASE_CONNECTED,
+ BS_PHASE_REQUEST,
+ BS_PHASE_REPLY,
+ BS_PHASE_REPLY_HAVE_LEN
+ } phase;
+
+ /* SHA1( SID + Initiator JID + Target JID) */
+ char *pseudoadr;
+
+ void (*parentfree) ( file_transfer_t *ft );
+
+ gint connect_timeout;
+};
+
+struct socks5_hdr
+{
+ unsigned char ver;
+ union
+ {
+ unsigned char cmd;
+ unsigned char rep;
+ } cmdrep;
+ unsigned char rsv;
+ unsigned char atyp;
+};
+
+struct socks5_message
+{
+ struct socks5_hdr hdr;
+ unsigned char addrlen;
+ unsigned char address[64];
+};
+
+/* connect() timeout in seconds. */
+#define JABBER_BS_CONTIMEOUT 15
+
+/* shouldn't matter if it's mostly too much, kernel's smart about that
+ * and will only reserve some address space */
+#define JABBER_BS_BUFSIZE 65536
+
+gboolean jabber_bs_handshake( gpointer data, gint fd, b_input_condition cond );
+
+gboolean jabber_bs_handshake_abort( struct bs_handshake_data *bhd, char *format, ... );
+
+void jabber_bs_answer_request( struct bs_handshake_data *bhd );
+
+gboolean jabber_bs_read( gpointer data, gint fd, b_input_condition cond );
+
+void jabber_bs_out_of_data( file_transfer_t *ft );
+
+void jabber_bs_canceled( file_transfer_t *ft , char *reason );
+
+
+void jabber_bs_free_transfer( file_transfer_t *ft) {
+ struct jabber_transfer *tf = ft->data;
+ struct bs_handshake_data *bhd = tf->streamhandle;
+ void (*parentfree) ( file_transfer_t *ft );
+
+ parentfree = bhd->parentfree;
+
+ if ( tf->watch_in )
+ b_event_remove( tf->watch_in );
+
+ if( tf->watch_out )
+ b_event_remove( tf->watch_out );
+
+ g_free( bhd->pseudoadr );
+ xt_free_node( bhd->qnode );
+ g_free( bhd );
+
+ parentfree( ft );
+}
+
+/*
+ * Parses an incoming bytestream request and calls jabber_bs_handshake on success.
+ */
+int jabber_bs_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_handshake_data *bhd;
+
+ 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;
+ }
+
+ /* 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] );
+
+ bhd = g_new0( struct bs_handshake_data, 1 );
+ bhd->tf = tf;
+ bhd->qnode = xt_dup( qnode );
+ bhd->shnode = bhd->qnode->children;
+ bhd->phase = BS_PHASE_CONNECT;
+ bhd->pseudoadr = g_strdup( hash_hex );
+ tf->streamhandle = bhd;
+ bhd->parentfree = tf->ft->free;
+ tf->ft->free = jabber_bs_free_transfer;
+
+ jabber_bs_handshake( bhd, 0, 0 );
+
+ return XT_HANDLED;
+}
+
+/*
+ * 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_handshake_data *bhd = data;
+
+ bhd->connect_timeout = 0;
+
+ jabber_bs_handshake_abort( bhd, "no connection after %d seconds", JABBER_BS_CONTIMEOUT );
+
+ return FALSE;
+}
+
+/*
+ * 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_handshake( gpointer data, gint fd, b_input_condition cond )
+{
+
+/* very useful */
+#define ASSERTSOCKOP(op, msg) \
+ if( (op) == -1 ) \
+ return jabber_bs_handshake_abort( bhd , msg ": %s", strerror( errno ) );
+
+ struct bs_handshake_data *bhd = data;
+ struct pollfd pfd = { .fd = fd, .events = POLLHUP|POLLERR };
+ short revents;
+
+ if ( bhd->connect_timeout )
+ {
+ b_event_remove( bhd->connect_timeout );
+ bhd->connect_timeout = 0;
+ }
+
+
+ /* we need the real io condition */
+ if ( poll( &pfd, 1, 0 ) == -1 )
+ {
+ imcb_log( bhd->tf->ic, "poll() failed, weird!" );
+ revents = 0;
+ };
+
+ revents = pfd.revents;
+
+ if( revents & POLLERR )
+ {
+ int sockerror;
+ socklen_t errlen = sizeof( sockerror );
+
+ if ( getsockopt( fd, SOL_SOCKET, SO_ERROR, &sockerror, &errlen ) )
+ return jabber_bs_handshake_abort( bhd, "getsockopt() failed, unknown socket error during SOCKS5 handshake (weird!)" );
+
+ if ( bhd->phase == BS_PHASE_CONNECTED )
+ return jabber_bs_handshake_abort( bhd, "connect() failed: %s", strerror( sockerror ) );
+
+ return jabber_bs_handshake_abort( bhd, "Socket error during SOCKS5 handshake(weird!): %s", strerror( sockerror ) );
+ }
+
+ if( revents & POLLHUP )
+ return jabber_bs_handshake_abort( bhd, "Remote end closed connection" );
+
+
+ switch( bhd->phase )
+ {
+ case BS_PHASE_CONNECT:
+ {
+ struct xt_node *c;
+ char *host, *port;
+ struct addrinfo hints, *rp;
+
+ if( ( c = bhd->shnode = xt_find_node( bhd->shnode, "streamhost" ) ) &&
+ ( port = xt_find_attr( c, "port" ) ) &&
+ ( host = xt_find_attr( c, "host" ) ) &&
+ xt_find_attr( c, "jid" ) )
+ {
+ memset( &hints, 0, sizeof( struct addrinfo ) );
+ hints.ai_socktype = SOCK_STREAM;
+
+ if ( getaddrinfo( host, port, &hints, &rp ) != 0 )
+ return jabber_bs_handshake_abort( bhd, "getaddrinfo() failed: %s", strerror( errno ) );
+
+ ASSERTSOCKOP( bhd->tf->fd = fd = socket( rp->ai_family, rp->ai_socktype, 0 ), "Opening socket" );
+
+ sock_make_nonblocking( fd );
+
+ imcb_log( bhd->tf->ic, "Transferring file %s: Connecting to streamhost %s:%s", bhd->tf->ft->file_name, host, port );
+
+ if( ( connect( fd, rp->ai_addr, rp->ai_addrlen ) == -1 ) &&
+ ( errno != EINPROGRESS ) )
+ return jabber_bs_handshake_abort( bhd , "connect() failed: %s", strerror( errno ) );
+
+ freeaddrinfo( rp );
+
+ bhd->phase = BS_PHASE_CONNECTED;
+
+ bhd->tf->watch_out = b_input_add( fd, GAIM_INPUT_WRITE, jabber_bs_handshake, bhd );
+
+ /* since it takes forever(3mins?) till connect() fails on itself we schedule a timeout */
+ bhd->connect_timeout = b_timeout_add( JABBER_BS_CONTIMEOUT * 1000, jabber_bs_connect_timeout, bhd );
+
+ bhd->tf->watch_in = 0;
+ return FALSE;
+ } else
+ return jabber_bs_handshake_abort( bhd, c ? "incomplete streamhost entry: host=%s port=%s jid=%s" : NULL,
+ host, port, xt_find_attr( c, "jid" ) );
+ }
+ 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" );
+
+ bhd->phase = BS_PHASE_REQUEST;
+
+ bhd->tf->watch_in = b_input_add( fd, GAIM_INPUT_READ, jabber_bs_handshake, bhd );
+
+ bhd->tf->watch_out = 0;
+ return FALSE;
+ }
+ case BS_PHASE_REQUEST:
+ {
+ struct socks5_message socks5_connect =
+ {
+ .hdr =
+ {
+ .ver = 5,
+ .cmdrep.cmd = 0x01,
+ .rsv = 0,
+ .atyp = 0x03
+ },
+ .addrlen = strlen( bhd->pseudoadr )
+ };
+ 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_handshake_abort( bhd, "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, bhd->pseudoadr, socks5_connect.addrlen );
+
+ /* after the address comes the port, which is always 0 */
+ memset( socks5_connect.address + socks5_connect.addrlen, 0, sizeof( in_port_t ) );
+
+ ASSERTSOCKOP( send( fd, &socks5_connect, sizeof( struct socks5_hdr ) + 1 + socks5_connect.addrlen + sizeof( in_port_t ), 0 ) , "Sending SOCKS5 Connect" );
+
+ bhd->phase = BS_PHASE_REPLY;
+
+ return TRUE;
+ }
+ case BS_PHASE_REPLY:
+ case BS_PHASE_REPLY_HAVE_LEN:
+ {
+ /* we have to wait till we have the address length, then we know how much data is left
+ * (not that we'd actually care about that data, but we need to eat it all up anyway)
+ */
+ struct socks5_message socks5_reply;
+ int ret;
+ int expectedbytes =
+ sizeof( struct socks5_hdr ) + 1 +
+ ( bhd->phase == BS_PHASE_REPLY_HAVE_LEN ? socks5_reply.addrlen + sizeof( in_port_t ) : 0 );
+
+ /* notice the peek, we're doing this till enough is there */
+ ASSERTSOCKOP( ret = recv( fd, &socks5_reply, expectedbytes, MSG_PEEK ) , "Peeking for SOCKS5 CONNECT reply" );
+
+ if ( ret == 0 )
+ return jabber_bs_handshake_abort( bhd , "peer has shutdown connection" );
+
+ /* come again */
+ if ( ret < expectedbytes )
+ return TRUE;
+
+ if ( bhd->phase == BS_PHASE_REPLY )
+ {
+ if( !( socks5_reply.hdr.ver == 5 ) ||
+ !( socks5_reply.hdr.cmdrep.rep == 0 ) ||
+ !( socks5_reply.hdr.atyp == 3 ) ||
+ !( socks5_reply.addrlen <= 62 ) ) /* should also be 40, but who cares as long as all fits in the buffer... */
+ return jabber_bs_handshake_abort( bhd, "SOCKS5 CONNECT failed (reply: ver=%d, rep=%d, atyp=%d, addrlen=%d",
+ socks5_reply.hdr.ver,
+ socks5_reply.hdr.cmdrep.rep,
+ socks5_reply.hdr.atyp,
+ socks5_reply.addrlen);
+
+ /* and again for the rest */
+ bhd->phase = BS_PHASE_REPLY_HAVE_LEN;
+
+ /* since it's very likely that the rest is there as well,
+ * let's not wait for the event loop to call us again */
+ return jabber_bs_handshake( bhd , fd, 0 );
+ }
+
+ /* got it all, remove it from the queue */
+ ASSERTSOCKOP( ret = recv( fd, &socks5_reply, expectedbytes, 0 ) , "Dequeueing MSG_PEEK'ed data after SOCKS5 CONNECT" );
+
+ /* this shouldn't happen */
+ if ( ret < expectedbytes )
+ return jabber_bs_handshake_abort( bhd, "internal error, couldn't dequeue MSG_PEEK'ed data after SOCKS5 CONNECT" );
+
+ /* we're actually done now... */
+
+ jabber_bs_answer_request( bhd );
+
+ bhd->tf->watch_in = 0;
+ return FALSE;
+ }
+ default:
+ /* BUG */
+ imcb_log( bhd->tf->ic, "BUG in file transfer code: undefined handshake phase" );
+
+ bhd->tf->watch_in = 0;
+ return FALSE;
+ }
+#undef ASSERTSOCKOP
+#undef JABBER_BS_ERR_CONDS
+}
+
+/*
+ * 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 is an example here). That way, a (potentially)
+ * slow proxy is only used if neccessary.
+ */
+gboolean jabber_bs_handshake_abort( struct bs_handshake_data *bhd, char *format, ... )
+{
+ struct jabber_transfer *tf = bhd->tf;
+ struct xt_node *reply, *iqnode;
+
+ if( bhd->shnode )
+ {
+ if( 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 );
+
+ imcb_log( tf->ic, "Transferring file %s: connection to streamhost %s:%s failed (%s)",
+ tf->ft->file_name,
+ xt_find_attr( bhd->shnode, "host" ),
+ xt_find_attr( bhd->shnode, "port" ),
+ error );
+ }
+
+ /* Alright, this streamhost failed, let's try the next... */
+ bhd->phase = BS_PHASE_CONNECT;
+ bhd->shnode = bhd->shnode->next;
+
+ /* the if is not neccessary but saves us one recursion */
+ if( bhd->shnode )
+ return jabber_bs_handshake( bhd, 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" );
+
+ bhd->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_answer_request( struct bs_handshake_data *bhd )
+{
+ struct jabber_transfer *tf = bhd->tf;
+ struct xt_node *reply;
+
+ imcb_log( tf->ic, "Transferring file %s: established SOCKS5 connection to %s:%s",
+ tf->ft->file_name,
+ xt_find_attr( bhd->shnode, "host" ),
+ xt_find_attr( bhd->shnode, "port" ) );
+
+ tf->ft->data = tf;
+ tf->ft->started = time( NULL );
+ tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_read, tf );
+ tf->ft->out_of_data = jabber_bs_out_of_data;
+
+ reply = xt_new_node( "streamhost-used", NULL, NULL );
+ xt_add_attr( reply, "jid", xt_find_attr( bhd->shnode, "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 );
+}
+
+/* Reads till it is unscheduled or the receiver signifies an overflow. */
+gboolean jabber_bs_read( gpointer data, gint fd, b_input_condition cond )
+{
+ int ret;
+ struct jabber_transfer *tf = data;
+ char *buffer = g_malloc( JABBER_BS_BUFSIZE );
+
+ if (tf->receiver_overflow)
+ {
+ if( tf->watch_in )
+ {
+ /* should never happen, BUG */
+ imcb_file_canceled( tf->ft, "Bug in jabber file transfer code: read while overflow is true. Please report" );
+ return FALSE;
+ }
+ }
+
+ ret = recv( fd, buffer, JABBER_BS_BUFSIZE, 0 );
+
+ if( ret == -1 )
+ {
+ /* shouldn't actually happen */
+ if( errno == EAGAIN )
+ return TRUE;
+
+ imcb_file_canceled( tf->ft, "Error reading tcp socket" ); /* , strerror( errnum ) */
+
+ return FALSE;
+ }
+
+ /* that should be all */
+ if( ret == 0 )
+ return FALSE;
+
+ tf->bytesread += ret;
+
+ buffer = g_realloc( buffer, ret );
+
+ if ( ( tf->receiver_overflow = imcb_file_write( tf->ft, buffer, ret ) ) )
+ {
+ /* wait for imcb to run out of data */
+ tf->watch_in = 0;
+ return FALSE;
+ }
+
+
+ return TRUE;
+}
+
+/* imcb callback that is invoked when it runs out of data.
+ * We reschedule jabber_bs_read here if neccessary. */
+void jabber_bs_out_of_data( file_transfer_t *ft )
+{
+ struct jabber_transfer *tf = ft->data;
+
+ tf->receiver_overflow = FALSE;
+
+ if ( !tf->watch_in )
+ tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_read, tf );
+}
+
+/* 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 );
+}
diff --git a/protocols/nogaim.h b/protocols/nogaim.h
index 0e890464..8651754a 100644
--- a/protocols/nogaim.h
+++ b/protocols/nogaim.h
@@ -42,6 +42,7 @@
#include "account.h"
#include "proxy.h"
#include "md5.h"
+#include "ft.h"
#define BUF_LEN MSG_LEN
#define BUF_LONG ( BUF_LEN * 2 )