diff options
Diffstat (limited to 'protocols')
| -rw-r--r-- | protocols/ft.h | 153 | ||||
| -rw-r--r-- | protocols/jabber/Makefile | 2 | ||||
| -rw-r--r-- | protocols/jabber/iq.c | 28 | ||||
| -rw-r--r-- | protocols/jabber/jabber.h | 58 | ||||
| -rw-r--r-- | protocols/jabber/jabber_util.c | 6 | ||||
| -rw-r--r-- | protocols/jabber/si.c | 246 | ||||
| -rw-r--r-- | protocols/jabber/stream.c | 593 | ||||
| -rw-r--r-- | protocols/nogaim.h | 1 | 
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 ) | 
