diff options
| -rw-r--r-- | Makefile | 4 | ||||
| -rw-r--r-- | conf.c | 1 | ||||
| -rw-r--r-- | conf.h | 1 | ||||
| -rw-r--r-- | dcc.c | 747 | ||||
| -rw-r--r-- | dcc.h | 98 | ||||
| -rw-r--r-- | doc/user-guide/commands.xml | 43 | ||||
| -rw-r--r-- | irc.c | 13 | ||||
| -rw-r--r-- | irc.h | 1 | ||||
| -rw-r--r-- | protocols/ft.h | 175 | ||||
| -rw-r--r-- | protocols/jabber/Makefile | 2 | ||||
| -rw-r--r-- | protocols/jabber/iq.c | 193 | ||||
| -rw-r--r-- | protocols/jabber/jabber.c | 15 | ||||
| -rw-r--r-- | protocols/jabber/jabber.h | 86 | ||||
| -rw-r--r-- | protocols/jabber/jabber_util.c | 6 | ||||
| -rw-r--r-- | protocols/jabber/s5bytestream.c | 1196 | ||||
| -rw-r--r-- | protocols/jabber/si.c | 516 | ||||
| -rw-r--r-- | protocols/nogaim.h | 4 | ||||
| -rw-r--r-- | root_commands.c | 66 | 
18 files changed, 3138 insertions, 29 deletions
| @@ -9,8 +9,8 @@  -include Makefile.settings  # Program variables -objects = account.o bitlbee.o crypting.o help.o ipc.o irc.o irc_commands.o nick.o query.o root_commands.o set.o storage.o $(STORAGE_OBJS) user.o -headers = account.h bitlbee.h commands.h conf.h config.h crypting.h help.h ipc.h irc.h log.h nick.h query.h set.h sock.h storage.h user.h lib/events.h lib/http_client.h lib/ini.h lib/md5.h lib/misc.h lib/proxy.h lib/sha1.h lib/ssl_client.h lib/url.h protocols/nogaim.h +objects = account.o bitlbee.o crypting.o help.o ipc.o irc.o irc_commands.o nick.o query.o root_commands.o set.o storage.o $(STORAGE_OBJS) user.o dcc.o +headers = account.h bitlbee.h commands.h conf.h config.h crypting.h help.h ipc.h irc.h log.h nick.h query.h set.h sock.h storage.h user.h lib/events.h lib/http_client.h lib/ini.h lib/md5.h lib/misc.h lib/proxy.h lib/sha1.h lib/ssl_client.h lib/url.h protocols/nogaim.h protocols/ft.h  subdirs = lib protocols  ifeq ($(TARGET),i586-mingw32msvc) @@ -62,6 +62,7 @@ conf_t *conf_load( int argc, char *argv[] )  	conf->ping_interval = 180;  	conf->ping_timeout = 300;  	conf->user = NULL; +	conf->max_filetransfer_size = G_MAXUINT;  	proxytype = 0;  	i = conf_loadini( conf, global.conf_file ); @@ -49,6 +49,7 @@ typedef struct conf  	int ping_interval;  	int ping_timeout;  	char *user; +	size_t max_filetransfer_size;  } conf_t;  G_GNUC_MALLOC conf_t *conf_load( int argc, char *argv[] ); @@ -0,0 +1,747 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway                     * +*                                                                    * +* 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 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 +*/ + +#define BITLBEE_CORE +#include "bitlbee.h" +#include "ft.h" +#include "dcc.h" +#include <poll.h> +#include <netinet/tcp.h> +#include <regex.h> + +/* Some ifdefs for ulibc (Thanks to Whoopie) */ +#ifndef HOST_NAME_MAX +#include <sys/param.h> +#ifdef MAXHOSTNAMELEN +#define HOST_NAME_MAX MAXHOSTNAMELEN +#else +#define HOST_NAME_MAX 255 +#endif +#endif + +#ifndef AI_NUMERICSERV +#define AI_NUMERICSERV 0x0400   /* Don't use name resolution.  */ +#endif + +/*  + * Since that might be confusing a note on naming: + * + * Generic dcc functions start with  + * + * 	dcc_ + * + * ,methods specific to DCC SEND start with + * + * 	dccs_ + * + * . Since we can be on both ends of a DCC SEND, + * functions specific to one end are called + * + * 	dccs_send and dccs_recv + * + * ,respectively. + */ + + +/*  + * used to generate a unique local transfer id the user + * can use to reject/cancel transfers + */ +unsigned int local_transfer_id=1; + +/*  + * just for debugging the nr. of chunks we received from im-protocols and the total data + */ +unsigned int receivedchunks=0, receiveddata=0; + +int max_packet_size = 0; + +static void dcc_finish( file_transfer_t *file ); +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); +gboolean dccs_recv_write_request( file_transfer_t *ft ); +gboolean dcc_progress( gpointer data, gint fd, b_input_condition cond ); + +/* 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 ) +{ +	user_t *u = user_findhandle( ic, handle ); +	/* one could handle this more intelligent like imcb_buddy_msg. +	 * can't call it directly though cause it does some wrapping. +	 * Maybe give imcb_buddy_msg a parameter NO_WRAPPING? */ +	if (!u) return NULL; + +	return dccs_send_start( ic, u->nick, file_name, file_size ); +}; + +/* As defined in ft.h */ +void imcb_file_canceled( file_transfer_t *file, char *reason ) +{ +	if( file->canceled ) +		file->canceled( file, reason ); + +	dcc_close( file ); +} + +/* As defined in ft.h */ +gboolean imcb_file_recv_start( file_transfer_t *ft ) +{ +	return dccs_recv_start( ft ); +} + +/* As defined in ft.h */ +void imcb_file_finished( file_transfer_t *file ) +{ +	dcc_file_transfer_t *df = file->priv; + +	if( file->bytes_transferred >= file->file_size ) +		dcc_finish( file ); +	else +		df->proto_finished = TRUE; +} + +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; + +	if( file_size > global.conf->max_filetransfer_size ) +		return NULL; +	 +	df = dcc_alloc_transfer( file_name, file_size, ic ); +	file = df->ft; +	file->write = dccs_send_write; + +	/* listen and request */ +	if( !dcc_listen( df, &saddr ) || +	    !dccs_send_request( df, user_nick, saddr ) ) +		return NULL; + +	g_free( saddr ); + +	/* watch */ +	df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_send_proto, df ); + +	df->ic->irc->file_transfers = g_slist_prepend( df->ic->irc->file_transfers, file ); + +	df->progress_timeout = b_timeout_add( DCC_MAX_STALL * 1000, dcc_progress, df ); + +	imcb_log( ic, "File transfer request from %s for %s (%zd kb). ", user_nick, file_name, file_size/1024 ); + +	imcb_log( ic, "Accept the file transfer if you'd like the file. If you don't, issue the 'transfers reject' command."); + +	return file; +} + +/* Used pretty much everywhere in the code to abort a transfer */ +gboolean dcc_abort( dcc_file_transfer_t *df, char *reason, ... ) +{ +	file_transfer_t *file = df->ft; +	va_list params; +	va_start( params, reason ); +	char *msg = g_strdup_vprintf( reason, params ); +	va_end( params ); +	 +	file->status |= FT_STATUS_CANCELED; +	 +	if( file->canceled ) +		file->canceled( file, msg ); + +	imcb_log( df->ic, "File %s: DCC transfer aborted: %s", file->file_name, msg ); + +	g_free( msg ); + +	dcc_close( df->ft ); + +	return FALSE; +} + +gboolean dcc_progress( gpointer data, gint fd, b_input_condition cond ) +{ +	struct dcc_file_transfer *df = data; + +	if( df->bytes_sent == df->progress_bytes_last ) +	{ +		/* no progress. cancel */ +		if( df->bytes_sent == 0 ) +			return dcc_abort( df, "Couldnt establish transfer within %d seconds", DCC_MAX_STALL ); +		else  +			return dcc_abort( df, "Transfer stalled for %d seconds at %d kb", DCC_MAX_STALL, df->bytes_sent / 1024 ); + +	} + +	df->progress_bytes_last = df->bytes_sent; + +	return TRUE; +} + +/* used extensively for socket operations */ +#define ASSERTSOCKOP(op, msg) \ +	if( (op) == -1 ) \ +		return dcc_abort( df , msg ": %s", strerror( errno ) ); + +/* Creates the "DCC SEND" line and sends it to the server */ +int dccs_send_request( struct dcc_file_transfer *df, char *user_nick, struct sockaddr_storage *saddr ) +{ +	char ipaddr[INET6_ADDRSTRLEN];  +	const void *netaddr; +	int port; +	char *cmd; + +	if( saddr->ss_family == AF_INET ) +	{ +		struct sockaddr_in *saddr_ipv4 = ( struct sockaddr_in *) saddr; + +		sprintf( ipaddr, "%d",  +			 ntohl( saddr_ipv4->sin_addr.s_addr ) ); +		port = saddr_ipv4->sin_port; +	} else  +	{ +		struct sockaddr_in6 *saddr_ipv6 = ( struct sockaddr_in6 *) saddr; + +		netaddr = &saddr_ipv6->sin6_addr.s6_addr; +		port = saddr_ipv6->sin6_port; + +		/*  +		 * Didn't find docs about this, but it seems that's the way irssi does it +		 */ +		if( !inet_ntop( saddr->ss_family, netaddr, ipaddr, sizeof( ipaddr ) ) ) +			return dcc_abort( df, "inet_ntop failed: %s", strerror( errno ) ); +	} + +	port = ntohs( port ); + +	cmd = g_strdup_printf( "\001DCC SEND %s %s %u %zu\001", +				df->ft->file_name, ipaddr, port, df->ft->file_size ); +	 +	if ( !irc_msgfrom( df->ic->irc, user_nick, cmd ) ) +		return dcc_abort( df, "couldn't send 'DCC SEND' message to %s", user_nick ); + +	g_free( cmd ); + +	return TRUE; +} + +/* + * Creates a listening socket and returns it in saddr_ptr. + */ +gboolean dcc_listen( dcc_file_transfer_t *df, struct sockaddr_storage **saddr_ptr ) +{ +	file_transfer_t *file = df->ft; +	struct sockaddr_storage *saddr; +	int fd,gret; +	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 ( ( gret = getaddrinfo( hostname, "0", &hints, &rp ) != 0 ) ) +		return dcc_abort( df, "getaddrinfo(): %s", gai_strerror( gret ) ); + +	saddr = g_new( struct sockaddr_storage, 1 ); + +	*saddr_ptr = saddr; + +	memcpy( saddr, rp->ai_addr, rp->ai_addrlen ); + +	ASSERTSOCKOP( fd = df->fd = socket( saddr->ss_family, SOCK_STREAM, 0 ), "Opening socket" ); + +	ASSERTSOCKOP( bind( fd, ( struct sockaddr *)saddr, rp->ai_addrlen ), "Binding socket" ); +	 +	freeaddrinfo( rp ); + +	ASSERTSOCKOP( getsockname( fd, ( struct sockaddr *)saddr, &ssize ), "Getting socket name" ); + +	ASSERTSOCKOP( listen( fd, 1 ), "Making socket listen" ); + +	file->status = FT_STATUS_LISTENING; + +	return TRUE; +} + +/* + * Checks poll(), same for receiving and sending + */ +gboolean dcc_poll( dcc_file_transfer_t *df, int fd, short *revents ) +{ +	struct pollfd pfd = { .fd = fd, .events = POLLHUP|POLLERR|POLLIN|POLLOUT }; + +	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 dcc_abort( df, "getsockopt() failed, unknown socket error (weird!)" ); + +		return dcc_abort( df, "Socket error: %s", strerror( sockerror ) ); +	} +	 +	if( pfd.revents & POLLHUP )  +		return dcc_abort( df, "Remote end closed connection" ); +	 +	*revents = pfd.revents; + +	return TRUE; +} + +/* + * fills max_packet_size with twice the TCP maximum segment size + */ +gboolean  dcc_check_maxseg( dcc_file_transfer_t *df, int fd ) +{ +	/*  +	 * 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; +	} +	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 ) ) +	{ 	 +		struct sockaddr *clt_addr; +		socklen_t ssize = sizeof( clt_addr ); + +		/* Connect */ + +		ASSERTSOCKOP( df->fd = accept( fd, (struct sockaddr *) &clt_addr, &ssize ), "Accepting connection" ); + +		closesocket( fd ); +		fd = df->fd; +		file->status = FT_STATUS_TRANSFERRING; +		sock_make_nonblocking( fd ); + +		if ( !dcc_check_maxseg( df, fd ) ) +			return FALSE; + +		/* IM protocol callback */ +		if( file->accept ) +			file->accept( file ); + +		/* reschedule for reading on new fd */ +		df->watch_in = b_input_add( fd, GAIM_INPUT_READ, dccs_send_proto, df ); + +		return FALSE; +	} + +	if( revents & POLLIN )  +	{ +		int bytes_received; +		int ret; +		 +		ASSERTSOCKOP( ret = recv( fd, &bytes_received, sizeof( bytes_received  ), MSG_PEEK ), "Receiving" ); + +		if( ret == 0 ) +			return dcc_abort( df, "Remote end closed connection" ); +			 +		if( ret < 4 ) +		{ +			imcb_log( df->ic, "WARNING: DCC SEND: receiver sent only 2 bytes instead of 4, shouldn't happen too often!" ); +			return TRUE; +		} + +		ASSERTSOCKOP( ret = recv( fd, &bytes_received, sizeof( bytes_received  ), 0 ), "Receiving" ); +		if( ret != 4 ) +			return dcc_abort( df, "MSG_PEEK'ed 4, but can only dequeue %d bytes", ret ); + +		bytes_received = ntohl( bytes_received ); + +		/* If any of this is actually happening, the receiver should buy a new IRC client */ + +		if ( bytes_received > df->bytes_sent ) +			return dcc_abort( df, "Receiver magically received more bytes than sent ( %d > %d ) (BUG at receiver?)", bytes_received, df->bytes_sent ); + +		if ( bytes_received < file->bytes_transferred ) +			return dcc_abort( df, "Receiver lost bytes? ( has %d, had %d ) (BUG at receiver?)", bytes_received, file->bytes_transferred ); +		 +		file->bytes_transferred = bytes_received; +	 +		if( file->bytes_transferred >= file->file_size ) { +			if( df->proto_finished ) +				dcc_finish( file ); +			return FALSE; +		} +	 +		return TRUE; +	} + +	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; +	char ipaddr[INET6_ADDRSTRLEN];  +	socklen_t sa_len = saddr->ss_family == AF_INET ?  +		sizeof( struct sockaddr_in ) : sizeof( struct sockaddr_in6 ); +	 +	if( !ft->write ) +		return dcc_abort( df, "BUG: 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 to %s:%d : %s",  +			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, +				ipaddr,  +				sizeof( ipaddr ) ), +			ntohs( saddr->ss_family == AF_INET ? +			    ( ( struct sockaddr_in *) saddr )->sin_port : +			    ( ( struct sockaddr_in6 *) saddr )->sin6_port ), +			strerror( errno ) ); + +	ft->status = FT_STATUS_CONNECTING; + +	/* watch */ +	df->watch_out = b_input_add( df->fd, GAIM_INPUT_WRITE, dccs_recv_proto, df ); +	ft->write_request = dccs_recv_write_request; + +	df->progress_timeout = b_timeout_add( DCC_MAX_STALL * 1000, dcc_progress, df ); + +	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 ) +	{ +		int ret, done; + +		ASSERTSOCKOP( ret = recv( fd, ft->buffer, sizeof( ft->buffer ), 0 ), "Receiving" ); + +		if( ret == 0 ) +			return dcc_abort( df, "Remote end closed connection" ); + +		if( !ft->write( df->ft, ft->buffer, ret ) ) +			return FALSE; + +		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", ackret ); +		} +		 +		if( df->bytes_sent == ret ) +			ft->started = time( NULL ); + +		if( done ) +		{ +			if( df->watch_out ) +				b_event_remove( df->watch_out ); + +			if( df->proto_finished ) +				dcc_finish( ft ); + +			df->watch_in = 0; +			return FALSE; +		} + +		df->watch_in = 0; +		return FALSE; +	} + +	return TRUE; +} + +gboolean dccs_recv_write_request( file_transfer_t *ft ) +{ +	dcc_file_transfer_t *df = ft->priv; + +	if( df->watch_in ) +		return dcc_abort( df, "BUG: write_request() called while watching" ); + +	df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_recv_proto, df ); + +	return TRUE; +} + +gboolean dccs_send_can_write( gpointer data, gint fd, b_input_condition cond ) +{ +	struct dcc_file_transfer *df = data; +	df->watch_out = 0; + +	df->ft->write_request( df->ft ); +	return FALSE; +} + +/*  + * Incoming data. + *  + */ +gboolean dccs_send_write( file_transfer_t *file, char *data, unsigned int data_len ) +{ +	dcc_file_transfer_t *df = file->priv; +	int ret; + +	receivedchunks++; receiveddata += data_len; + +	if( df->watch_out ) +		return dcc_abort( df, "BUG: write() called while watching" ); + +	ASSERTSOCKOP( ret = send( df->fd, data, data_len, 0 ), "Sending data" ); + +	if( ret == 0 ) +		return dcc_abort( df, "Remote end closed connection" ); + +	/* TODO: this should really not be fatal */ +	if( ret < data_len ) +		return dcc_abort( df, "send() sent %d instead of %d", ret, data_len ); + +	if( df->bytes_sent == 0 ) +		file->started = time( NULL ); + +	df->bytes_sent += ret; + +	if( df->bytes_sent < df->ft->file_size ) +		df->watch_out = b_input_add( df->fd, GAIM_INPUT_WRITE, dccs_send_can_write, df ); + +	return TRUE; +} + +/* + * Cleans up after a transfer. + */ +static void dcc_close( file_transfer_t *file ) +{ +	dcc_file_transfer_t *df = file->priv; + +	if( file->free ) +		file->free( file ); +	 +	closesocket( df->fd ); + +	if( df->watch_in ) +		b_event_remove( df->watch_in ); + +	if( df->watch_out ) +		b_event_remove( df->watch_out ); +	 +	if( df->progress_timeout ) +		b_event_remove( df->progress_timeout ); +	 +	df->ic->irc->file_transfers = g_slist_remove( df->ic->irc->file_transfers, file ); +	 +	g_free( df ); +	g_free( file->file_name ); +	g_free( file ); +} + +void dcc_finish( file_transfer_t *file ) +{ +	dcc_file_transfer_t *df = file->priv; +	time_t diff = time( NULL ) - file->started ? : 1; + +	file->status |= FT_STATUS_FINISHED; +	 +	if( file->finished ) +		file->finished( file ); + +	imcb_log( df->ic, "File %s transferred successfully at %d kb/s!" , file->file_name, (int) ( file->bytes_transferred / 1024 / diff ) ); +	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[10]; +	regex_t re; +	file_transfer_t *ft; +	dcc_file_transfer_t *df; +	char errbuf[256]; +	int regerrcode, gret; + +	if( ( regerrcode = regcomp( &re, pattern, REG_EXTENDED ) ) || +	    ( regerrcode = regexec( &re, line, 10, pmatch, 0 ) ) ) { +		regerror( regerrcode,&re,errbuf,sizeof( errbuf ) ); +		imcb_log( ic,  +			  "DCC: error parsing 'DCC SEND': %s, line: %s",  +			  errbuf, line ); +		return NULL; +	} + +	if( ( pmatch[1].rm_so > 0 ) && +	    ( pmatch[5].rm_so > 0 ) && +	    ( pmatch[8].rm_so > 0 ) && +	    ( pmatch[9].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[5].rm_eo] = '\0'; + +		/* number means ipv4, something else means ipv6 */ +		if ( pmatch[6].rm_so > 0 ) +		{ +			struct in_addr ipaddr = { .s_addr = htonl( atoi( input + pmatch[5].rm_so ) ) }; +			host = inet_ntoa( ipaddr ); +		} else +		{ +			/* Contains non-numbers, hopefully an IPV6 address */ +			host = input + pmatch[7].rm_so; +		} + +		input[pmatch[8].rm_eo] = '\0'; +		input[pmatch[9].rm_eo] = '\0'; + +		port = input + pmatch[8].rm_so; +		filesize = atoll( input + pmatch[9].rm_so ); + +		memset( &hints, 0, sizeof ( struct addrinfo ) ); +		if ( ( gret = getaddrinfo( host, port, &hints, &rp ) ) ) +		{ +			g_free( input ); +			imcb_log( ic, "DCC: getaddrinfo() failed with %s " +				  "when parsing incoming 'DCC SEND': " +				  "host %s, port %s",  +				  gai_strerror( gret ), host, port ); +			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; +	} + +	imcb_log( ic, "DCC: couldnt parse 'DCC SEND' line: %s", line ); + +	return NULL; +} + @@ -0,0 +1,98 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway                     * +*                                                                    * +* Copyright 2006 Marijn Kruisselbrink and others                     * +* 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 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 +*/ + +/*  + * DCC SEND + * + * Historically, DCC means send 1024 Bytes and wait for a 4 byte reply + * acknowledging all transferred data. This is ridiculous for two reasons.  The + * first being that TCP is a stream oriented protocol that doesn't care much + * about your idea of a packet. The second reason being that TCP is a reliable + * transfer protocol with its own sophisticated ACK mechanism, making DCCs ACK + * mechanism look like a joke. For these reasons, DCCs requirements have + * (hopefully) been relaxed in most implementations and this implementation + * depends upon at least the following: The 1024 bytes need not be transferred + * at once, i.e. packets can be smaller. A second relaxation has apparently + * gotten the name "DCC SEND ahead" which basically means to not give a damn + * about those DCC ACKs and just send data as you please. This behaviour is + * enabled by default. Note that this also means that packets may be as large + * as the maximum segment size. + */  + +#ifndef _DCC_H +#define _DCC_H + +/* Send an ACK after receiving this amount of data */ +#define DCC_PACKET_SIZE 1024 + +/* Time in seconds that a DCC transfer can be stalled before being aborted. + * By handling this here individual protocols don't have to think about this. */ +#define DCC_MAX_STALL 120 + +typedef struct dcc_file_transfer { + +	struct im_connection *ic; + +	/* +	 * Depending in the status of the file transfer, this is either the socket that is +	 * being listened on for connections, or the socket over which the file transfer is +	 * taking place. +	 */ +	int fd; +	 +	/* +	 * IDs returned by b_input_add for watch_ing over the above socket. +	 */ +	gint watch_in;   /* readable */ +	gint watch_out;  /* writable */ +	 +	/* the progress watcher cancels any file transfer if nothing happens within DCC_MAX_STALL */ +	gint progress_timeout; +	size_t progress_bytes_last; + +	/* +	 * The total amount of bytes that have been sent to the irc client. +	 */ +	size_t bytes_sent; +	 +	/* imc's handle */ +	file_transfer_t *ft; + +	/* if we're receiving, this is the sender's socket address */ +	struct sockaddr_storage saddr; + +	/* set to true if the protocol has finished  +	 * (i.e. called imcb_file_finished) +	 */ +	int proto_finished; +} dcc_file_transfer_t; + +file_transfer_t *dccs_send_start( struct im_connection *ic, char *user_nick, char *file_name, size_t file_size ); + +void dcc_canceled( file_transfer_t *file, char *reason ); + +gboolean dccs_send_write( file_transfer_t *file, char *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 6d77f8cd..7081663f 100644 --- a/doc/user-guide/commands.xml +++ b/doc/user-guide/commands.xml @@ -873,4 +873,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> @@ -28,6 +28,7 @@  #include "sock.h"  #include "crypting.h"  #include "ipc.h" +#include "dcc.h"  static gboolean irc_userping( gpointer _irc, int fd, b_input_condition cond ); @@ -1074,9 +1075,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 );  		}  	} @@ -86,6 +86,7 @@ typedef struct irc  	struct query *queries;  	struct account *accounts; +	GSList *file_transfers;  	struct __USER *users;  	GHashTable *userhash; diff --git a/protocols/ft.h b/protocols/ft.h new file mode 100644 index 00000000..1155f06f --- /dev/null +++ b/protocols/ft.h @@ -0,0 +1,175 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway                     * +*                                                                    * +* Copyright 2006 Marijn Kruisselbrink and others                     * +\********************************************************************/ + +/* Generic file transfer header                                     */ + +/* +  This program is free software; you can redistribute it and/or modify +  it under the terms of the GNU General Public License as published by +  the Free Software Foundation; either version 2 of the License, or +  (at your option) any later version. + +  This program is distributed in the hope that it will be useful, +  but WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public License with +  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; +  if not, write to the Free Software Foundation, Inc., 59 Temple Place, +  Suite 330, Boston, MA  02111-1307  USA +*/ + +#ifndef _FT_H +#define _FT_H + +/* + * One buffer is needed for each transfer. The receiver stores a message + * in it and gives it to the sender. The sender will stall the receiver + * till the buffer has been sent out. + */ +#define FT_BUFFER_SIZE 2048 + +typedef enum { +	FT_STATUS_LISTENING	= 1, +	FT_STATUS_TRANSFERRING	= 2, +	FT_STATUS_FINISHED	= 4, +	FT_STATUS_CANCELED	= 8, +	FT_STATUS_CONNECTING	= 16 +} file_status_t; + +/* + * This structure holds all irc specific information regarding an incoming (from the point of view of + * the irc client) file transfer. New instances of this struct should only be created by calling the + * imcb_file_send_start() method, which will initialize most of the fields. The data field and the various + * methods are zero-initialized. Instances will automatically be deleted once the transfer is completed, + * canceled, or the connection to the irc client has been lost (note that also if only the irc connection + * and not the file transfer connection is lost, the file transfer will still be canceled and freed). + * + * The following (poor ascii-art) diagram illustrates what methods are called for which status-changes: + * + *	                        /-----------\                    /----------\ + *	               -------> | LISTENING | -----------------> | CANCELED | + *	                        \-----------/  [canceled,]free   \----------/ + *	                              | + *	                              | accept + *	                              V + *	               /------ /-------------\                    /------------------------\ + *	   out_of_data |       | TRANSFERING | -----------------> | TRANSFERING | CANCELED | + *	               \-----> \-------------/  [canceled,]free   \------------------------/ + *	                              | + *	                              | finished,free + *	                              V + *	                 /------------------------\ + *	                 | TRANSFERING | FINISHED | + *	                 \------------------------/ + */ +typedef struct file_transfer { + +	/* Are we sending something? */ +	int sending; + +	/* +	 * The current status of this file transfer. +	 */  +	file_status_t status; +	 +	/* +	 * file size +	 */ +	size_t file_size; +	 +	/* +	 * Number of bytes that have been successfully transferred. +	 */ +	size_t bytes_transferred; + +	/* +	 * Time started. Used to calculate kb/s. +	 */ +	time_t started; + +	/* +	 * file name +	 */ +	char *file_name; + +	/* +	 * A unique local ID for this file transfer. +	 */ +	unsigned int local_id; + +	/* +	 * IM-protocol specific data associated with this file transfer. +	 */ +	gpointer data; +	 +	/* +	 * Private data. +	 */ +	gpointer priv; +	 +	/* +	 * If set, called after succesful connection setup. +	 */ +	void (*accept) ( struct file_transfer *file ); +	 +	/* +	 * If set, called when the transfer is canceled or finished. +	 * Subsequently, this structure will be freed. +	 * +	 */ +	void (*free) ( struct file_transfer *file ); +	 +	/* +	 * If set, called when the transfer is finished and successful. +	 */ +	void (*finished) ( struct file_transfer *file ); +	 +	/* +	 * If set, called when the transfer is canceled. +	 * ( canceled either by the transfer implementation or by +	 *  a call to imcb_file_canceled ) +	 */ +	void (*canceled) ( struct file_transfer *file, char *reason ); +	 +	/* +	 * called by the sending side to indicate that it is writable. +	 * The callee should check if data is available and call the  +	 * function(as seen below) if that is the case. +	 */ +	gboolean (*write_request) ( struct file_transfer *file ); + +	/* +	 * When sending files, protocols register this function to receive data. +	 * This should only be called once after write_request is called. The caller +	 * should not read more data until write_request is called again. This technique +	 * avoids buffering. +	 */ +	gboolean (*write) (struct file_transfer *file, char *buffer, unsigned int len ); + +	/* The send buffer associated with this transfer. +	 * Since receivers always wait for a write_request call one is enough. +	 */ +	char buffer[FT_BUFFER_SIZE]; + +} file_transfer_t; + +/* + * This starts a file transfer from bitlbee to the user. + */ +file_transfer_t *imcb_file_send_start( struct im_connection *ic, char *user_nick, char *file_name, size_t file_size ); + +/* + * This should be called by a protocol when the transfer is canceled. Note that + * the canceled() and free() callbacks given in file will be called by this function. + */ +void imcb_file_canceled( file_transfer_t *file, char *reason ); + +gboolean imcb_file_recv_start( file_transfer_t *ft ); + +void imcb_file_finished( file_transfer_t *file ); +#endif diff --git a/protocols/jabber/Makefile b/protocols/jabber/Makefile index e7a505ba..891df5eb 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 +objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o sasl.o si.o s5bytestream.o  CFLAGS += -Wall  LFLAGS += -r diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c index 38c5a5a9..eacc85af 100644 --- a/protocols/jabber/iq.c +++ b/protocols/jabber/iq.c @@ -89,14 +89,17 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data )  				xt_add_attr( reply, "id", s );  			pack = 0;  		} -		else if( strcmp( s, XMLNS_DISCOVER ) == 0 ) +		else if( strcmp( s, XMLNS_DISCO_INFO ) == 0 )  		{ -			const char *features[] = { XMLNS_DISCOVER, +			const char *features[] = { XMLNS_DISCO_INFO,  			                           XMLNS_VERSION,  			                           XMLNS_TIME,  			                           XMLNS_CHATSTATES,  			                           XMLNS_MUC,  			                           XMLNS_PING, +						   XMLNS_SI, +						   XMLNS_BYTESTREAMS, +						   XMLNS_FILETRANSFER,  			                           NULL };  			const char **f; @@ -116,24 +119,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 || @@ -150,14 +155,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_recv_request( ic, node, c ); +		} else  		{  			xt_free_node( reply ); -			reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel" ); +			reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel", NULL );  			pack = 0;  		}  	} @@ -592,3 +600,164 @@ int jabber_remove_from_roster( struct im_connection *ic, char *handle )  	xt_free_node( node );  	return st;  } + +xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); + +xt_status jabber_iq_query_features( struct im_connection *ic, char *bare_jid ) +{ +	struct xt_node *node, *query; +	struct jabber_buddy *bud; +	 +	if( ( bud = jabber_buddy_by_jid( ic, bare_jid , 0 ) ) == NULL ) +	{ +		/* Who cares about the unknown... */ +		imcb_log( ic, "Couldn't find buddy: %s", bare_jid); +		return 0; +	} +	 +	if( bud->features ) /* been here already */ +		return XT_HANDLED; +	 +	node = xt_new_node( "query", NULL, NULL ); +	xt_add_attr( node, "xmlns", XMLNS_DISCO_INFO ); +	 +	if( !( query = jabber_make_packet( "iq", "get", bare_jid, node ) ) ) +	{ +		imcb_log( ic, "WARNING: Couldn't generate feature query" ); +		xt_free_node( node ); +		return 0; +	} + +	jabber_cache_add( ic, query, jabber_iq_parse_features ); + +	return jabber_write_packet( ic, query ); +} + +xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ +	struct xt_node *c; +	struct jabber_buddy *bud; +	char *feature; + +	if( !( c = xt_find_node( node->children, "query" ) ) || +	    !( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_DISCO_INFO ) == 0 ) ) +	{ +		imcb_log( ic, "WARNING: Received incomplete IQ-result packet for discover" ); +		return XT_HANDLED; +	} +	if( ( bud = jabber_buddy_by_jid( ic, xt_find_attr( node, "from") , 0 ) ) == NULL ) +	{ +		/* Who cares about the unknown... */ +		imcb_log( ic, "Couldn't find buddy: %s", xt_find_attr( node, "from")); +		return 0; +	} +	 +	c = c->children; +	while( ( c = xt_find_node( c, "feature" ) ) ) { +		feature = xt_find_attr( c, "var" ); +		bud->features = g_slist_append(bud->features, g_strdup(feature) ); +		c = c->next; +	} + +	return XT_HANDLED; +} + +xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); + +xt_status jabber_iq_query_server( struct im_connection *ic, char *jid, char *xmlns ) +{ +	struct xt_node *node, *query; +	struct jabber_data *jd = ic->proto_data; +	 +	node = xt_new_node( "query", NULL, NULL ); +	xt_add_attr( node, "xmlns", xmlns ); +	 +	if( !( query = jabber_make_packet( "iq", "get", jid, node ) ) ) +	{ +		imcb_log( ic, "WARNING: Couldn't generate server query" ); +		xt_free_node( node ); +	} + +	jd->have_streamhosts--; +	jabber_cache_add( ic, query, jabber_iq_parse_server_features ); + +	return jabber_write_packet( ic, query ); +} + +/* + * Query the server for "items", query each "item" for identities, query each "item" that's a proxy for it's bytestream info + */ +xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ +	struct xt_node *c; +	struct jabber_data *jd = ic->proto_data; + +	if( !( c = xt_find_node( node->children, "query" ) ) || +	    !xt_find_attr( node, "from" ) ) +	{ +		imcb_log( ic, "WARNING: Received incomplete IQ-result packet for discover" ); +		return XT_HANDLED; +	} + +	jd->have_streamhosts++; + +	if( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_DISCO_ITEMS ) == 0 ) +	{ +		char *item, *itemjid; + +		/* answer from server */ +	 +		c = c->children; +		while( ( c = xt_find_node( c, "item" ) ) ) +		{ +			item = xt_find_attr( c, "name" ); +			itemjid = xt_find_attr( c, "jid" ); + +			jabber_iq_query_server( ic, itemjid, XMLNS_DISCO_INFO ); + +			c = c->next; +		} +	} else if( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_DISCO_INFO ) == 0 ) +	{ +		char *category, *type; + +		/* answer from potential proxy */ + +		c = c->children; +		while( ( c = xt_find_node( c, "identity" ) ) ) +		{ +			category = xt_find_attr( c, "category" ); +			type = xt_find_attr( c, "type" ); + +			if( type && ( strcmp( type, "bytestreams" ) == 0 ) && +			    category && ( strcmp( category, "proxy" ) == 0 ) ) +				jabber_iq_query_server( ic, xt_find_attr( node, "from" ), XMLNS_BYTESTREAMS ); + +			c = c->next; +		} +	} else if( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_BYTESTREAMS ) == 0 ) +	{ +		char *host, *jid; +		int port; + +		/* answer from proxy */ + +		if( ( c = xt_find_node( c->children, "streamhost" ) ) && +		    ( host = xt_find_attr( c, "host" ) ) && +		    ( port = atoi( xt_find_attr( c, "port" ) ) ) && +		    ( jid = xt_find_attr( c, "jid" ) ) ) +		{ +			jabber_streamhost_t *sh = g_new0( jabber_streamhost_t, 1 ); +			sh->jid = g_strdup( jid ); +			sh->host = g_strdup( host ); +			sprintf( sh->port, "%u", port ); + +			imcb_log( ic, "Proxy found: jid %s host %s port %u", jid, host, port ); +			jd->streamhosts = g_slist_append( jd->streamhosts, sh ); +		} +	} + +	if( jd->have_streamhosts == 0 ) +		jd->have_streamhosts++; +	return XT_HANDLED; +} diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c index c9c1d0a0..48f71ff1 100644 --- a/protocols/jabber/jabber.c +++ b/protocols/jabber/jabber.c @@ -79,6 +79,8 @@ static void jabber_init( account_t *acc )  	s = set_add( &acc->set, "xmlconsole", "false", set_eval_bool, acc );  	s->flags |= ACC_SET_OFFLINE_ONLY; + +	s = set_add( &acc->set, "proxy", "<local>;<auto>", NULL, acc );  }  static void jabber_generate_id_hash( struct jabber_data *jd ); @@ -259,6 +261,18 @@ static void jabber_logout( struct im_connection *ic )  {  	struct jabber_data *jd = ic->proto_data; +	while( jd->filetransfers ) +		imcb_file_canceled( ( ( struct jabber_transfer *) jd->filetransfers->data )->ft, "Logging out" ); + +	while( jd->streamhosts ) +	{ +		jabber_streamhost_t *sh = jd->streamhosts->data; +		jd->streamhosts = g_slist_remove( jd->streamhosts, sh ); +		g_free( sh->jid ); +		g_free( sh->host ); +		g_free( sh ); +	} +  	if( jd->fd >= 0 )  		jabber_end_stream( ic ); @@ -545,6 +559,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 904bf0c4..cc12bdbb 100644 --- a/protocols/jabber/jabber.h +++ b/protocols/jabber/jabber.h @@ -58,6 +58,14 @@ typedef enum  	                                   have a real JID. */  } jabber_buddy_flags_t; +/* Stores a streamhost's(a.k.a. proxy) data */ +typedef struct +{ +	char *jid; +	char *host; +	char port[6]; +} jabber_streamhost_t; +  typedef enum  {  	JCFLAG_MESSAGE_SENT = 1,        /* Set this after sending the first message, so @@ -88,6 +96,10 @@ struct jabber_data  	md5_state_t cached_id_prefix;  	GHashTable *node_cache;  	GHashTable *buddies; + +	GSList *filetransfers; +	GSList *streamhosts; +	int have_streamhosts;  };  struct jabber_away_state @@ -117,6 +129,7 @@ struct jabber_buddy  	int priority;  	struct jabber_away_state *away_state;  	char *away_message; +	GSList *features;  	time_t last_act;  	jabber_buddy_flags_t flags; @@ -132,6 +145,36 @@ 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; + +	/* timeout for discover queries */ +	gint disco_timeout; +	gint disco_timeout_fired; + +	struct im_connection *ic; + +	struct jabber_buddy *bud; + +	int watch_in; +	int watch_out; + +	char *ini_jid; +	char *tgt_jid; +	char *iq_id; +	char *sid; +	int accepted; + +	size_t bytesread, byteswritten; +	int fd; +	struct sockaddr_storage saddr; +}; +  #define JABBER_XMLCONSOLE_HANDLE "xmlconsole"  /* Prefixes to use for packet IDs (mainly for IQ packets ATM). Usually the @@ -157,17 +200,24 @@ 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_PING         "urn:xmpp:ping"                      /* XEP-0199 */ -#define XMLNS_VCARD        "vcard-temp"                         /* XEP-0054 */ -#define XMLNS_DELAY        "jabber:x:delay"                     /* XEP-0091 */ -#define XMLNS_CHATSTATES   "http://jabber.org/protocol/chatstates"  /* 0085 */ -#define XMLNS_DISCOVER     "http://jabber.org/protocol/disco#info"  /* 0030 */ -#define XMLNS_MUC          "http://jabber.org/protocol/muc"     /* XEP-0045 */ -#define XMLNS_MUC_USER     "http://jabber.org/protocol/muc#user"/* XEP-0045 */ -#define XMLNS_CAPS         "http://jabber.org/protocol/caps"    /* XEP-0115 */ +#define XMLNS_AUTH         "jabber:iq:auth"                                      /* XEP-0078 */ +#define XMLNS_VERSION      "jabber:iq:version"                                   /* XEP-0092 */ +#define XMLNS_TIME         "jabber:iq:time"                                      /* XEP-0090 */ +#define XMLNS_PING         "urn:xmpp:ping"                                       /* XEP-0199 */ +#define XMLNS_VCARD        "vcard-temp"                                          /* XEP-0054 */ +#define XMLNS_DELAY        "jabber:x:delay"                                      /* XEP-0091 */ +#define XMLNS_XDATA        "jabber:x:data"                                       /* XEP-0004 */ +#define XMLNS_CHATSTATES   "http://jabber.org/protocol/chatstates"               /* XEP-0085 */ +#define XMLNS_DISCO_INFO   "http://jabber.org/protocol/disco#info"               /* XEP-0030 */ +#define XMLNS_DISCO_ITEMS  "http://jabber.org/protocol/disco#items"              /* XEP-0030 */ +#define XMLNS_MUC          "http://jabber.org/protocol/muc"                      /* XEP-0045 */ +#define XMLNS_MUC_USER     "http://jabber.org/protocol/muc#user"                 /* XEP-0045 */ +#define XMLNS_CAPS         "http://jabber.org/protocol/caps"                     /* XEP-0115 */ +#define XMLNS_FEATURE      "http://jabber.org/protocol/feature-neg"              /* XEP-0020 */ +#define XMLNS_SI           "http://jabber.org/protocol/si"                       /* XEP-0095 */ +#define XMLNS_FILETRANSFER "http://jabber.org/protocol/si/profile/file-transfer" /* XEP-0096 */ +#define XMLNS_BYTESTREAMS  "http://jabber.org/protocol/bytestreams"              /* XEP-0065 */ +#define XMLNS_IBB          "http://jabber.org/protocol/ibb"                      /* XEP-0047 */  /* iq.c */  xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ); @@ -177,6 +227,18 @@ int jabber_get_roster( struct im_connection *ic );  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 ); +xt_status jabber_iq_query_features( struct im_connection *ic, char *bare_jid ); +xt_status jabber_iq_query_server( struct im_connection *ic, char *jid, char *xmlns ); + +/* si.c */ +int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, struct xt_node *sinode ); +void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who ); +void jabber_si_free_transfer( file_transfer_t *ft); + +/* s5bytestream.c */ +int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode); +gboolean jabber_bs_send_start( struct jabber_transfer *tf ); +gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, unsigned int len );  /* message.c */  xt_status jabber_pkt_message( struct xt_node *node, gpointer data ); @@ -190,7 +252,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 1bee5009..9579a848 100644 --- a/protocols/jabber/jabber_util.c +++ b/protocols/jabber/jabber_util.c @@ -98,7 +98,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; @@ -111,6 +111,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/s5bytestream.c b/protocols/jabber/s5bytestream.c new file mode 100644 index 00000000..7e4ca2ab --- /dev/null +++ b/protocols/jabber/s5bytestream.c @@ -0,0 +1,1196 @@ +/***************************************************************************\ +*                                                                           * +*  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> + +/* Some ifdefs for ulibc (Thanks to Whoopie) */ +#ifndef HOST_NAME_MAX +#include <sys/param.h> +#ifdef MAXHOSTNAMELEN +#define HOST_NAME_MAX MAXHOSTNAMELEN +#else +#define HOST_NAME_MAX 255 +#endif +#endif + +#ifndef AI_NUMERICSERV +#define AI_NUMERICSERV 0x0400   /* Don't use name resolution.  */ +#endif + +struct bs_transfer { + +	struct jabber_transfer *tf; + +	jabber_streamhost_t *sh; +	GSList *streamhosts; + +	enum  +	{  +		BS_PHASE_CONNECT,  +		BS_PHASE_CONNECTED,  +		BS_PHASE_REQUEST,  +		BS_PHASE_REPLY +	} phase; + +	/* SHA1( SID + Initiator JID + Target JID) */ +	char *pseudoadr; + +	gint connect_timeout; +}; + +struct socks5_message +{ +	unsigned char ver; +	union +	{ +		unsigned char cmd; +		unsigned char rep; +	} cmdrep; +	unsigned char rsv; +	unsigned char atyp; +	unsigned char addrlen; +	unsigned char address[40]; +	in_port_t port; +} __attribute__ ((packed));  + +char *socks5_reply_code[] = { +	"succeeded", +	"general SOCKS server failure", +	"connection not allowed by ruleset", +	"Network unreachable", +	"Host unreachable", +	"Connection refused", +	"TTL expired", +	"Command not supported", +	"Address type not supported", +	"unassigned"}; + +/* connect() timeout in seconds. */ +#define JABBER_BS_CONTIMEOUT 15 +/* listen timeout */ +#define JABBER_BS_LISTEN_TIMEOUT  90 + +/* very useful */ +#define ASSERTSOCKOP(op, msg) \ +	if( (op) == -1 ) \ +		return jabber_bs_abort( bt , msg ": %s", strerror( errno ) ); + +gboolean jabber_bs_abort( struct bs_transfer *bt, char *format, ... ); +void jabber_bs_canceled( file_transfer_t *ft , char *reason ); +void jabber_bs_free_transfer( file_transfer_t *ft ); +gboolean jabber_bs_connect_timeout( gpointer data, gint fd, b_input_condition cond ); +gboolean jabber_bs_poll( struct bs_transfer *bt, int fd, short *revents ); +gboolean jabber_bs_peek( struct bs_transfer *bt, void *buffer, int buflen ); + +void jabber_bs_recv_answer_request( struct bs_transfer *bt ); +gboolean jabber_bs_recv_read( gpointer data, gint fd, b_input_condition cond ); +gboolean jabber_bs_recv_write_request( file_transfer_t *ft ); +gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition cond ); +gboolean jabber_bs_recv_handshake_abort( struct bs_transfer *bt, char *error ); +int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode ); + +gboolean jabber_bs_send_handshake_abort( struct bs_transfer *bt, char *error ); +gboolean jabber_bs_send_request( struct jabber_transfer *tf, GSList *streamhosts ); +gboolean jabber_bs_send_handshake( gpointer data, gint fd, b_input_condition cond ); +gboolean jabber_bs_send_listen( struct bs_transfer *bt, struct sockaddr_storage *saddr, char *host, char *port ); +static xt_status jabber_bs_send_handle_activate( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); +void jabber_bs_send_activate( struct bs_transfer *bt ); + +/* + * Frees a bs_transfer struct and calls the SI free function + */ +void jabber_bs_free_transfer( file_transfer_t *ft) { +	struct jabber_transfer *tf = ft->data; +	struct bs_transfer *bt = tf->streamhandle; +	jabber_streamhost_t *sh; + +	if ( bt->connect_timeout ) +	{ +		b_event_remove( bt->connect_timeout ); +		bt->connect_timeout = 0; +	} + +	if ( tf->watch_in ) +		b_event_remove( tf->watch_in ); +	 +	if( tf->watch_out ) +		b_event_remove( tf->watch_out ); +	 +	g_free( bt->pseudoadr ); + +	while( bt->streamhosts ) +	{ +		sh = bt->streamhosts->data; +		bt->streamhosts = g_slist_remove( bt->streamhosts, sh ); +		g_free( sh->jid ); +		g_free( sh->host ); +		g_free( sh ); +	} +	 +	g_free( bt ); + +	jabber_si_free_transfer( ft ); +} + +/* + * Checks if buflen data is available on the socket and + * writes it to buffer if that's the case. + */ +gboolean jabber_bs_peek( struct bs_transfer *bt, void *buffer, int buflen ) +{ +	int ret; +	int fd = bt->tf->fd; + +	ASSERTSOCKOP( ret = recv( fd, buffer, buflen, MSG_PEEK ), "MSG_PEEK'ing" ); + +	if( ret == 0 ) +		return jabber_bs_abort( bt, "Remote end closed connection" ); +		 +	if( ret < buflen ) +		return ret; + +	ASSERTSOCKOP( ret = recv( fd, buffer, buflen, 0 ), "Dequeuing after MSG_PEEK" ); + +	if( ret != buflen ) +		return jabber_bs_abort( bt, "recv returned less than previous recv with MSG_PEEK" ); +	 +	return ret; +} + + +/*  + * This function is scheduled in bs_handshake via b_timeout_add after a (non-blocking) connect(). + */ +gboolean jabber_bs_connect_timeout( gpointer data, gint fd, b_input_condition cond ) +{ +	struct bs_transfer *bt = data; + +	bt->connect_timeout = 0; + +	jabber_bs_abort( bt, "no connection after %d seconds", bt->tf->ft->sending ? JABBER_BS_LISTEN_TIMEOUT : JABBER_BS_CONTIMEOUT ); + +	return FALSE; +} + +/*  + * Polls the socket, checks for errors and removes a connect timer + * if there is one. + */ +gboolean jabber_bs_poll( struct bs_transfer *bt, int fd, short *revents ) +{ +	struct pollfd pfd = { .fd = fd, .events = POLLHUP|POLLERR }; +	 +	if ( bt->connect_timeout ) +	{ +		b_event_remove( bt->connect_timeout ); +		bt->connect_timeout = 0; +	} + +	ASSERTSOCKOP( poll( &pfd, 1, 0 ), "poll()" ) + +	if( pfd.revents & POLLERR ) +	{ +		int sockerror; +		socklen_t errlen = sizeof( sockerror ); + +		if ( getsockopt( fd, SOL_SOCKET, SO_ERROR, &sockerror, &errlen ) ) +			return jabber_bs_abort( bt, "getsockopt() failed, unknown socket error during SOCKS5 handshake (weird!)" ); + +		if ( bt->phase == BS_PHASE_CONNECTED ) +			return jabber_bs_abort( bt, "connect failed: %s", strerror( sockerror ) ); + +		return jabber_bs_abort( bt, "Socket error during SOCKS5 handshake(weird!): %s", strerror( sockerror ) ); +	} + +	if( pfd.revents & POLLHUP ) +		return jabber_bs_abort( bt, "Remote end closed connection" ); +	 +	*revents = pfd.revents; +	 +	return TRUE; +} + +/* + * Used for receive and send path. + */ +gboolean jabber_bs_abort( struct bs_transfer *bt, char *format, ... ) +{ +	va_list params; +	va_start( params, format ); +	char error[128]; + +	if( vsnprintf( error, 128, format, params ) < 0 ) +		sprintf( error, "internal error parsing error string (BUG)" ); +	va_end( params ); +	if( bt->tf->ft->sending ) +		return jabber_bs_send_handshake_abort( bt, error ); +	else +		return jabber_bs_recv_handshake_abort( bt, error ); +} + +/* Bad luck */ +void jabber_bs_canceled( file_transfer_t *ft , char *reason ) +{ +	struct jabber_transfer *tf = ft->data; + +	imcb_log( tf->ic, "File transfer aborted: %s", reason ); +} + +/* + * Parses an incoming bytestream request and calls jabber_bs_handshake on success. + */ +int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode) +{ +	char *sid, *ini_jid, *tgt_jid, *mode, *iq_id; +	struct jabber_data *jd = ic->proto_data; +	struct jabber_transfer *tf = NULL; +	GSList *tflist; +	struct bs_transfer *bt; +	GSList *shlist=NULL; +	struct xt_node *shnode; + +	sha1_state_t sha; +	char hash_hex[41]; +	unsigned char hash[20]; +	int i; +	 +	if( !(iq_id   = xt_find_attr( node, "id" ) ) || +	    !(ini_jid = xt_find_attr( node, "from" ) ) || +	    !(tgt_jid = xt_find_attr( node, "to" ) ) || +	    !(sid     = xt_find_attr( qnode, "sid" ) ) ) +	{ +		imcb_log( ic, "WARNING: Received incomplete SI bytestream request"); +		return XT_HANDLED; +	} + +	if( ( mode = xt_find_attr( qnode, "mode" ) ) && +	      ( strcmp( mode, "tcp" ) != 0 ) )  +	{ +		imcb_log( ic, "WARNING: Received SI Request for unsupported bytestream mode %s", xt_find_attr( qnode, "mode" ) ); +		return XT_HANDLED; +	} + +	shnode = qnode->children; +	while( ( shnode = xt_find_node( shnode, "streamhost" ) ) ) +	{ +		char *jid, *host; +		int port; +		if( ( jid = xt_find_attr( shnode, "jid" ) ) && +		    ( host = xt_find_attr( shnode, "host" ) ) && +		    ( ( port = atoi( xt_find_attr( shnode, "port" ) ) ) ) ) +		{ +			jabber_streamhost_t *sh = g_new0( jabber_streamhost_t, 1 ); +			sh->jid = g_strdup(jid); +			sh->host = g_strdup(host); +			sprintf( sh->port, "%u", port ); +			shlist = g_slist_append( shlist, sh ); +		} +		shnode = shnode->next; +	} +	 +	if( !shlist ) +	{ +		imcb_log( ic, "WARNING: Received incomplete SI bytestream request, no parseable streamhost entries"); +		return XT_HANDLED; +	} + +	/* Let's see if we can find out what this bytestream should be for... */ + +	for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) +	{ +		struct jabber_transfer *tft = tflist->data; +		if( ( strcmp( tft->sid, sid ) == 0 ) && +		    ( strcmp( tft->ini_jid, ini_jid ) == 0 ) && +		    ( strcmp( tft->tgt_jid, tgt_jid ) == 0 ) ) +		{ +		    	tf = tft; +			break; +		} +	} + +	if (!tf)  +	{ +		imcb_log( ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid ); +		return XT_HANDLED; +	} + +	/* iq_id and canceled can be reused since SI is done */ +	g_free( tf->iq_id ); +	tf->iq_id = g_strdup( iq_id ); + +	tf->ft->canceled = jabber_bs_canceled; + +	/* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */ +	sha1_init( &sha ); +	sha1_append( &sha, (unsigned char*) sid, strlen( sid ) ); +	sha1_append( &sha, (unsigned char*) ini_jid, strlen( ini_jid ) ); +	sha1_append( &sha, (unsigned char*) tgt_jid, strlen( tgt_jid ) ); +	sha1_finish( &sha, hash ); +	 +	for( i = 0; i < 20; i ++ ) +		sprintf( hash_hex + i * 2, "%02x", hash[i] ); +		 +	bt = g_new0( struct bs_transfer, 1 ); +	bt->tf = tf; +	bt->streamhosts = shlist; +	bt->sh = shlist->data; +	bt->phase = BS_PHASE_CONNECT; +	bt->pseudoadr = g_strdup( hash_hex ); +	tf->streamhandle = bt; +	tf->ft->free = jabber_bs_free_transfer; + +	jabber_bs_recv_handshake( bt, -1, 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; +	int gret; + +	if ( ( fd != -1 ) && !jabber_bs_poll( bt, fd, &revents ) ) +		return FALSE; +	 +	switch( bt->phase )  +	{ +	case BS_PHASE_CONNECT: +		{ +			struct addrinfo hints, *rp; + +			memset( &hints, 0, sizeof( struct addrinfo ) ); +			hints.ai_socktype = SOCK_STREAM; + +			if ( ( gret = getaddrinfo( bt->sh->host, bt->sh->port, &hints, &rp ) ) != 0 ) +				return jabber_bs_abort( bt, "getaddrinfo() failed: %s", gai_strerror( gret ) ); + +			ASSERTSOCKOP( bt->tf->fd = fd = socket( rp->ai_family, rp->ai_socktype, 0 ), "Opening socket" ); + +			sock_make_nonblocking( fd ); + +			imcb_log( bt->tf->ic, "File %s: Connecting to streamhost %s:%s", bt->tf->ft->file_name, bt->sh->host, bt->sh->port ); + +			if( ( connect( fd, rp->ai_addr, rp->ai_addrlen ) == -1 ) && +			    ( errno != EINPROGRESS ) ) +				return jabber_bs_abort( bt , "connect() failed: %s", strerror( errno ) ); + +			freeaddrinfo( rp ); + +			bt->phase = BS_PHASE_CONNECTED; +			 +			bt->tf->watch_out = b_input_add( fd, GAIM_INPUT_WRITE, jabber_bs_recv_handshake, bt ); + +			/* since it takes forever(3mins?) till connect() fails on itself we schedule a timeout */ +			bt->connect_timeout = b_timeout_add( JABBER_BS_CONTIMEOUT * 1000, jabber_bs_connect_timeout, bt ); + +			bt->tf->watch_in = 0; +			return FALSE; +		} +	case BS_PHASE_CONNECTED: +		{ +			struct { +				unsigned char ver; +				unsigned char nmethods; +				unsigned char method; +			} socks5_hello = { +				.ver = 5, +				.nmethods = 1, +				.method = 0x00 /* no auth */ +				/* one could also implement username/password. If you know +				 * a jabber client or proxy that actually does it, tell me. +				 */ +			}; +			 +			ASSERTSOCKOP( send( fd, &socks5_hello, sizeof( socks5_hello ) , 0 ), "Sending auth request" ); + +			bt->phase = BS_PHASE_REQUEST; + +			bt->tf->watch_in = b_input_add( fd, GAIM_INPUT_READ, jabber_bs_recv_handshake, bt ); + +			bt->tf->watch_out = 0; +			return FALSE; +		} +	case BS_PHASE_REQUEST: +		{ +			struct socks5_message socks5_connect =  +			{ +				.ver = 5, +				.cmdrep.cmd = 0x01, +				.rsv = 0, +				.atyp = 0x03, +				.addrlen = strlen( bt->pseudoadr ), +				.port = 0 +			}; +			int ret; +			char buf[2]; + +			/* If someone's trying to be funny and sends only one byte at a time we'll fail :) */ +			ASSERTSOCKOP( ret = recv( fd, buf, 2, 0 ) , "Receiving auth reply" ); + +			if( !( ret == 2 ) || +			    !( buf[0] == 5 ) || +			    !( buf[1] == 0 ) ) +				return jabber_bs_abort( bt, "Auth not accepted by streamhost (reply: len=%d, ver=%d, status=%d)", +									ret, buf[0], buf[1] ); + +			/* copy hash into connect message */ +			memcpy( socks5_connect.address, bt->pseudoadr, socks5_connect.addrlen ); + +			ASSERTSOCKOP( send( fd, &socks5_connect, sizeof( struct socks5_message ), 0 ) , "Sending SOCKS5 Connect" ); + +			bt->phase = BS_PHASE_REPLY; + +			return TRUE; +		} +	case BS_PHASE_REPLY: +		{ +			struct socks5_message socks5_reply; +			int ret; + +			if ( !( ret = jabber_bs_peek( bt, &socks5_reply, sizeof( struct socks5_message ) ) ) ) +				return FALSE; + +			if ( ret < 5 ) /* header up to address length */ +				return TRUE; +			else if( ret < sizeof( struct socks5_message ) ) +			{ +				/* Either a buggy proxy or just one that doesnt regard the SHOULD in XEP-0065 +				 * saying the reply SHOULD contain the address */ + +				ASSERTSOCKOP( ret = recv( fd, &socks5_reply, ret, 0 ), "Dequeuing after MSG_PEEK" ); +			} + +			if( !( socks5_reply.ver == 5 ) || +			    !( socks5_reply.cmdrep.rep == 0 ) ) { +			    	char errstr[128] = ""; +				if( ( socks5_reply.ver == 5 ) && ( socks5_reply.cmdrep.rep <  +				    ( sizeof( socks5_reply_code ) / sizeof( socks5_reply_code[0] ) ) ) ) { +					sprintf( errstr, "with \"%s\" ", socks5_reply_code[ socks5_reply.cmdrep.rep ] ); +				} +				return jabber_bs_abort( bt, "SOCKS5 CONNECT failed %s(reply: ver=%d, rep=%d, atyp=%d, addrlen=%d)",  +					errstr, +					socks5_reply.ver, +					socks5_reply.cmdrep.rep, +					socks5_reply.atyp, +					socks5_reply.addrlen); +			} +			 +			/* usually a proxy sends back the 40 bytes address but I encountered at least one (of jabber.cz)  +			 * that sends atyp=0 addrlen=0 and only 6 bytes (one less than one would expect). +			 * Therefore I removed the wait for more bytes. Since we don't care about what else the proxy +			 * is sending, it shouldnt matter */ + +			if( bt->tf->ft->sending ) +				jabber_bs_send_activate( bt ); +			else +				jabber_bs_recv_answer_request( bt ); + +			return FALSE; +		} +	default: +		/* BUG */ +		imcb_log( bt->tf->ic, "BUG in file transfer code: undefined handshake phase" ); + +		bt->tf->watch_in = 0; +		return FALSE; +	} +} + +/* + * If the handshake failed we can try the next streamhost, if there is one. + * An intelligent sender would probably specify himself as the first streamhost and + * a proxy as the second (Kopete and PSI are examples here). That way, a (potentially)  + * slow proxy is only used if neccessary. This of course also means, that the timeout + * per streamhost should be kept short. If one or two firewalled adresses are specified, + * they have to timeout first before a proxy is tried. + */ +gboolean jabber_bs_recv_handshake_abort( struct bs_transfer *bt, char *error ) +{ +	struct jabber_transfer *tf = bt->tf; +	struct xt_node *reply, *iqnode; +	GSList *shlist; + +	imcb_log( tf->ic, "Transferring file %s: connection to streamhost %s:%s failed (%s)",  +		  tf->ft->file_name,  +		  bt->sh->host, +		  bt->sh->port, +		  error ); + +	/* Alright, this streamhost failed, let's try the next... */ +	bt->phase = BS_PHASE_CONNECT; +	shlist = g_slist_find( bt->streamhosts, bt->sh ); +	if( shlist && shlist->next ) +	{ +		bt->sh = shlist->next->data; +		return jabber_bs_recv_handshake( bt, -1, 0 ); +	} + + +	/* out of stream hosts */ + +	iqnode = jabber_make_packet( "iq", "result", tf->ini_jid, NULL ); +	reply = jabber_make_error_packet( iqnode, "item-not-found", "cancel" , "404" ); +	xt_free_node( iqnode ); + +	xt_add_attr( reply, "id", tf->iq_id ); +		 +	if( !jabber_write_packet( tf->ic, reply ) ) +		imcb_log( tf->ic, "WARNING: Error transmitting bytestream response" ); +	xt_free_node( reply ); + +	imcb_file_canceled( tf->ft, "couldn't connect to any streamhosts" ); + +	bt->tf->watch_in = 0; +	return FALSE; +} + +/*  + * After the SOCKS5 handshake succeeds we need to inform the initiator which streamhost we chose. + * If he is the streamhost himself, he might already know that. However, if it's a proxy, + * the initiator will have to make a connection himself. + */ +void jabber_bs_recv_answer_request( struct bs_transfer *bt ) +{ +	struct jabber_transfer *tf = bt->tf; +	struct xt_node *reply; + +	imcb_log( tf->ic, "File %s: established SOCKS5 connection to %s:%s",  +		  tf->ft->file_name,  +		  bt->sh->host, +		  bt->sh->port ); + +	tf->ft->data = tf; +	tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_recv_read, bt ); +	tf->ft->write_request = jabber_bs_recv_write_request; + +	reply = xt_new_node( "streamhost-used", NULL, NULL ); +	xt_add_attr( reply, "jid", bt->sh->jid ); + +	reply = xt_new_node( "query", NULL, reply ); +	xt_add_attr( reply, "xmlns", XMLNS_BYTESTREAMS ); + +	reply = jabber_make_packet( "iq", "result", tf->ini_jid, reply ); + +	xt_add_attr( reply, "id", tf->iq_id ); +		 +	if( !jabber_write_packet( tf->ic, reply ) ) +		imcb_file_canceled( tf->ft, "Error transmitting bytestream response" ); +	xt_free_node( reply ); +} + +/*  + * This function is called from write_request directly. If no data is available, it will install itself + * as a watcher for input on fd and once that happens, deliver the data and unschedule itself again. + */ +gboolean jabber_bs_recv_read( gpointer data, gint fd, b_input_condition cond ) +{ +	int ret; +	struct bs_transfer *bt = data; +	struct jabber_transfer *tf = bt->tf; + +	if( fd != -1 ) /* called via event thread */ +	{ +		tf->watch_in = 0; +		ASSERTSOCKOP( ret = recv( fd, tf->ft->buffer, sizeof( tf->ft->buffer ), 0 ) , "Receiving" ); +	} +	else +	{ +		/* called directly. There might not be any data available. */ +		if( ( ( ret = recv( tf->fd, tf->ft->buffer, sizeof( tf->ft->buffer ), 0 ) ) == -1 ) && +		    ( errno != EAGAIN ) ) +		    return jabber_bs_abort( bt, "Receiving: %s", strerror( errno ) ); + +		if( ( ret == -1 ) && ( errno == EAGAIN ) ) +		{ +			tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_recv_read, bt ); +			return FALSE; +		} +	} + +	/* shouldn't happen since we know the file size */ +	if( ret == 0 ) +		return jabber_bs_abort( bt, "Remote end closed connection" ); +	 +	tf->bytesread += ret; + +	if( tf->bytesread >= tf->ft->file_size ) +		imcb_file_finished( tf->ft ); + +	tf->ft->write( tf->ft, tf->ft->buffer, ret );	 + +	return FALSE; +} + +/*  + * imc callback that is invoked when it is ready to receive some data. + */ +gboolean jabber_bs_recv_write_request( file_transfer_t *ft ) +{ +	struct jabber_transfer *tf = ft->data; + +	if( tf->watch_in ) +	{ +		imcb_file_canceled( ft, "BUG in jabber file transfer: write_request called when already watching for input" ); +		return FALSE; +	} +	 +	jabber_bs_recv_read( tf->streamhandle, -1 , 0 ); + +	return TRUE; +} + +/*  + * Issues a write_request to imc. + * */ +gboolean jabber_bs_send_can_write( gpointer data, gint fd, b_input_condition cond ) +{ +	struct bs_transfer *bt = data; + +	bt->tf->watch_out = 0; + +	bt->tf->ft->write_request( bt->tf->ft ); + +	return FALSE; +} + +/* + * This should only be called if we can write, so just do it. + * Add a write watch so we can write more during the next cycle (if possible). + */ +gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, unsigned int len ) +{ +	struct jabber_transfer *tf = ft->data; +	struct bs_transfer *bt = tf->streamhandle; +	int ret; + +	if( tf->watch_out ) +		return jabber_bs_abort( bt, "BUG: write() called while watching " ); +	 +	/* TODO: catch broken pipe */ +	ASSERTSOCKOP( ret = send( tf->fd, buffer, len, 0 ), "Sending" ); + +	tf->byteswritten += ret; +	 +	/* TODO: this should really not be fatal */ +	if( ret < len ) +		return jabber_bs_abort( bt, "send() sent %d instead of %d (send buffer too big!)", ret, len ); + +	if( tf->byteswritten >= ft->file_size ) +		imcb_file_finished( ft ); +	else +		bt->tf->watch_out = b_input_add( tf->fd, GAIM_INPUT_WRITE, jabber_bs_send_can_write, bt ); +		 +	return TRUE; +} + +/* + * Handles the reply by the receiver containing the used streamhost. + */ +static xt_status jabber_bs_send_handle_reply(struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) { +	struct jabber_transfer *tf = NULL; +	struct jabber_data *jd = ic->proto_data; +	struct bs_transfer *bt; +	GSList *tflist; +	struct xt_node *c; +	char *sid, *jid; + +	if( !( c = xt_find_node( node->children, "query" ) ) || +	    !( c = xt_find_node( c->children, "streamhost-used" ) ) || +	    !( jid = xt_find_attr( c, "jid" ) ) ) + +	{ +		imcb_log( ic, "WARNING: Received incomplete bytestream reply" ); +		return XT_HANDLED; +	} +	 +	if( !( c = xt_find_node( orig->children, "query" ) ) || +	    !( sid = xt_find_attr( c, "sid" ) ) ) +	{ +		imcb_log( ic, "WARNING: Error parsing request corresponding to the incoming bytestream reply" ); +		return XT_HANDLED; +	} + +	/* Let's see if we can find out what this bytestream should be for... */ + +	for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) +	{ +		struct jabber_transfer *tft = tflist->data; +		if( ( strcmp( tft->sid, sid ) == 0 ) ) +		{ +		    	tf = tft; +			break; +		} +	} + +	if( !tf ) +	{ +		imcb_log( ic, "WARNING: Received SOCKS5 bytestream reply to unknown request" ); +		return XT_HANDLED; +	} + +	bt = tf->streamhandle; + +	tf->accepted = TRUE; + +	if( strcmp( jid, tf->ini_jid ) == 0 ) +	{ +		/* we're streamhost and target */ +		if( bt->phase == BS_PHASE_REPLY ) +		{ +			/* handshake went through, let's start transferring */ +			tf->ft->write_request( tf->ft ); +		} +	} else +	{ +		/* using a proxy, abort listen */ + +		if( tf->watch_in ) +		{ +			b_event_remove( tf->watch_in ); +			tf->watch_in = 0; +		} +		 +		if( tf->fd != -1 ) { +			closesocket( tf->fd ); +			tf->fd = -1; +		} + +		if ( bt->connect_timeout ) +		{ +			b_event_remove( bt->connect_timeout ); +			bt->connect_timeout = 0; +		} + +		GSList *shlist; +		for( shlist = jd->streamhosts ; shlist ; shlist = g_slist_next( shlist ) ) +		{ +			jabber_streamhost_t *sh = shlist->data; +			if( strcmp( sh->jid, jid ) == 0 ) +			{ +				bt->sh = sh; +				jabber_bs_recv_handshake( bt, -1, 0 ); +				return XT_HANDLED; +			} +		} + +		imcb_log( ic, "WARNING: Received SOCKS5 bytestream reply with unknown streamhost %s", jid ); +	} + +	return XT_HANDLED; +} + +/*  + * Tell the proxy to activate the stream. Looks like this: + * + * <iq type=set> + * 	<query xmlns=bs sid=sid> + * 		<activate>tgt_jid</activate> + * 	</query> + * </iq> + */ +void jabber_bs_send_activate( struct bs_transfer *bt ) +{ +	struct xt_node *node; + +	node = xt_new_node( "activate", bt->tf->tgt_jid, NULL ); +	node = xt_new_node( "query", NULL, node ); +	xt_add_attr( node, "xmlns", XMLNS_BYTESTREAMS ); +	xt_add_attr( node, "sid", bt->tf->sid ); +	node = jabber_make_packet( "iq", "set", bt->sh->jid, node ); + +	jabber_cache_add( bt->tf->ic, node, jabber_bs_send_handle_activate ); + +	jabber_write_packet( bt->tf->ic, node ); +} + +/* + * The proxy has activated the bytestream. + * We can finally start pushing some data out. + */ +static xt_status jabber_bs_send_handle_activate( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ +	char *sid; +	GSList *tflist; +	struct jabber_transfer *tf; +	struct xt_node *query; +	struct jabber_data *jd = ic->proto_data; + +	query = xt_find_node( orig->children, "query" ); +	sid = xt_find_attr( query, "sid" ); + +	for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) +	{ +		struct jabber_transfer *tft = tflist->data; +		if( ( strcmp( tft->sid, sid ) == 0 ) ) +		{ +		    	tf = tft; +			break; +		} +	} + +	if( !tf ) +	{ +		imcb_log( ic, "WARNING: Received SOCKS5 bytestream activation for unknown stream" ); +		return XT_HANDLED; +	} + +	imcb_log( tf->ic, "File %s: SOCKS5 handshake and activation successful! Transfer about to start...", tf->ft->file_name ); + +	/* handshake went through, let's start transferring */ +	tf->ft->write_request( tf->ft ); + +	return XT_HANDLED; +} + +jabber_streamhost_t *jabber_si_parse_proxy( struct im_connection *ic, char *proxy ) +{ +	char *host, *port, *jid; +	jabber_streamhost_t *sh; + +	if( ( ( host = strchr( proxy, ',' ) ) == 0 ) || +	     ( ( port = strchr( host+1, ',' ) ) == 0 ) ) { +		imcb_log( ic, "Error parsing proxy setting: \"%s\" (ignored)", proxy ); +		return NULL; +	} +	 +	jid = proxy; +	*host++ = '\0'; +	*port++ = '\0'; + +	sh = g_new0( jabber_streamhost_t, 1 ); +	sh->jid = g_strdup( jid ); +	sh->host = g_strdup( host ); +	strcpy( sh->port, port ); + +	return sh; +} + +void jabber_si_set_proxies( struct bs_transfer *bt ) +{ +	struct jabber_transfer *tf = bt->tf; +	struct jabber_data *jd = tf->ic->proto_data; +	char *proxysetting = g_strdup ( set_getstr( &tf->ic->acc->set, "proxy" ) ); +	char *proxy,*next; +	char port[6]; +	char host[INET6_ADDRSTRLEN]; +	jabber_streamhost_t *sh, *sh2; +	GSList *streamhosts = jd->streamhosts; + +	proxy = proxysetting; +	while ( proxy && ( *proxy!='\0' ) ) { +		if( ( next = strchr( proxy, ';' ) ) ) +			*next++ = '\0';	 +		 +		if( ( strcmp( proxy, "<local>" ) == 0 ) && jabber_bs_send_listen( bt, &tf->saddr, host, port ) ) { +			sh = g_new0( jabber_streamhost_t, 1 ); +			sh->jid = g_strdup( tf->ini_jid ); +			sh->host = g_strdup( host ); +			strcpy( sh->port, port ); +			bt->streamhosts = g_slist_append( bt->streamhosts, sh ); + +			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 ); +		} else if( strcmp( proxy, "<auto>" ) == 0 ) { +			while ( streamhosts ) { +				sh = g_new0( jabber_streamhost_t, 1 ); +				sh2 = streamhosts->data; +				sh->jid = g_strdup( sh2->jid ); +				sh->host = g_strdup( sh2->host ); +				strcpy( sh->port, sh2->port ); +				bt->streamhosts = g_slist_append( bt->streamhosts, sh ); +				streamhosts = g_slist_next( streamhosts ); +			} +		} else if( ( sh = jabber_si_parse_proxy( tf->ic, proxy ) ) ) +			bt->streamhosts = g_slist_append( bt->streamhosts, sh ); +		proxy = next; +	} +} + +/* + * Starts a bytestream. + */ +gboolean jabber_bs_send_start( struct jabber_transfer *tf ) +{ +	struct bs_transfer *bt; +	sha1_state_t sha; +	char hash_hex[41]; +	unsigned char hash[20]; +	int i,ret; + +	/* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */ +	sha1_init( &sha ); +	sha1_append( &sha, (unsigned char*) tf->sid, strlen( tf->sid ) ); +	sha1_append( &sha, (unsigned char*) tf->ini_jid, strlen( tf->ini_jid ) ); +	sha1_append( &sha, (unsigned char*) tf->tgt_jid, strlen( tf->tgt_jid ) ); +	sha1_finish( &sha, hash ); +	 +	for( i = 0; i < 20; i ++ ) +		sprintf( hash_hex + i * 2, "%02x", hash[i] ); +		 +	bt = g_new0( struct bs_transfer, 1 ); +	bt->tf = tf; +	bt->phase = BS_PHASE_CONNECT; +	bt->pseudoadr = g_strdup( hash_hex ); +	tf->streamhandle = bt; +	tf->ft->free = jabber_bs_free_transfer; +	tf->ft->canceled = jabber_bs_canceled; + +	jabber_si_set_proxies( bt ); + +	ret = jabber_bs_send_request( tf, bt->streamhosts); + +	return ret; +} + +gboolean jabber_bs_send_request( struct jabber_transfer *tf, GSList *streamhosts ) +{ +	struct xt_node *shnode, *query, *iq; + +	query = xt_new_node( "query", NULL, NULL ); +	xt_add_attr( query, "xmlns", XMLNS_BYTESTREAMS ); +	xt_add_attr( query, "sid", tf->sid ); +	xt_add_attr( query, "mode", "tcp" ); + +	while( streamhosts ) { +		jabber_streamhost_t *sh = streamhosts->data; +		shnode = xt_new_node( "streamhost", NULL, NULL ); +		xt_add_attr( shnode, "jid", sh->jid ); +		xt_add_attr( shnode, "host", sh->host ); +		xt_add_attr( shnode, "port", sh->port ); + +		xt_add_child( query, shnode ); + +		streamhosts = g_slist_next( streamhosts ); +	} + + +	iq = jabber_make_packet( "iq", "set", tf->tgt_jid, query ); +	xt_add_attr( iq, "from", tf->ini_jid ); + +	jabber_cache_add( tf->ic, iq, jabber_bs_send_handle_reply ); + +	if( !jabber_write_packet( tf->ic, iq ) ) +		imcb_file_canceled( tf->ft, "Error transmitting bytestream request" ); +	return TRUE; +} + +gboolean jabber_bs_send_handshake_abort(struct bs_transfer *bt, char *error ) +{ +	struct jabber_transfer *tf = bt->tf; +	struct jabber_data *jd = tf->ic->proto_data; + +	/* TODO: did the receiver get here somehow??? */ +	imcb_log( tf->ic, "Transferring file %s: SOCKS5 handshake failed: %s",  +		  tf->ft->file_name,  +		  error ); + +	if( jd->streamhosts==NULL ) /* we're done here unless we have a proxy to try */ +		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,gret; +	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 ( ( gret = getaddrinfo( hostname, "0", &hints, &rp ) ) != 0 ) +		return jabber_bs_abort( bt, "getaddrinfo() failed: %s", gai_strerror( gret ) ); + +	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 ); +			int ret; + +			if( !( ret = jabber_bs_peek( bt, &socks5_connect, msgsize ) ) ) +				return FALSE; + +			if( ret < msgsize ) +				return TRUE; + +			if( !( socks5_connect.ver == 5) || +			    !( socks5_connect.cmdrep.cmd == 1 ) || +			    !( socks5_connect.atyp == 3 ) || +			    !(socks5_connect.addrlen == 40 ) ) +				return jabber_bs_abort( bt, "Invalid SOCKS5 Connect message (addrlen=%d, ver=%d, cmd=%d, atyp=%d)", socks5_connect.addrlen, socks5_connect.ver, socks5_connect.cmdrep.cmd, socks5_connect.atyp ); +			if( !( memcmp( socks5_connect.address, bt->pseudoadr, 40 ) == 0 ) ) +				return jabber_bs_abort( bt, "SOCKS5 Connect message contained wrong digest"); + +			socks5_connect.cmdrep.rep = 0; + +			ASSERTSOCKOP( send( fd, &socks5_connect, msgsize, 0 ), "Sending connect reply" ); + +			bt->phase = BS_PHASE_REPLY; + +			imcb_log( tf->ic, "File %s: SOCKS5 handshake successful! Transfer about to start...", tf->ft->file_name ); + +			if( tf->accepted ) +			{ +				/* streamhost-used message came already in(possible?), let's start sending */ +				tf->ft->write_request( tf->ft ); +			} + +			tf->watch_in = 0; +			return FALSE; + +		} +	default: +		/* BUG */ +		imcb_log( bt->tf->ic, "BUG in file transfer code: undefined handshake phase" ); + +		bt->tf->watch_in = 0; +		return FALSE; +	} +} +#undef ASSERTSOCKOP diff --git a/protocols/jabber/si.c b/protocols/jabber/si.c new file mode 100644 index 00000000..e7aeffc9 --- /dev/null +++ b/protocols/jabber/si.c @@ -0,0 +1,516 @@ +/***************************************************************************\ +*                                                                           * +*  BitlBee - An IRC to IM gateway                                           * +*  Jabber module - SI packets                                               * +*                                                                           * +*  Copyright 2007 Uli Meis <a.sporto+bee@gmail.com>                         * +*                                                                           * +*  This program is free software; you can redistribute it and/or modify     * +*  it under the terms of the GNU General Public License as published by     * +*  the Free Software Foundation; either version 2 of the License, or        * +*  (at your option) any later version.                                      * +*                                                                           * +*  This program is distributed in the hope that it will be useful,          * +*  but WITHOUT ANY WARRANTY; without even the implied warranty of           * +*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            * +*  GNU General Public License for more details.                             * +*                                                                           * +*  You should have received a copy of the GNU General Public License along  * +*  with this program; if not, write to the Free Software Foundation, Inc.,  * +*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              * +*                                                                           * +\***************************************************************************/ + +#include "jabber.h" +#include "sha1.h" + +void jabber_si_answer_request( file_transfer_t *ft ); +int jabber_si_send_request(struct im_connection *ic, char *who, struct jabber_transfer *tf ); + +/* file_transfer free() callback */ +void jabber_si_free_transfer( file_transfer_t *ft) +{ +	struct jabber_transfer *tf = ft->data; +	struct jabber_data *jd = tf->ic->proto_data; + +	if ( tf->watch_in ) +		b_event_remove( tf->watch_in ); + +	jd->filetransfers = g_slist_remove( jd->filetransfers, tf ); + +	if( tf->fd != -1 ) +	{ +		close( tf->fd ); +		tf->fd = -1; +	} + +	if( tf->disco_timeout ) +		b_event_remove( tf->disco_timeout ); +	 +	g_free( tf->ini_jid ); +	g_free( tf->tgt_jid ); +	g_free( tf->iq_id ); +	g_free( tf->sid ); +} + +/* file_transfer canceled() callback */ +void jabber_si_canceled( file_transfer_t *ft, char *reason ) +{ +	struct jabber_transfer *tf = ft->data; +	struct xt_node *reply, *iqnode; + +	if( tf->accepted ) +		return; +	 +	iqnode = jabber_make_packet( "iq", "error", tf->ini_jid, NULL ); +	xt_add_attr( iqnode, "id", tf->iq_id ); +	reply = jabber_make_error_packet( iqnode, "forbidden", "cancel", "403" ); +	xt_free_node( iqnode ); +	 +	if( !jabber_write_packet( tf->ic, reply ) ) +		imcb_log( tf->ic, "WARNING: Error generating reply to file transfer request" ); +	xt_free_node( reply ); + +} + +int jabber_si_check_features( struct jabber_transfer *tf, GSList *features ) { +	int foundft = FALSE, foundbt = FALSE, foundsi = FALSE; + +	while ( features ) +	{ +		if( !strcmp( features->data, XMLNS_FILETRANSFER ) ) +			foundft = TRUE; +		if( !strcmp( features->data, XMLNS_BYTESTREAMS ) ) +			foundbt = TRUE; +		if( !strcmp( features->data, XMLNS_SI ) ) +			foundsi = TRUE; + +		features = g_slist_next(features); +	} + +	if( !foundft ) +		imcb_file_canceled( tf->ft, "Buddy's client doesn't feature file transfers" ); +	else if( !foundbt ) +		imcb_file_canceled( tf->ft, "Buddy's client doesn't feature byte streams (required)" ); +	else if( !foundsi ) +		imcb_file_canceled( tf->ft, "Buddy's client doesn't feature stream initiation (required)" ); +		 +	return foundft && foundbt && foundsi; +} + +void jabber_si_transfer_start( struct jabber_transfer *tf ) { + +	if( !jabber_si_check_features( tf, tf->bud->features ) ) +		return; +		 +	/* send the request to our buddy */ +	jabber_si_send_request( tf->ic, tf->bud->full_jid, tf ); + +	/* and start the receive logic */ +	imcb_file_recv_start( tf->ft ); + +} + +gboolean jabber_si_waitfor_disco( gpointer data, gint fd, b_input_condition cond ) +{ +	struct jabber_transfer *tf = data; +	struct jabber_data *jd = tf->ic->proto_data; + +	tf->disco_timeout_fired++; + +	if( tf->bud->features && jd->have_streamhosts==1 ) { +		tf->disco_timeout = 0; +		jabber_si_transfer_start( tf ); +		return FALSE; +	} + +	/* 8 seconds should be enough for server and buddy to respond */ +	if ( tf->disco_timeout_fired < 16 ) +		return TRUE; +	 +	if( !tf->bud->features && jd->have_streamhosts!=1 ) +		imcb_log( tf->ic, "Couldn't get buddy's features nor discover all services of the server" ); +	else if( !tf->bud->features ) +		imcb_log( tf->ic, "Couldn't get buddy's features" ); +	else +		imcb_log( tf->ic, "Couldn't discover some of the server's services" ); +	 +	tf->disco_timeout = 0; +	jabber_si_transfer_start( tf ); +	return FALSE; +} + +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; +	struct jabber_buddy *bud; +	char *server = jd->server, *s; + +	if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) ) +		bud = jabber_buddy_by_ext_jid( ic, who, 0 ); +	else +		bud = jabber_buddy_by_jid( ic, who, 0 ); + +	if( bud == NULL ) +	{ +		imcb_file_canceled( tf->ft, "Couldn't find buddy (BUG?)" ); +		return; +	} +		 +	imcb_log( ic, "Trying to send %s(%zd bytes) to %s", ft->file_name, ft->file_size, who ); + +	tf = g_new0( struct jabber_transfer, 1 ); + +	tf->ic = ic; +	tf->ft = ft; +	tf->fd = -1; +	tf->ft->data = tf; +	tf->ft->free = jabber_si_free_transfer; +	tf->bud = bud; +	ft->write = jabber_bs_send_write; + +	jd->filetransfers = g_slist_prepend( jd->filetransfers, tf ); + +	/* query buddy's features and server's streaming proxies if neccessary */ + +	if( !tf->bud->features ) +		jabber_iq_query_features( ic, bud->full_jid ); + +	/* If <auto> is not set don't check for proxies */ +	if( ( jd->have_streamhosts!=1 ) && ( jd->streamhosts==NULL ) && +	    ( strstr( set_getstr( &ic->acc->set, "proxy" ), "<auto>" ) != NULL ) ) { +		jd->have_streamhosts = 0; +		jabber_iq_query_server( ic, server, XMLNS_DISCO_ITEMS ); +	} else if ( jd->streamhosts!=NULL ) +		jd->have_streamhosts = 1; + +	/* if we had to do a query, wait for the result.  +	 * Otherwise fire away. */ +	if( !tf->bud->features || jd->have_streamhosts!=1 ) +		tf->disco_timeout = b_timeout_add( 500, jabber_si_waitfor_disco, tf ); +	else +		jabber_si_transfer_start( tf ); +} + +/* + * First function that gets called when a file transfer request comes in. + * A lot to parse. + * + * We choose a stream type from the options given by the initiator. + * Then we wait for imcb to call the accept or cancel callbacks. + */ +int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, struct xt_node *sinode) +{ +	struct xt_node *c, *d, *reply; +	char *sid, *ini_jid, *tgt_jid, *iq_id, *s, *ext_jid; +	struct jabber_buddy *bud; +	int requestok = FALSE; +	char *name; +	size_t size; +	struct jabber_transfer *tf; +	struct jabber_data *jd = ic->proto_data; +	file_transfer_t *ft; +	 +	/* All this means we expect something like this: ( I think ) +	 * <iq from=... to=... id=...> +	 * 	<si id=id xmlns=si profile=ft> +	 * 		<file xmlns=ft/> +	 * 		<feature xmlns=feature> +	 * 			<x xmlns=xdata type=submit> +	 * 				<field var=stream-method> +	 * +	 */ +	if( !( ini_jid 		= xt_find_attr(   node, "from" ) 			) || +	    !( tgt_jid 		= xt_find_attr(   node, "to" ) 				) || +	    !( iq_id 		= xt_find_attr(   node, "id" ) 				) || +	    !( sid 		= xt_find_attr( sinode, "id" ) 				) || +	    !( strcmp( xt_find_attr( sinode, "profile" ), XMLNS_FILETRANSFER ) == 0	) || +	    !( d 		= xt_find_node( sinode->children, "file" ) 		) || +	    !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_FILETRANSFER ) == 0 		) || +	    !( name 		= xt_find_attr( d, "name" ) 				) || +	    !( size 		= (size_t) atoll( xt_find_attr( d, "size" ) ) 		) || +	    !( d 		= xt_find_node( sinode->children, "feature" ) 		) || +	    !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_FEATURE ) == 0 		) || +	    !( d 		= xt_find_node( d->children, "x" ) 			) || +	    !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_XDATA ) == 0 			) || +	    !( strcmp( xt_find_attr( d, "type" ), "form" ) == 0 			) || +	    !( d 		= xt_find_node( d->children, "field" ) 			) || +	    !( strcmp( xt_find_attr( d, "var" ), "stream-method" ) == 0 		) ) +	{ +		imcb_log( ic, "WARNING: Received incomplete Stream Initiation request" ); +	} else +	{ +		/* Check if we support one of the options */ + +		c = d->children; +		while( ( c = xt_find_node( c, "option" ) ) ) +			if( 	( d = xt_find_node( c->children, "value" ) ) && +				( strcmp( d->text, XMLNS_BYTESTREAMS ) == 0 ) ) +			{ +				requestok = TRUE; +				break; +			} + +		if ( !requestok ) +			imcb_log( ic, "WARNING: Unsupported file transfer request from %s", ini_jid); +	} +	 +	if ( requestok ) +	{ +		/* Figure out who the transfer should come frome... */ + +		if( ( s = strchr( ini_jid, '/' ) ) ) +		{ +			if( ( bud = jabber_buddy_by_jid( ic, ini_jid, GET_BUDDY_EXACT ) ) ) +			{ +				bud->last_act = time( NULL ); +				ext_jid = bud->ext_jid ? : bud->bare_jid; +			} +			else +				*s = 0; /* We need to generate a bare JID now. */ +		} + +		if( !( ft = imcb_file_send_start( ic, ext_jid, name, size ) ) ) +		{  +			imcb_log( ic, "WARNING: Error handling transfer request from %s", ini_jid); +			requestok = FALSE; +		} + +		*s = '/'; +	} + +	if ( !requestok ) +	{  +		reply = jabber_make_error_packet( node, "item-not-found", "cancel", NULL ); +		if (!jabber_write_packet( ic, reply )) +			imcb_log( ic, "WARNING: Error generating reply to file transfer request" ); +		xt_free_node( reply ); +		return XT_HANDLED; +	} + +	/* Request is fine. */ + +	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->fd = -1; +	tf->ft->data = tf; +	tf->ft->accept = jabber_si_answer_request; +	tf->ft->free = jabber_si_free_transfer; +	tf->ft->canceled = jabber_si_canceled; + +	jd->filetransfers = g_slist_prepend( jd->filetransfers, tf ); + +	return XT_HANDLED; +} + +/* + * imc called the accept callback which probably means that the user accepted this file transfer. + * We send our response to the initiator. + * In the next step, the initiator will send us a request for the given stream type. + * (currently that can only be a SOCKS5 bytestream) + */ +void jabber_si_answer_request( file_transfer_t *ft ) { +	struct jabber_transfer *tf = ft->data; +	struct xt_node *node, *sinode, *reply; + +	/* generate response, start with the SI tag */ +	sinode = xt_new_node( "si", NULL, NULL ); +	xt_add_attr( sinode, "xmlns", XMLNS_SI ); +	xt_add_attr( sinode, "profile", XMLNS_FILETRANSFER ); +	xt_add_attr( sinode, "id", tf->sid ); + +	/* now the file tag */ +	node = xt_new_node( "file", NULL, NULL ); +	xt_add_attr( node, "xmlns", XMLNS_FILETRANSFER ); + +	xt_add_child( sinode, node ); + +	/* and finally the feature tag */ +	node = xt_new_node( "field", NULL, NULL ); +	xt_add_attr( node, "var", "stream-method" ); +	xt_add_attr( node, "type", "list-single" ); + +	/* Currently all we can do. One could also implement in-band (IBB) */ +	xt_add_child( node, xt_new_node( "value", XMLNS_BYTESTREAMS, NULL ) ); + +	node = xt_new_node( "x", NULL, node ); +	xt_add_attr( node, "xmlns", XMLNS_XDATA ); +	xt_add_attr( node, "type", "submit" ); + +	node = xt_new_node( "feature", NULL, node ); +	xt_add_attr( node, "xmlns", XMLNS_FEATURE ); + +	xt_add_child( sinode, node ); + +	reply = jabber_make_packet( "iq", "result", tf->ini_jid, sinode ); +	xt_add_attr( reply, "id", tf->iq_id ); +	 +	if( !jabber_write_packet( tf->ic, reply ) ) +		imcb_log( tf->ic, "WARNING: Error generating reply to file transfer request" ); +	else +		tf->accepted = TRUE; +	xt_free_node( reply ); +} + +static xt_status jabber_si_handle_response(struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ +	struct xt_node *c, *d; +	char *ini_jid, *tgt_jid, *iq_id; +	GSList *tflist; +	struct jabber_transfer *tf=NULL; +	struct jabber_data *jd = ic->proto_data; + +	if( !( tgt_jid = xt_find_attr( node, "from" ) ) || +	    !( ini_jid = xt_find_attr( node, "to" ) ) ) +	{ +		imcb_log( ic, "Invalid SI response from=%s to=%s", tgt_jid, ini_jid ); +		return XT_HANDLED; +	} +	 +	/* All this means we expect something like this: ( I think ) +	 * <iq from=... to=... id=...> +	 * 	<si xmlns=si> +	 * 	[	<file xmlns=ft/>    ] <-- not neccessary +	 * 		<feature xmlns=feature> +	 * 			<x xmlns=xdata type=submit> +	 * 				<field var=stream-method> +	 * 					<value> +	 */ +	if( !( tgt_jid = xt_find_attr( node, "from" ) ) || +	    !( ini_jid = xt_find_attr( node, "to" ) ) || +	    !( iq_id   = xt_find_attr( node, "id" ) ) || +	    !( c = xt_find_node( node->children, "si" ) ) || +	    !( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_SI ) == 0 ) || +/*	    !( d = xt_find_node( c->children, "file" ) ) || +	    !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_FILETRANSFER ) == 0 ) || */ +	    !( d = xt_find_node( c->children, "feature" ) ) || +	    !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_FEATURE ) == 0 ) || +	    !( d = xt_find_node( d->children, "x" ) ) || +	    !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_XDATA ) == 0 ) || +	    !( strcmp( xt_find_attr( d, "type" ), "submit" ) == 0 ) || +	    !( d = xt_find_node( d->children, "field" ) ) || +	    !( strcmp( xt_find_attr( d, "var" ), "stream-method" ) == 0 ) || +	    !( d = xt_find_node( d->children, "value" ) ) ) +	{ +		imcb_log( ic, "WARNING: Received incomplete Stream Initiation response" ); +		return XT_HANDLED; +	} + +	if( !( strcmp( d->text, XMLNS_BYTESTREAMS ) == 0 ) ) {  +		/* since we should only have advertised what we can do and the peer should +		 * only have chosen what we offered, this should never happen */ +		imcb_log( ic, "WARNING: Received invalid Stream Initiation response, method %s", d->text ); +			 +		return XT_HANDLED; +	} +	 +	/* Let's see if we can find out what this bytestream should be for... */ + +	for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) +	{ +		struct jabber_transfer *tft = tflist->data; +		if( ( strcmp( tft->iq_id, iq_id ) == 0 ) ) +		{ +		    	tf = tft; +			break; +		} +	} + +	if (!tf)  +	{ +		imcb_log( ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid ); +		return XT_HANDLED; +	} + +	tf->ini_jid = g_strdup( ini_jid ); +	tf->tgt_jid = g_strdup( tgt_jid ); + +	imcb_log( ic, "File %s: %s accepted the transfer!", tf->ft->file_name, tgt_jid ); + +	jabber_bs_send_start( tf ); + +	return XT_HANDLED; +} + +int jabber_si_send_request(struct im_connection *ic, char *who, struct jabber_transfer *tf ) +{ +	struct xt_node *node, *sinode; +	struct jabber_buddy *bud; + +	/* who knows how many bits the future holds :) */ +	char filesizestr[ 1 + ( int ) ( 0.301029995663981198f * sizeof( size_t ) * 8 ) ]; + +	const char *methods[] =  +	{  	 +		XMLNS_BYTESTREAMS, +		//XMLNS_IBB, +		NULL  +	}; +	const char **m; +	char *s; + +	/* Maybe we should hash this? */ +	tf->sid = g_strdup_printf( "BitlBeeJabberSID%d", tf->ft->local_id ); +	 +	if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) ) +		bud = jabber_buddy_by_ext_jid( ic, who, 0 ); +	else +		bud = jabber_buddy_by_jid( ic, who, 0 ); + +	/* start with the SI tag */ +	sinode = xt_new_node( "si", NULL, NULL ); +	xt_add_attr( sinode, "xmlns", XMLNS_SI ); +	xt_add_attr( sinode, "profile", XMLNS_FILETRANSFER ); +	xt_add_attr( sinode, "id", tf->sid ); + +/*	if( mimetype )  +		xt_add_attr( node, "mime-type", mimetype ); */ + +	/* now the file tag */ +/*	if( desc ) + 		node = xt_new_node( "desc", descr, NULL ); */ +	node = xt_new_node( "range", NULL, NULL ); + +	sprintf( filesizestr, "%zd", tf->ft->file_size ); +	node = xt_new_node( "file", NULL, node ); +	xt_add_attr( node, "xmlns", XMLNS_FILETRANSFER ); +	xt_add_attr( node, "name", tf->ft->file_name ); +	xt_add_attr( node, "size", filesizestr ); +/*	if (hash) +		xt_add_attr( node, "hash", hash ); +	if (date) +		xt_add_attr( node, "date", date ); */ + +	xt_add_child( sinode, node ); + +	/* and finally the feature tag */ +	node = xt_new_node( "field", NULL, NULL ); +	xt_add_attr( node, "var", "stream-method" ); +	xt_add_attr( node, "type", "list-single" ); + +	for ( m = methods ; *m ; m ++ ) +		xt_add_child( node, xt_new_node( "option", NULL, xt_new_node( "value", (char *)*m, NULL ) ) ); + +	node = xt_new_node( "x", NULL, node ); +	xt_add_attr( node, "xmlns", XMLNS_XDATA ); +	xt_add_attr( node, "type", "form" ); + +	node = xt_new_node( "feature", NULL, node ); +	xt_add_attr( node, "xmlns", XMLNS_FEATURE ); + +	xt_add_child( sinode, node ); + +	/* and we are there... */ +	node = jabber_make_packet( "iq", "set", bud ? bud->full_jid : who, sinode ); +	jabber_cache_add( ic, node, jabber_si_handle_response ); +	tf->iq_id = g_strdup( xt_find_attr( node, "id" ) ); +	 +	return jabber_write_packet( ic, node ); +} diff --git a/protocols/nogaim.h b/protocols/nogaim.h index 9fe843b5..5b094b3f 100644 --- a/protocols/nogaim.h +++ b/protocols/nogaim.h @@ -43,6 +43,7 @@  #include "proxy.h"  #include "query.h"  #include "md5.h" +#include "ft.h"  #define BUDDY_ALIAS_MAXLEN 388   /* because MSN names can be 387 characters */ @@ -223,6 +224,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. */ diff --git a/root_commands.c b/root_commands.c index f55c4b5e..93bd86d1 100644 --- a/root_commands.c +++ b/root_commands.c @@ -1043,6 +1043,71 @@ static void cmd_join_chat( irc_t *irc, char **cmd )  	}  } +static void cmd_transfers( irc_t *irc, char **cmd ) +{ +	GSList *files = irc->file_transfers; +	enum { LIST, REJECT, CANCEL }; +	int subcmd = LIST; +	int fid; + +	if( !files ) +	{ +		irc_usermsg( irc, "No pending transfers" ); +		return; +	} + +	if( cmd[1] &&  +	    ( strcmp( cmd[1], "reject" ) == 0 ) ) +	{ +		subcmd = REJECT; +	} +	else if( cmd[1] &&  +		 ( strcmp( cmd[1], "cancel" ) == 0 ) &&  +		 cmd[2] && +		 ( fid = atoi( cmd[2] ) ) ) +	{ +		subcmd = CANCEL; +	} + +	for( ; files; files = g_slist_next( files ) ) +	{ +		file_transfer_t *file = files->data; +		 +		switch( subcmd ) { +		case LIST: +			if ( file->status == FT_STATUS_LISTENING ) +				irc_usermsg( irc,  +					"Pending file(id %d): %s (Listening...)", file->local_id, file->file_name); +			else  +			{ +				int kb_per_s = 0; +				time_t diff = time( NULL ) - file->started ? : 1; +				if ( ( file->started > 0 ) && ( file->bytes_transferred > 0 ) ) +					kb_per_s = file->bytes_transferred / 1024 / diff; +					 +				irc_usermsg( irc,  +					"Pending file(id %d): %s (%10zd/%zd kb, %d kb/s)", file->local_id, file->file_name,  +					file->bytes_transferred/1024, file->file_size/1024, kb_per_s); +			} +			break; +		case REJECT: +			if( file->status == FT_STATUS_LISTENING ) +			{ +				irc_usermsg( irc, "Rejecting file transfer for %s", file->file_name ); +				imcb_file_canceled( file, "Denied by user" ); +			} +			break; +		case CANCEL: +			if( file->local_id == fid ) +			{ +				irc_usermsg( irc, "Canceling file transfer for %s", file->file_name ); +				imcb_file_canceled( file, "Canceled by user" ); +			} +			break; +		} +	} +} +  const command_t commands[] = {  	{ "help",           0, cmd_help,           0 },   	{ "identify",       1, cmd_identify,       0 }, @@ -1063,5 +1128,6 @@ const command_t commands[] = {  	{ "nick",           1, cmd_nick,           0 },  	{ "qlist",          0, cmd_qlist,          0 },  	{ "join_chat",      2, cmd_join_chat,      0 }, +	{ "transfers",      0, cmd_transfers,      0 },  	{ NULL }  }; | 
