diff options
| -rw-r--r-- | dcc.c | 312 | ||||
| -rw-r--r-- | dcc.h | 6 | ||||
| -rw-r--r-- | doc/user-guide/commands.xml | 43 | ||||
| -rw-r--r-- | irc.c | 16 | ||||
| -rw-r--r-- | protocols/ft.h | 15 | ||||
| -rw-r--r-- | protocols/jabber/Makefile | 2 | ||||
| -rw-r--r-- | protocols/jabber/iq.c | 2 | ||||
| -rw-r--r-- | protocols/jabber/jabber.c | 1 | ||||
| -rw-r--r-- | protocols/jabber/jabber.h | 13 | ||||
| -rw-r--r-- | protocols/jabber/jabber_util.c | 7 | ||||
| -rw-r--r-- | protocols/jabber/s5bytestream.c | 906 | ||||
| -rw-r--r-- | protocols/jabber/si.c | 191 | ||||
| -rw-r--r-- | protocols/jabber/stream.c | 593 | ||||
| -rw-r--r-- | protocols/nogaim.h | 3 | 
14 files changed, 1460 insertions, 650 deletions
| @@ -27,6 +27,7 @@  #include "dcc.h"  #include <poll.h>  #include <netinet/tcp.h> +#include <regex.h>  /*    * Since that might be confusing a note on naming: @@ -75,6 +76,9 @@ static void dcc_close( file_transfer_t *file );  gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond );  gboolean dcc_listen( dcc_file_transfer_t *df, struct sockaddr_storage **saddr_ptr );  int dccs_send_request( struct dcc_file_transfer *df, char *user_nick, struct sockaddr_storage *saddr ); +gboolean dccs_recv_start( file_transfer_t *ft ); +gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond); +void dccs_recv_out_of_data( file_transfer_t *ft );  /* As defined in ft.h */  file_transfer_t *imcb_file_send_start( struct im_connection *ic, char *handle, char *file_name, size_t file_size ) @@ -103,31 +107,44 @@ gboolean imcb_file_write( file_transfer_t *file, gpointer data, size_t data_size  	return dccs_send_write( file, data, data_size );  } +/* As defined in ft.h */ +gboolean imcb_file_recv_start( file_transfer_t *ft ) +{ +	return dccs_recv_start( ft ); +} + +dcc_file_transfer_t *dcc_alloc_transfer( char *file_name, size_t file_size, struct im_connection *ic ) +{ +	file_transfer_t *file = g_new0( file_transfer_t, 1 ); +	dcc_file_transfer_t *df = file->priv = g_new0( dcc_file_transfer_t, 1); +	file->file_size = file_size; +	file->file_name = g_strdup( file_name ); +	file->local_id = local_transfer_id++; +	df->ic = ic; +	df->ft = file; +	 +	return df; +} +  /* This is where the sending magic starts... */  file_transfer_t *dccs_send_start( struct im_connection *ic, char *user_nick, char *file_name, size_t file_size )  {  	file_transfer_t *file;  	dcc_file_transfer_t *df; -	struct sockaddr_storage **saddr; +	struct sockaddr_storage *saddr;  	if( file_size > global.conf->max_filetransfer_size )  		return NULL; -	/* alloc stuff */ -	file = g_new0( file_transfer_t, 1 ); -	file->priv = df = g_new0( dcc_file_transfer_t, 1); -	file->file_size = file_size; -	file->file_name = g_strdup( file_name ); -	file->local_id = local_transfer_id++; -	df->ic = ic; -	df->ft = file; -	 +	df = dcc_alloc_transfer( file_name, file_size, ic ); +	file = df->ft; +  	/* listen and request */ -	if( !dcc_listen( df, saddr ) || -	    !dccs_send_request( df, user_nick, *saddr ) ) +	if( !dcc_listen( df, &saddr ) || +	    !dccs_send_request( df, user_nick, saddr ) )  		return NULL; -	g_free( *saddr ); +	g_free( saddr );  	/* watch */  	df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_send_proto, df ); @@ -260,25 +277,15 @@ gboolean dcc_listen( dcc_file_transfer_t *df, struct sockaddr_storage **saddr_pt  }  /* - * After setup, the transfer itself is handled entirely by this function. - * There are basically four things to handle: connect, receive, send, and error. + * Checks poll(), same for receiving and sending   */ -gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond ) +gboolean dcc_poll( dcc_file_transfer_t *df, int fd, short *revents )  { -	dcc_file_transfer_t *df = data; -	file_transfer_t *file = df->ft;  	struct pollfd pfd = { .fd = fd, .events = POLLHUP|POLLERR|POLLIN|POLLOUT }; -	short revents; -	 -	if ( poll( &pfd, 1, 0 ) == -1 ) -	{ -		imcb_log( df->ic, "poll() failed, weird!" ); -		revents = 0; -	}; -	revents = pfd.revents; +	ASSERTSOCKOP( poll( &pfd, 1, 0 ), "poll()" ) -	if( revents & POLLERR ) +	if( pfd.revents & POLLERR )  	{  		int sockerror;  		socklen_t errlen = sizeof( sockerror ); @@ -289,9 +296,44 @@ gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond )  		return dcc_abort( df, "Socket error: %s", strerror( sockerror ) );  	} -	if( revents & POLLHUP )  +	if( pfd.revents & POLLHUP )   		return dcc_abort( df, "Remote end closed connection" ); +	*revents = pfd.revents; + +	return TRUE; +} + +gboolean  dcc_check_maxseg( dcc_file_transfer_t *df, int fd ) +{ +#ifdef DCC_SEND_AHEAD +	/*  +	 * use twice the maximum segment size as a maximum for calls to send(). +	 */ +	if( max_packet_size == 0 ) +	{ +		unsigned int mpslen = sizeof( max_packet_size ); +		if( getsockopt( fd, IPPROTO_TCP, TCP_MAXSEG, &max_packet_size, &mpslen ) ) +			return dcc_abort( df, "getsockopt() failed" ); +		max_packet_size *= 2; +	} +#endif +	return TRUE; +} + +/* + * After setup, the transfer itself is handled entirely by this function. + * There are basically four things to handle: connect, receive, send, and error. + */ +gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond ) +{ +	dcc_file_transfer_t *df = data; +	file_transfer_t *file = df->ft; +	short revents; +	 +	if( !dcc_poll( df, fd, &revents) ) +		return FALSE; +  	if( ( revents & POLLIN ) &&  	    ( file->status & FT_STATUS_LISTENING ) )  	{ 	 @@ -304,21 +346,12 @@ gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond )  		closesocket( fd );  		fd = df->fd; -		file->status = FT_STATUS_TRANSFERING; +		file->status = FT_STATUS_TRANSFERRING;  		sock_make_nonblocking( fd ); -#ifdef DCC_SEND_AHEAD -		/*  -		 * use twice the maximum segment size as a maximum for calls to send(). -		 */ -		if( max_packet_size == 0 ) -		{ -			unsigned int mpslen = sizeof( max_packet_size ); -			if( getsockopt( fd, IPPROTO_TCP, TCP_MAXSEG, &max_packet_size, &mpslen ) ) -				return dcc_abort( df, "getsockopt() failed" ); -			max_packet_size *= 2; -		} -#endif +		if ( !dcc_check_maxseg( df, fd ) ) +			return FALSE; +  		/* IM protocol callback */  		if( file->accept ) @@ -452,6 +485,113 @@ gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond )  	return TRUE;  } +gboolean dccs_recv_start( file_transfer_t *ft ) +{ +	dcc_file_transfer_t *df = ft->priv; +	struct sockaddr_storage *saddr = &df->saddr; +	int fd; +	socklen_t sa_len = saddr->ss_family == AF_INET ?  +		sizeof( struct sockaddr_in ) : sizeof( struct sockaddr_in6 ); +	 +	if( !ft->write ) +		return dcc_abort( df, "Protocol didn't register write()" ); +	 +	ASSERTSOCKOP( fd = df->fd = socket( saddr->ss_family, SOCK_STREAM, 0 ) , "Opening Socket" ); + +	sock_make_nonblocking( fd ); + +	if( ( connect( fd, (struct sockaddr *)saddr, sa_len ) == -1 ) && +	    ( errno != EINPROGRESS ) ) +		return dcc_abort( df, "Connecting" ); + +	ft->status = FT_STATUS_CONNECTING; + +	/* watch */ +	df->watch_out = b_input_add( df->fd, GAIM_INPUT_WRITE, dccs_recv_proto, df ); +	ft->out_of_data = dccs_recv_out_of_data; + +	return TRUE; +} + +gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond) +{ +	dcc_file_transfer_t *df = data; +	file_transfer_t *ft = df->ft; +	short revents; + +	if( !dcc_poll( df, fd, &revents ) ) +		return FALSE; +	 +	if( ( revents & POLLOUT ) && +	    ( ft->status & FT_STATUS_CONNECTING ) ) +	{ +		ft->status = FT_STATUS_TRANSFERRING; +		if ( !dcc_check_maxseg( df, fd ) ) +			return FALSE; + +		//df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_recv_proto, df ); + +		df->watch_out = 0; +		return FALSE; +	} + +	if( revents & POLLIN ) +	{ +		char *buffer = g_malloc( 65536 ); +		int ret, done; + +		ASSERTSOCKOP( ret = recv( fd, buffer, 65536, 0 ), "Receiving" ); + +		if( ret == 0 ) +			return dcc_abort( df, "Remote end closed connection" ); + +		buffer = g_realloc( buffer, ret ); + +		df->bytes_sent += ret; + +		done = df->bytes_sent >= ft->file_size; + +		if( ( ( df->bytes_sent - ft->bytes_transferred ) > DCC_PACKET_SIZE ) || +		    done ) +		{ +			int ack, ackret; +			ack = htonl( ft->bytes_transferred = df->bytes_sent ); + +			ASSERTSOCKOP( ackret = send( fd, &ack, 4, 0 ), "Sending DCC ACK" ); +			 +			if ( ackret != 4 ) +				return dcc_abort( df, "Error sending DCC ACK, sent %d instead of 4 bytes", ret ); +		} +		 +		if( !ft->write( df->ft, buffer, ret ) && !done ) +		{ +			df->watch_in = 0; +			return FALSE; +		} + +		if( done ) +		{ +			closesocket( fd ); +			dcc_finish( ft ); + +			df->watch_in = 0; +			return FALSE; +		} + +		return TRUE; +	} + +	return TRUE; +} + +void dccs_recv_out_of_data( file_transfer_t *ft ) +{ +	dcc_file_transfer_t *df = ft->priv; + +	if( !df->watch_in ) +		df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_recv_proto, df ); +} +  /*    * Incoming data. Note that the buffer MUST NOT be freed by the caller!   * We don't copy the buffer but put it in our queue. @@ -471,7 +611,7 @@ gboolean dccs_send_write( file_transfer_t *file, gpointer data, unsigned int dat  	df->queued_bytes += data_size; -	if( ( file->status & FT_STATUS_TRANSFERING ) &&  +	if( ( file->status & FT_STATUS_TRANSFERRING ) &&   #ifndef DCC_SEND_AHEAD  	    ( file->bytes_transferred >= df->bytes_sent ) &&   #endif @@ -532,3 +672,91 @@ void dcc_finish( file_transfer_t *file )  	dcc_close( file );  } + +/*  + * DCC SEND <filename> <IP> <port> <filesize> + * + * filename can be in "" or not. If it is, " can probably be escaped... + * IP can be an unsigned int (IPV4) or something else (IPV6) + *  + */ +file_transfer_t *dcc_request( struct im_connection *ic, char *line ) +{ +	char *pattern = "SEND" +		" (([^\"][^ ]*)|\"([^\"]|\\\")*\")" +		" (([0-9]*)|([^ ]*))" +		" ([0-9]*)" +		" ([0-9]*)\001"; +	regmatch_t pmatch[9]; +	regex_t re; +	file_transfer_t *ft; +	dcc_file_transfer_t *df; + +	if( regcomp( &re, pattern, REG_EXTENDED ) ) +		return NULL; +	if( regexec( &re, line, 9, pmatch, 0 ) ) +		return NULL; + +	if( ( pmatch[1].rm_so > 0 ) && +	    ( pmatch[4].rm_so > 0 ) && +	    ( pmatch[7].rm_so > 0 ) && +	    ( pmatch[8].rm_so > 0 ) ) +	{ +		char *input = g_strdup( line ); +		char *filename, *host, *port; +		size_t filesize; +		struct addrinfo hints, *rp; + +		/* "filename" or filename */ +		if ( pmatch[2].rm_so > 0 ) +		{ +			input[pmatch[2].rm_eo] = '\0'; +			filename = input + pmatch[2].rm_so; +		} else +		{ +			input[pmatch[3].rm_eo] = '\0'; +			filename = input + pmatch[3].rm_so; +		} +			 +		input[pmatch[4].rm_eo] = '\0'; + +		/* number means ipv4, something else means ipv6 */ +		if ( pmatch[5].rm_so > 0 ) +		{ +			struct in_addr ipaddr = { htonl( atoi( input + pmatch[5].rm_so ) ) }; +			host = inet_ntoa( ipaddr ); +		} else +		{ +			/* Contains non-numbers, hopefully an IPV6 address */ +			host = input + pmatch[6].rm_so; +		} + +		input[pmatch[7].rm_eo] = '\0'; +		input[pmatch[8].rm_eo] = '\0'; + +		port = input + pmatch[7].rm_so; +		filesize = atoll( input + pmatch[8].rm_so ); + +		memset( &hints, 0, sizeof ( struct addrinfo ) ); +		if ( getaddrinfo( host, port, &hints, &rp ) ) +		{ +			g_free( input ); +			return NULL; +		} + +		df = dcc_alloc_transfer( filename, filesize, ic ); +		ft = df->ft; +		ft->sending = TRUE; +		memcpy( &df->saddr, rp->ai_addr, rp->ai_addrlen ); + +		freeaddrinfo( rp ); +		g_free( input ); + +		df->ic->irc->file_transfers = g_slist_prepend( df->ic->irc->file_transfers, ft ); + +		return ft; +	} + +	return NULL; +} + @@ -111,9 +111,12 @@ typedef struct dcc_file_transfer {  	 */  	size_t bytes_sent; -	/* imcb's handle */ +	/* imc's handle */  	file_transfer_t *ft; +	/* if we're receiving, this is the sender's socket address */ +	struct sockaddr_storage saddr; +  } dcc_file_transfer_t;  file_transfer_t *dccs_send_start( struct im_connection *ic, char *user_nick, char *file_name, size_t file_size ); @@ -122,4 +125,5 @@ void dcc_canceled( file_transfer_t *file, char *reason );  gboolean dccs_send_write( file_transfer_t *file, gpointer data, unsigned int data_size ); +file_transfer_t *dcc_request( struct im_connection *ic, char *line );  #endif diff --git a/doc/user-guide/commands.xml b/doc/user-guide/commands.xml index ac9bdf11..8c874014 100644 --- a/doc/user-guide/commands.xml +++ b/doc/user-guide/commands.xml @@ -860,4 +860,47 @@  		</ircexample>  	</bitlbee-command> +	 +	<bitlbee-command name="transfers"> +		<short-description>Monitor, cancel, or reject file transfers</short-description> +		<syntax>transfers [<cancel> id | <reject>]</syntax> +		 +		<description> +			<para> +				Without parameters the currently pending file transfers and their status will be listed. Available actions are <emphasis>cancel</emphasis> and <emphasis>reject</emphasis>. See <emphasis>help transfers <action></emphasis> for more information. +			</para> + +			<ircexample> +				<ircline nick="ulim">transfers</ircline> +			</ircexample> +		</description> +		 +		<bitlbee-command name="cancel"> +			<short-description>Cancels the file transfer with the given id</short-description> +			<syntax>transfers <cancel> id</syntax> + +			<description> +				<para>Cancels the file transfer with the given id</para> +			</description> + +			<ircexample> +				<ircline nick="ulim">transfers cancel 1</ircline> +				<ircline nick="root">Canceling file transfer for test</ircline> +			</ircexample> +		</bitlbee-command> + +		<bitlbee-command name="reject"> +			<short-description>Rejects all incoming transfers</short-description> +			<syntax>transfers <reject></syntax> + +			<description> +				<para>Rejects all incoming (not already transferring) file transfers. Since you probably have only one incoming transfer at a time, no id is neccessary. Or is it?</para> +			</description> + +			<ircexample> +				<ircline nick="ulim">transfers reject</ircline> +			</ircexample> +		</bitlbee-command> +	</bitlbee-command> +	  </chapter> @@ -27,6 +27,10 @@  #include "bitlbee.h"  #include "crypting.h"  #include "ipc.h" +#include "dcc.h" + +#include <regex.h> +#include <netinet/in.h>  static gboolean irc_userping( gpointer _irc, int fd, b_input_condition cond ); @@ -980,9 +984,19 @@ int irc_send( irc_t *irc, char *nick, char *s, int flags )  			}  			return( 1 );  		} +		else if( g_strncasecmp( s + 1, "DCC", 3 ) == 0 ) +		{ +			if( u && u->ic && u->ic->acc->prpl->transfer_request ) +			{ +				file_transfer_t *ft = dcc_request( u->ic, s + 5 ); +				if ( ft ) +					u->ic->acc->prpl->transfer_request( u->ic, ft, u->handle ); +			} +			return( 1 ); +		}		  		else  		{ -			irc_usermsg( irc, "Non-ACTION CTCP's aren't supported" ); +			irc_usermsg( irc, "Supported CTCPs are ACTION, VERSION, PING, TYPING, DCC" );  			return( 0 );  		}  	} diff --git a/protocols/ft.h b/protocols/ft.h index 0ff44873..d41eb6c1 100644 --- a/protocols/ft.h +++ b/protocols/ft.h @@ -28,9 +28,10 @@  typedef enum {  	FT_STATUS_LISTENING	= 1, -	FT_STATUS_TRANSFERING	= 2, +	FT_STATUS_TRANSFERRING	= 2,  	FT_STATUS_FINISHED	= 4, -	FT_STATUS_CANCELED	= 8 +	FT_STATUS_CANCELED	= 8, +	FT_STATUS_CONNECTING	= 16  } file_status_t;  /* @@ -60,6 +61,10 @@ typedef enum {   *	                 \------------------------/   */  typedef struct file_transfer { + +	/* Are we sending something? */ +	int sending; +  	/*  	 * The current status of this file transfer.  	 */  @@ -130,6 +135,11 @@ typedef struct file_transfer {  	 */  	void (*out_of_data) ( struct file_transfer *file ); +	/* +	 * When sending files, protocols register this function to receive data. +	 */ +	gboolean (*write) (struct file_transfer *file, char *buffer, int len ); +  } file_transfer_t;  /* @@ -150,4 +160,5 @@ void imcb_file_canceled( file_transfer_t *file, char *reason );   */  gboolean imcb_file_write( file_transfer_t *file, gpointer data, size_t data_size ); +gboolean imcb_file_recv_start( file_transfer_t *ft );  #endif diff --git a/protocols/jabber/Makefile b/protocols/jabber/Makefile index 47c832ae..21d7ef07 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 si.o stream.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  LFLAGS += -r diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c index df0102b8..77def222 100644 --- a/protocols/jabber/iq.c +++ b/protocols/jabber/iq.c @@ -167,7 +167,7 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data )  		} else if( strcmp( s, XMLNS_BYTESTREAMS ) == 0 )  		{  		     	/* Bytestream Request (stage 2 of file transfer) */ -			return jabber_bs_request( ic, node, c ); +			return jabber_bs_recv_request( ic, node, c );  		} else  		{  			xt_free_node( reply ); diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c index b0651a59..1f4f42ea 100644 --- a/protocols/jabber/jabber.c +++ b/protocols/jabber/jabber.c @@ -501,6 +501,7 @@ void jabber_initmodule()  	ret->keepalive = jabber_keepalive;  	ret->send_typing = jabber_send_typing;  	ret->handle_cmp = g_strcasecmp; +	ret->transfer_request = jabber_si_transfer_request;  	register_protocol( ret );  } diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h index 0cb2b733..cb52d396 100644 --- a/protocols/jabber/jabber.h +++ b/protocols/jabber/jabber.h @@ -147,6 +147,7 @@ struct jabber_transfer  	size_t bytesread, byteswritten;  	int receiver_overflow;  	int fd; +	struct sockaddr_storage saddr;  };  #define JABBER_XMLCONSOLE_HANDLE "xmlconsole" @@ -200,10 +201,14 @@ 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); +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, int len );  /* message.c */  xt_status jabber_pkt_message( struct xt_node *node, gpointer data ); diff --git a/protocols/jabber/jabber_util.c b/protocols/jabber/jabber_util.c index 0c5b813e..6bb65878 100644 --- a/protocols/jabber/jabber_util.c +++ b/protocols/jabber/jabber_util.c @@ -264,7 +264,14 @@ char *jabber_normalize( const char *orig )  	len = strlen( orig );  	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;  	return new; diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c new file mode 100644 index 00000000..e2f32bd0 --- /dev/null +++ b/protocols/jabber/s5bytestream.c @@ -0,0 +1,906 @@ +/***************************************************************************\ +*                                                                           * +*  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; + +	/* <query> element and <streamhost> elements */ +	struct xt_node *qnode, *shnode; + +	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 ) ); + +#define JABBER_BS_BUFSIZE 65536 + +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 ); +void jabber_bs_recv_out_of_data( 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, char *host, char *port ); +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 ); + +/* + * 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; + +	if ( tf->watch_in ) +		b_event_remove( tf->watch_in ); +	 +	if( tf->watch_out ) +		b_event_remove( tf->watch_out ); +	 +	g_free( bt->pseudoadr ); +	xt_free_node( bt->qnode ); +	g_free( bt ); +//iq_id +	jabber_si_free_transfer( ft ); +} + +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; +} + +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; +} + +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_recv_handshake_abort( bt, error ); +	else +		return jabber_bs_send_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; + +	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] ); +		 +	bt = g_new0( struct bs_transfer, 1 ); +	bt->tf = tf; +	bt->qnode = xt_dup( qnode ); +	bt->shnode = bt->qnode->children; +	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 xt_node *c; +			char *host, *port; +			struct addrinfo hints, *rp; + +			if( ( c = bt->shnode = xt_find_node( bt->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_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, "Transferring file %s: Connecting to streamhost %s:%s", bt->tf->ft->file_name, host, 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; +			} else +				return jabber_bs_abort( bt, 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" ); + +			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); + +			jabber_bs_recv_answer_request( bt ); + +			// reset in answer_request bt->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; +	} +} + +/* + * 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_recv_handshake_abort( struct bs_transfer *bt, char *error ) +{ +	struct jabber_transfer *tf = bt->tf; +	struct xt_node *reply, *iqnode; + +	if( bt->shnode )  +	{ +		imcb_log( tf->ic, "Transferring file %s: connection to streamhost %s:%s failed (%s)",  +			  tf->ft->file_name,  +			  xt_find_attr( bt->shnode, "host" ), +			  xt_find_attr( bt->shnode, "port" ), +			  error ); + +		/* Alright, this streamhost failed, let's try the next... */ +		bt->phase = BS_PHASE_CONNECT; +		bt->shnode = bt->shnode->next; +		 +		/* the if is not neccessary but saves us one recursion */ +		if( bt->shnode ) +			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, "Transferring file %s: established SOCKS5 connection to %s:%s",  +		  tf->ft->file_name,  +		  xt_find_attr( bt->shnode, "host" ), +		  xt_find_attr( bt->shnode, "port" ) ); + +	tf->ft->data = tf; +	tf->ft->started = time( NULL ); +	tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_recv_read, tf ); +	tf->ft->out_of_data = jabber_bs_recv_out_of_data; + +	reply = xt_new_node( "streamhost-used", NULL, NULL ); +	xt_add_attr( reply, "jid", xt_find_attr( bt->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_recv_read( gpointer data, gint fd, b_input_condition cond ) +{ +	int ret; +	struct jabber_transfer *tf = data; +	struct bs_transfer *bt = tf->streamhandle; +	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; +		} +	} + +	ASSERTSOCKOP( ret = recv( fd, buffer, JABBER_BS_BUFSIZE, 0 ) , "Receiving" ); + +	/* 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_recv_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_recv_read, tf ); +} + +/* signal ood and be done */ +gboolean jabber_bs_send_can_write( gpointer data, gint fd, b_input_condition cond ) +{ +	struct bs_transfer *bt = data; + +	bt->tf->ft->out_of_data( bt->tf->ft ); + +	bt->tf->watch_out = 0; +	return FALSE; +} + +/* try to send the stuff. If you can't return false and wait for writable */ +gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, int len ) +{ +	struct jabber_transfer *tf = ft->data; +	struct bs_transfer *bt = tf->streamhandle; +	int ret; + +	if ( ( ( ret = send( tf->fd, buffer, len, 0 ) ) == -1 ) && +	     ( errno != EAGAIN ) ) +		return jabber_bs_abort( bt, "send failed on socket with: %s", strerror( errno ) ); +	 +	if( ret == 0 ) +		return jabber_bs_abort( bt, "Remote end closed connection" ); +	 +	if( ret == -1 ) +	{ +		bt->tf->watch_out = b_input_add( tf->fd, GAIM_INPUT_WRITE, jabber_bs_send_can_write, bt ); +		return FALSE; +	} +		 +	return TRUE; +} + +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( bt->phase == BS_PHASE_REPLY ) +	{ +		tf->ft->started = time( NULL ); +		tf->ft->out_of_data( tf->ft ); +	} + +	//bt->tf->watch_out = b_input_add( tf->fd, GAIM_INPUT_WRITE, jabber_bs_send_write, tf ); + +	return XT_HANDLED; +} + +gboolean jabber_bs_send_start( struct jabber_transfer *tf ) +{ +	char host[INET6_ADDRSTRLEN], port[6]; +	struct bs_transfer *bt; +	sha1_state_t sha; +	char hash_hex[41]; +	unsigned char hash[20]; +	int i; + +	/* 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->qnode = xt_dup( qnode ); +	//bt->shnode = bt->qnode->children; +	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, host, 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 ); +	return jabber_bs_send_request( tf, host, port ); +} + +gboolean jabber_bs_send_request( struct jabber_transfer *tf, char *host, char *port ) +{ +	struct xt_node *sh, *query, *iq; + +	sh = xt_new_node( "streamhost", NULL, NULL ); +	xt_add_attr( sh, "jid", tf->ini_jid ); +	xt_add_attr( sh, "host", host ); +	xt_add_attr( sh, "port", port ); + +	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" ); +	xt_add_child( query, sh ); + +	iq = jabber_make_packet( "iq", "set", tf->tgt_jid, query ); +	xt_add_attr( iq, "from", tf->ini_jid ); + +	//xt_free_node( query ); + +	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; + +	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; + +			/* don't start sending till the streamhost-used message comes in */ +			if( tf->accepted ) +			{ +				tf->ft->started = time( NULL ); +				tf->ft->out_of_data( 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 diff --git a/protocols/jabber/si.c b/protocols/jabber/si.c index d16f723a..598cbd03 100644 --- a/protocols/jabber/si.c +++ b/protocols/jabber/si.c @@ -25,8 +25,9 @@  #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 ); -/* imcb callback */ +/* file_transfer free() callback */  void jabber_si_free_transfer( file_transfer_t *ft)  {  	struct jabber_transfer *tf = ft->data; @@ -49,7 +50,7 @@ void jabber_si_free_transfer( file_transfer_t *ft)  	g_free( tf->sid );  } -/* imcb callback */ +/* file_transfer finished() callback */  void jabber_si_finished( file_transfer_t *ft )  {  	struct jabber_transfer *tf = ft->data; @@ -57,7 +58,7 @@ void jabber_si_finished( file_transfer_t *ft )  	imcb_log( tf->ic, "File %s transferred successfully!" , ft->file_name );  } -/* imcb callback */ +/* file_transfer canceled() callback */  void jabber_si_canceled( file_transfer_t *ft, char *reason )  {  	struct jabber_transfer *tf = ft->data; @@ -77,6 +78,29 @@ void jabber_si_canceled( file_transfer_t *ft, char *reason )  } +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; + +	imcb_log( ic, "Incoming file from %s : %s %zd bytes", ic->irc->nick, ft->file_name, ft->file_size ); + +	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 ); + +	jabber_si_send_request( ic, who, tf ); + +	imcb_file_recv_start( ft ); +} +  /*   * First function that gets called when a file transfer request comes in.   * A lot to parse. @@ -135,6 +159,9 @@ int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, st  				requestok = TRUE;  				break;  			} + +		if ( !requestok ) +			imcb_log( ic, "WARNING: Unsupported file transfer request from %s", ini_jid);  	}  	if ( requestok ) @@ -159,8 +186,7 @@ int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, st  		}  		*s = '/'; -	} else  -		imcb_log( ic, "WARNING: Unsupported file transfer request from %s", ini_jid); +	}  	if ( !requestok )  	{  @@ -244,3 +270,158 @@ void jabber_si_answer_request( file_transfer_t *ft ) {  		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; +	GSList *tflist; +	struct jabber_transfer *tf=NULL; +	struct jabber_data *jd = ic->proto_data; +	char *sid; + +	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; +	} +	 +	imcb_log( ic, "GOT RESPONSE TO FILE" ); +	/* All this means we expect something like this: ( I think ) +	 * <iq from=... to=...> +	 * 	<si xmlns=si> +	 * 		<file xmlns=ft/> +	 * 		<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" ) ) || +	    !( c = xt_find_node( node->children, "si" ) ) || +	    !( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_SI ) == 0 ) || +	    !( sid = xt_find_attr( c, "id" ) )|| +	    !( 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->sid, sid ) == 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 ); + +	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_name( 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 ); +	 +	return jabber_write_packet( ic, node ); +} diff --git a/protocols/jabber/stream.c b/protocols/jabber/stream.c deleted file mode 100644 index c88a72fd..00000000 --- a/protocols/jabber/stream.c +++ /dev/null @@ -1,593 +0,0 @@ -/***************************************************************************\ -*                                                                           * -*  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 8651754a..f17c5a1e 100644 --- a/protocols/nogaim.h +++ b/protocols/nogaim.h @@ -228,6 +228,9 @@ struct prpl {  	/* Mainly for AOL, since they think "Bung hole" == "Bu ngho le". *sigh*  	 * - 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 );  };  /* im_api core stuff. */ | 
