From 2c2df7dd91930345a9b22a8bb61327d1dcc7e3d5 Mon Sep 17 00:00:00 2001 From: ulim Date: Wed, 28 Nov 2007 22:07:30 +0100 Subject: Initial import of jabber file receive and DCC send support. This introduces only a few changes to bitlbees code, mainly the addition of the "transfers" command. This is known to work with Kopete, Psi, and Pidgin (formerly known as gaim). At least with Pidgin also over a proxy. DCC has only been tested with irssi. IPV6 is untested but should work. Currently, only receiving via SOCKS5BYTESREAMS is implemented. I'm not sure if the alternative(in-band bytestreams IBB) is worth implementing since I didn't see a client yet that can do it. Additionally, it is probably very slow and needs support by the server as well. --- Makefile | 4 +- conf.c | 1 + conf.h | 1 + dcc.c | 534 +++++++++++++++++++++++++++++++++++++ dcc.h | 125 +++++++++ irc.h | 1 + protocols/ft.h | 153 +++++++++++ protocols/jabber/Makefile | 2 +- protocols/jabber/iq.c | 28 +- protocols/jabber/jabber.h | 58 +++- protocols/jabber/jabber_util.c | 6 +- protocols/jabber/si.c | 246 +++++++++++++++++ protocols/jabber/stream.c | 593 +++++++++++++++++++++++++++++++++++++++++ protocols/nogaim.h | 1 + root_commands.c | 66 +++++ 15 files changed, 1795 insertions(+), 24 deletions(-) create mode 100644 dcc.c create mode 100644 dcc.h create mode 100644 protocols/ft.h create mode 100644 protocols/jabber/si.c create mode 100644 protocols/jabber/stream.c diff --git a/Makefile b/Makefile index a9dd2e17..1f250420 100644 --- a/Makefile +++ b/Makefile @@ -9,8 +9,8 @@ -include Makefile.settings # Program variables -objects = account.o bitlbee.o conf.o crypting.o help.o ipc.o irc.o irc_commands.o log.o nick.o query.o root_commands.o set.o storage.o $(STORAGE_OBJS) unix.o 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 conf.o crypting.o help.o ipc.o irc.o irc_commands.o log.o nick.o query.o root_commands.o set.o storage.o $(STORAGE_OBJS) unix.o 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 dcc.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 # Expansion of variables diff --git a/conf.c b/conf.c index 82487ddf..1768866b 100644 --- a/conf.c +++ b/conf.c @@ -62,6 +62,7 @@ conf_t *conf_load( int argc, char *argv[] ) conf->motdfile = g_strdup( ETCDIR "/motd.txt" ); conf->ping_interval = 180; conf->ping_timeout = 300; + conf->max_filetransfer_size = G_MAXUINT; proxytype = 0; i = conf_loadini( conf, CONF_FILE ); diff --git a/conf.h b/conf.h index 5138ce11..c88c8303 100644 --- a/conf.h +++ b/conf.h @@ -48,6 +48,7 @@ typedef struct conf char **migrate_storage; int ping_interval; int ping_timeout; + size_t max_filetransfer_size; } conf_t; G_GNUC_MALLOC conf_t *conf_load( int argc, char *argv[] ); diff --git a/dcc.c b/dcc.c new file mode 100644 index 00000000..73fe0180 --- /dev/null +++ b/dcc.c @@ -0,0 +1,534 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway * +* * +* Copyright 2007 Uli Meis * +\********************************************************************/ + +/* + 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 +#include + +/* + * 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; + +/* + * If using DCC SEND AHEAD this value will be set before the first transfer starts. + * Not that in this case it degenerates to the maximum message size to send() and + * has nothing to do with packets. + */ +#ifdef DCC_SEND_AHEAD +int max_packet_size = DCC_PACKET_SIZE; +#else +int max_packet_size = 0; +#endif + +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 ); + +/* 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_write( file_transfer_t *file, gpointer data, size_t data_size ) +{ + return dccs_send_write( file, data, data_size ); +} + +/* 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; + + /* alloc stuff */ + file = g_new0( file_transfer_t, 1 ); + file->priv = df = g_new0( dcc_file_transfer_t, 1); + file->file_size = file_size; + file->file_name = g_strdup( file_name ); + file->local_id = local_transfer_id++; + df->ic = ic; + df->ft = file; + + /* 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 ); + + 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 ); + else + imcb_log( df->ic, "DCC transfer aborted: %s", msg ); + + g_free( msg ); + + dcc_close( df->ft ); + + return FALSE; +} + +/* 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; + + /* + * this is so ridiculous. We're supposed to convert the address to + * host byte order!!! Let's exclude anyone running big endian just + * for the fun of it... + */ + sprintf( ipaddr, "%d", + htonl( 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 ); + + /* message is sortof redundant cause the users client probably informs him about that. remove? */ + imcb_log( df->ic, "Transferring file %s: Chose local address %s for DCC connection", df->ft->file_name, ipaddr ); + + 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; + char hostname[ HOST_NAME_MAX + 1 ]; + struct addrinfo hints, *rp; + socklen_t ssize = sizeof( struct sockaddr_storage ); + + /* won't be long till someone asks for this to be configurable :) */ + + ASSERTSOCKOP( gethostname( hostname, sizeof( hostname ) ), "gethostname()" ); + + memset( &hints, 0, sizeof( struct addrinfo ) ); + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_NUMERICSERV; + + if ( getaddrinfo( hostname, "0", &hints, &rp ) != 0 ) + return dcc_abort( df, "getaddrinfo()" ); + + 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; +} + +/* + * 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; + struct pollfd pfd = { .fd = fd, .events = POLLHUP|POLLERR|POLLIN|POLLOUT }; + short revents; + + if ( poll( &pfd, 1, 0 ) == -1 ) + { + imcb_log( df->ic, "poll() failed, weird!" ); + revents = 0; + }; + + revents = pfd.revents; + + if( 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( revents & POLLHUP ) + return dcc_abort( df, "Remote end closed connection" ); + + 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_TRANSFERING; + sock_make_nonblocking( fd ); + +#ifdef DCC_SEND_AHEAD + /* + * use twice the maximum segment size as a maximum for calls to send(). + */ + if( max_packet_size == 0 ) + { + unsigned int mpslen = sizeof( max_packet_size ); + if( getsockopt( fd, IPPROTO_TCP, TCP_MAXSEG, &max_packet_size, &mpslen ) ) + return dcc_abort( df, "getsockopt() failed" ); + max_packet_size *= 2; + } +#endif + /* 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 ) { + dcc_finish( file ); + return FALSE; + } + +#ifndef DCC_SEND_AHEAD + /* reschedule writer if neccessary */ + if( file->bytes_transferred >= df->bytes_sent && + df->watch_out == 0 && + df->queued_bytes > 0 ) { + df->watch_out = b_input_add( fd, GAIM_INPUT_WRITE, dcc_send_proto, df ); + } +#endif + return TRUE; + } + + if( revents & POLLOUT ) + { + struct dcc_buffer *dccb; + int ret; + char *msg; + + if( df->queued_bytes == 0 ) + { + /* shouldn't happen */ + imcb_log( df->ic, "WARNING: DCC SEND: write called with empty queue" ); + + df->watch_out = 0; + return FALSE; + } + + /* start where we left off */ + if( !( df->queued_buffers ) || + !( dccb = df->queued_buffers->data ) ) + return dcc_abort( df, "BUG in DCC SEND: queued data but no buffers" ); + + msg = dccb->b + df->buffer_pos; + + int msgsize = MIN( +#ifndef DCC_SEND_AHEAD + file->bytes_transferred + MAX_PACKET_SIZE - df->bytes_sent, +#else + max_packet_size, +#endif + dccb->len - df->buffer_pos ); + + if ( msgsize == 0 ) + { + df->watch_out = 0; + return FALSE; + } + + ASSERTSOCKOP( ret = send( fd, msg, msgsize, 0 ), "Sending data" ); + + if( ret == 0 ) + return dcc_abort( df, "Remote end closed connection" ); + + df->bytes_sent += ret; + df->queued_bytes -= ret; + df->buffer_pos += ret; + + if( df->buffer_pos == dccb->len ) + { + df->buffer_pos = 0; + df->queued_buffers = g_slist_remove( df->queued_buffers, dccb ); + g_free( dccb->b ); + g_free( dccb ); + } + + if( ( df->queued_bytes < DCC_QUEUE_THRESHOLD_LOW ) && file->out_of_data ) + file->out_of_data( file ); + + if( df->queued_bytes > 0 ) + { + /* Who knows how long the event loop cycle will take, + * let's just try to send some more now. */ +#ifndef DCC_SEND_AHEAD + if( df->bytes_sent < ( file->bytes_transferred + max_packet_size ) ) +#endif + return dccs_send_proto( df, fd, cond ); + } + + df->watch_out = 0; + return FALSE; + } + + /* Send buffer full, come back later */ + + return TRUE; +} + +/* + * Incoming data. Note that the buffer MUST NOT be freed by the caller! + * We don't copy the buffer but put it in our queue. + * + * */ +gboolean dccs_send_write( file_transfer_t *file, gpointer data, unsigned int data_size ) +{ + dcc_file_transfer_t *df = file->priv; + struct dcc_buffer *dccb = g_new0( struct dcc_buffer, 1 ); + + receivedchunks++; receiveddata += data_size; + + dccb->b = data; + dccb->len = data_size; + + df->queued_buffers = g_slist_append( df->queued_buffers, dccb ); + + df->queued_bytes += data_size; + + if( ( file->status & FT_STATUS_TRANSFERING ) && +#ifndef DCC_SEND_AHEAD + ( file->bytes_transferred >= df->bytes_sent ) && +#endif + ( df->watch_out == 0 ) && + ( df->queued_bytes > 0 ) ) + { + df->watch_out = b_input_add( df->fd, GAIM_INPUT_WRITE, dccs_send_proto, df ); + } + + return df->queued_bytes > DCC_QUEUE_THRESHOLD_HIGH; +} + +/* + * 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->queued_buffers ) + { + struct dcc_buffer *dccb; + GSList *gslist = df->queued_buffers; + + for( ; gslist ; gslist = g_slist_next( gslist ) ) + { + dccb = gslist->data; + g_free( dccb->b ); + g_free( dccb ); + } + g_slist_free( df->queued_buffers ); + } + + 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 ) +{ + file->status |= FT_STATUS_FINISHED; + + if( file->finished ) + file->finished( file ); + + dcc_close( file ); +} diff --git a/dcc.h b/dcc.h new file mode 100644 index 00000000..205107c5 --- /dev/null +++ b/dcc.h @@ -0,0 +1,125 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway * +* * +* Copyright 2006 Marijn Kruisselbrink and others * +* Copyright 2007 Uli Meis * +\********************************************************************/ + +/* + 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 + +/* don't wait for acknowledgments */ +#define DCC_SEND_AHEAD + +/* This multiplier specifies how many bytes we + * can go ahead within one event loop cycle. Notice that all in all, + * we can easily be more ahead if the event loop shoots often enough. + * (or the receiver processes slow enough) + * + * Setting this value too high will cause send buffer overflows. + */ +#define DCC_SEND_AHEAD_MUL 10 + +/* + * queue thresholds for the out of data and overflow conditions + */ +#define DCC_QUEUE_THRESHOLD_LOW 2048 +#define DCC_QUEUE_THRESHOLD_HIGH 65536 + +/* only used in non-ahead mode */ +#define DCC_PACKET_SIZE 1024 + +/* stores buffers handed over by IM protocols */ +struct dcc_buffer { + char *b; + int len; +}; + +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 total number of queued bytes. The following equality should always hold: + * + * queued_bytes = sum(queued_buffers.len) - buffer_pos + */ + unsigned int queued_bytes; + + /* + * A list of dcc_buffer structures. + * These are provided by the protocols directly so that no copying is neccessary. + */ + GSList *queued_buffers; + + /* + * current position within the first buffer. + * Is non-null if the whole buffer couldn't be sent at once. + */ + int buffer_pos; + + /* + * The total amount of bytes that have been sent to the irc client. + */ + size_t bytes_sent; + + /* imcb's handle */ + file_transfer_t *ft; + +} 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, gpointer data, unsigned int data_size ); + +#endif diff --git a/irc.h b/irc.h index 8be3579e..d581e813 100644 --- a/irc.h +++ b/irc.h @@ -83,6 +83,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..0ff44873 --- /dev/null +++ b/protocols/ft.h @@ -0,0 +1,153 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway * +* * +* Copyright 2006 Marijn Kruisselbrink and others * +\********************************************************************/ + +/* Generic file transfer header */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _FT_H +#define _FT_H + +typedef enum { + FT_STATUS_LISTENING = 1, + FT_STATUS_TRANSFERING = 2, + FT_STATUS_FINISHED = 4, + FT_STATUS_CANCELED = 8 +} file_status_t; + +/* + * This structure holds all irc specific information regarding an incoming (from the point of view of + * the irc client) file transfer. New instances of this struct should only be created by calling the + * imcb_file_send_start() method, which will initialize most of the fields. The data field and the various + * methods are zero-initialized. Instances will automatically be deleted once the transfer is completed, + * canceled, or the connection to the irc client has been lost (note that also if only the irc connection + * and not the dcc connection is lost, the file transfer will still be canceled and freed). + * + * The following (poor ascii-art) diagram illustrates what methods are called for which status-changes: + * + * /-----------\ /----------\ + * -------> | LISTENING | -----------------> | CANCELED | + * \-----------/ [canceled,]free \----------/ + * | + * | accept + * V + * /------ /-------------\ /------------------------\ + * out_of_data | | TRANSFERING | -----------------> | TRANSFERING | CANCELED | + * \-----> \-------------/ [canceled,]free \------------------------/ + * | + * | finished,free + * V + * /------------------------\ + * | TRANSFERING | FINISHED | + * \------------------------/ + */ +typedef struct file_transfer { + /* + * The current status of this file transfer. + */ + file_status_t status; + + /* + * file size + */ + size_t file_size; + + /* + * Number of bytes that have been successfully transferred. + */ + size_t bytes_transferred; + + /* + * Time started. Used to calculate kb/s. + */ + time_t started; + + /* + * file name + */ + char *file_name; + + /* + * A unique local ID for this file transfer. + */ + unsigned int local_id; + + /* + * IM-protocol specific data associated with this file transfer. + */ + gpointer data; + + /* + * Private data. + */ + gpointer priv; + + /* + * If set, called after succesful connection setup. + */ + void (*accept) ( struct file_transfer *file ); + + /* + * If set, called when the transfer is canceled or finished. + * Subsequently, this structure will be freed. + * + */ + void (*free) ( struct file_transfer *file ); + + /* + * If set, called when the transfer is finished and successful. + */ + void (*finished) ( struct file_transfer *file ); + + /* + * If set, called when the transfer is canceled. + * ( canceled either by the transfer implementation or by + * a call to imcb_file_canceled ) + */ + void (*canceled) ( struct file_transfer *file, char *reason ); + + /* + * If set, called when the transfer queue is running empty and + * more data can be added. + */ + void (*out_of_data) ( struct file_transfer *file ); + +} file_transfer_t; + +/* + * This starts a file transfer from bitlbee to the user (currently via DCC). + */ +file_transfer_t *imcb_file_send_start( struct im_connection *ic, char *user_nick, char *file_name, size_t file_size ); + +/* + * This should be called by a protocol when the transfer is canceled. Note that + * the canceled() and free() callbacks given in file will be called by this function. + */ +void imcb_file_canceled( file_transfer_t *file, char *reason ); + +/* + * The given buffer is queued for transfer and MUST NOT be freed by the caller. + * When the method returns false the caller should not invoke this method again + * until out_of_data has been called. + */ +gboolean imcb_file_write( file_transfer_t *file, gpointer data, size_t data_size ); + +#endif diff --git a/protocols/jabber/Makefile b/protocols/jabber/Makefile index e042f812..47c832ae 100644 --- a/protocols/jabber/Makefile +++ b/protocols/jabber/Makefile @@ -9,7 +9,7 @@ -include ../../Makefile.settings # [SH] Program variables -objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o sasl.o xmltree.o +objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o sasl.o xmltree.o si.o stream.o CFLAGS += -Wall LFLAGS += -r diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c index 595718fb..df0102b8 100644 --- a/protocols/jabber/iq.c +++ b/protocols/jabber/iq.c @@ -103,6 +103,9 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ) XMLNS_TIME, XMLNS_CHATSTATES, XMLNS_MUC, + XMLNS_SI, + XMLNS_BYTESTREAMS, + XMLNS_FILETRANSFER, NULL }; const char **f; @@ -122,24 +125,26 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ) else { xt_free_node( reply ); - reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel" ); + reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel", NULL ); pack = 0; } } else if( strcmp( type, "set" ) == 0 ) { - if( !( c = xt_find_node( node->children, "query" ) ) || + if( ( c = xt_find_node( node->children, "si" ) ) && + ( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_SI ) == 0 ) ) + { + return jabber_si_handle_request( ic, node, c ); + } else if( !( c = xt_find_node( node->children, "query" ) ) || !( s = xt_find_attr( c, "xmlns" ) ) ) { imcb_log( ic, "WARNING: Received incomplete IQ-%s packet", type ); return XT_HANDLED; - } - + } else if( strcmp( s, XMLNS_ROSTER ) == 0 ) + { /* This is a roster push. XMPP servers send this when someone was added to (or removed from) the buddy list. AFAIK they're sent even if we added this buddy in our own session. */ - if( strcmp( s, XMLNS_ROSTER ) == 0 ) - { int bare_len = strlen( ic->acc->user ); if( ( s = xt_find_attr( node, "from" ) ) == NULL || @@ -156,14 +161,17 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ) imcb_log( ic, "WARNING: %s tried to fake a roster push!", s ? s : "(unknown)" ); xt_free_node( reply ); - reply = jabber_make_error_packet( node, "not-allowed", "cancel" ); + reply = jabber_make_error_packet( node, "not-allowed", "cancel", NULL ); pack = 0; } - } - else + } else if( strcmp( s, XMLNS_BYTESTREAMS ) == 0 ) + { + /* Bytestream Request (stage 2 of file transfer) */ + return jabber_bs_request( ic, node, c ); + } else { xt_free_node( reply ); - reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel" ); + reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel", NULL ); pack = 0; } } diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h index fc9d2fc4..0cb2b733 100644 --- a/protocols/jabber/jabber.h +++ b/protocols/jabber/jabber.h @@ -80,6 +80,8 @@ struct jabber_data char *cached_id_prefix; GHashTable *node_cache; GHashTable *buddies; + + GSList *filetransfers; }; struct jabber_away_state @@ -123,6 +125,30 @@ struct jabber_chat struct jabber_buddy *me; }; +struct jabber_transfer +{ + /* bitlbee's handle for this transfer */ + file_transfer_t *ft; + + /* the stream's private handle */ + gpointer streamhandle; + + struct im_connection *ic; + + int watch_in; + int watch_out; + + char *ini_jid; + char *tgt_jid; + char *iq_id; + char *sid; + int accepted; + + size_t bytesread, byteswritten; + int receiver_overflow; + int fd; +}; + #define JABBER_XMLCONSOLE_HANDLE "xmlconsole" #define JABBER_PORT_DEFAULT "5222" @@ -148,15 +174,21 @@ struct jabber_chat #define XMLNS_ROSTER "jabber:iq:roster" /* Some supported extensions/legacy stuff */ -#define XMLNS_AUTH "jabber:iq:auth" /* XEP-0078 */ -#define XMLNS_VERSION "jabber:iq:version" /* XEP-0092 */ -#define XMLNS_TIME "jabber:iq:time" /* XEP-0090 */ -#define XMLNS_VCARD "vcard-temp" /* XEP-0054 */ -#define XMLNS_DELAY "jabber:x:delay" /* XEP-0091 */ -#define XMLNS_CHATSTATES "http://jabber.org/protocol/chatstates" /* 0085 */ -#define XMLNS_DISCOVER "http://jabber.org/protocol/disco#info" /* 0030 */ -#define XMLNS_MUC "http://jabber.org/protocol/muc" /* XEP-0045 */ -#define XMLNS_MUC_USER "http://jabber.org/protocol/muc#user"/* XEP-0045 */ +#define XMLNS_AUTH "jabber:iq:auth" /* XEP-0078 */ +#define XMLNS_VERSION "jabber:iq:version" /* XEP-0092 */ +#define XMLNS_TIME "jabber:iq:time" /* XEP-0090 */ +#define XMLNS_VCARD "vcard-temp" /* XEP-0054 */ +#define XMLNS_DELAY "jabber:x:delay" /* XEP-0091 */ +#define XMLNS_XDATA "jabber:x:data" /* XEP-0004 */ +#define XMLNS_CHATSTATES "http://jabber.org/protocol/chatstates" /* XEP-0085 */ +#define XMLNS_DISCOVER "http://jabber.org/protocol/disco#info" /* XEP-0030 */ +#define XMLNS_MUC "http://jabber.org/protocol/muc" /* XEP-0045 */ +#define XMLNS_MUC_USER "http://jabber.org/protocol/muc#user" /* XEP-0045 */ +#define XMLNS_FEATURE "http://jabber.org/protocol/feature-neg" /* XEP-0020 */ +#define XMLNS_SI "http://jabber.org/protocol/si" /* XEP-0095 */ +#define XMLNS_FILETRANSFER "http://jabber.org/protocol/si/profile/file-transfer" /* XEP-0096 */ +#define XMLNS_BYTESTREAMS "http://jabber.org/protocol/bytestreams" /* XEP-0065 */ +#define XMLNS_IBB "http://jabber.org/protocol/ibb" /* XEP-0047 */ /* iq.c */ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ); @@ -167,6 +199,12 @@ int jabber_get_vcard( struct im_connection *ic, char *bare_jid ); int jabber_add_to_roster( struct im_connection *ic, char *handle, char *name ); int jabber_remove_from_roster( struct im_connection *ic, char *handle ); +/* si.c */ +int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, struct xt_node *sinode); + +/* stream.c */ +int jabber_bs_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode); + /* message.c */ xt_status jabber_pkt_message( struct xt_node *node, gpointer data ); @@ -179,7 +217,7 @@ int presence_send_request( struct im_connection *ic, char *handle, char *request char *set_eval_priority( set_t *set, char *value ); char *set_eval_tls( set_t *set, char *value ); struct xt_node *jabber_make_packet( char *name, char *type, char *to, struct xt_node *children ); -struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type ); +struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type, char *err_code ); void jabber_cache_add( struct im_connection *ic, struct xt_node *node, jabber_cache_event func ); struct xt_node *jabber_cache_get( struct im_connection *ic, char *id ); void jabber_cache_entry_free( gpointer entry ); diff --git a/protocols/jabber/jabber_util.c b/protocols/jabber/jabber_util.c index 43b91fe3..0c5b813e 100644 --- a/protocols/jabber/jabber_util.c +++ b/protocols/jabber/jabber_util.c @@ -96,7 +96,7 @@ struct xt_node *jabber_make_packet( char *name, char *type, char *to, struct xt_ return node; } -struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type ) +struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type, char *err_code ) { struct xt_node *node, *c; char *to; @@ -109,6 +109,10 @@ struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, c = xt_new_node( "error", NULL, c ); xt_add_attr( c, "type", err_type ); + /* Add the error code, if present */ + if (err_code) + xt_add_attr( c, "code", err_code ); + /* To make the actual error packet, we copy the original packet and add our /type="error" tag. Including the original packet is recommended, so let's just do it. */ diff --git a/protocols/jabber/si.c b/protocols/jabber/si.c new file mode 100644 index 00000000..d16f723a --- /dev/null +++ b/protocols/jabber/si.c @@ -0,0 +1,246 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Jabber module - SI packets * +* * +* Copyright 2007 Uli Meis * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +#include "jabber.h" +#include "sha1.h" + +void jabber_si_answer_request( file_transfer_t *ft ); + +/* imcb callback */ +void jabber_si_free_transfer( file_transfer_t *ft) +{ + struct jabber_transfer *tf = ft->data; + struct jabber_data *jd = tf->ic->proto_data; + + if ( tf->watch_in ) + b_event_remove( tf->watch_in ); + + jd->filetransfers = g_slist_remove( jd->filetransfers, tf ); + + if( tf->fd ) + { + close( tf->fd ); + tf->fd = 0; + } + + g_free( tf->ini_jid ); + g_free( tf->tgt_jid ); + g_free( tf->iq_id ); + g_free( tf->sid ); +} + +/* imcb callback */ +void jabber_si_finished( file_transfer_t *ft ) +{ + struct jabber_transfer *tf = ft->data; + + imcb_log( tf->ic, "File %s transferred successfully!" , ft->file_name ); +} + +/* imcb callback */ +void jabber_si_canceled( file_transfer_t *ft, char *reason ) +{ + struct jabber_transfer *tf = ft->data; + struct xt_node *reply, *iqnode; + + if( tf->accepted ) + return; + + iqnode = jabber_make_packet( "iq", "error", tf->ini_jid, NULL ); + xt_add_attr( iqnode, "id", tf->iq_id ); + reply = jabber_make_error_packet( iqnode, "forbidden", "cancel", "403" ); + xt_free_node( iqnode ); + + if( !jabber_write_packet( tf->ic, reply ) ) + imcb_log( tf->ic, "WARNING: Error generating reply to file transfer request" ); + xt_free_node( reply ); + +} + +/* + * First function that gets called when a file transfer request comes in. + * A lot to parse. + * + * We choose a stream type from the options given by the initiator. + * Then we wait for imcb to call the accept or cancel callbacks. + */ +int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, struct xt_node *sinode) +{ + struct xt_node *c, *d, *reply; + char *sid, *ini_jid, *tgt_jid, *iq_id, *s, *ext_jid; + struct jabber_buddy *bud; + int requestok = FALSE; + char *name; + size_t size; + struct jabber_transfer *tf; + struct jabber_data *jd = ic->proto_data; + file_transfer_t *ft; + + /* All this means we expect something like this: ( I think ) + * + * + * + * + * + * + * + */ + if( !( ini_jid = xt_find_attr( node, "from" ) ) || + !( tgt_jid = xt_find_attr( node, "to" ) ) || + !( iq_id = xt_find_attr( node, "id" ) ) || + !( sid = xt_find_attr( sinode, "id" ) ) || + !( strcmp( xt_find_attr( sinode, "profile" ), XMLNS_FILETRANSFER ) == 0 ) || + !( d = xt_find_node( sinode->children, "file" ) ) || + !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_FILETRANSFER ) == 0 ) || + !( name = xt_find_attr( d, "name" ) ) || + !( size = (size_t) atoll( xt_find_attr( d, "size" ) ) ) || + !( d = xt_find_node( sinode->children, "feature" ) ) || + !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_FEATURE ) == 0 ) || + !( d = xt_find_node( d->children, "x" ) ) || + !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_XDATA ) == 0 ) || + !( strcmp( xt_find_attr( d, "type" ), "form" ) == 0 ) || + !( d = xt_find_node( d->children, "field" ) ) || + !( strcmp( xt_find_attr( d, "var" ), "stream-method" ) == 0 ) ) + { + imcb_log( ic, "WARNING: Received incomplete Stream Initiation request" ); + } else + { + /* Check if we support one of the options */ + + c = d->children; + while( ( c = xt_find_node( c, "option" ) ) ) + if( ( d = xt_find_node( c->children, "value" ) ) && + ( strcmp( d->text, XMLNS_BYTESTREAMS ) == 0 ) ) + { + requestok = TRUE; + break; + } + } + + if ( requestok ) + { + /* Figure out who the transfer should come frome... */ + + if( ( s = strchr( ini_jid, '/' ) ) ) + { + if( ( bud = jabber_buddy_by_jid( ic, ini_jid, GET_BUDDY_EXACT ) ) ) + { + bud->last_act = time( NULL ); + ext_jid = bud->ext_jid ? : bud->bare_jid; + } + else + *s = 0; /* We need to generate a bare JID now. */ + } + + if( !( ft = imcb_file_send_start( ic, ext_jid, name, size ) ) ) + { + imcb_log( ic, "WARNING: Error handling transfer request from %s", ini_jid); + requestok = FALSE; + } + + *s = '/'; + } else + imcb_log( ic, "WARNING: Unsupported file transfer request from %s", ini_jid); + + if ( !requestok ) + { + reply = jabber_make_error_packet( node, "item-not-found", "cancel", NULL ); + if (!jabber_write_packet( ic, reply )) + imcb_log( ic, "WARNING: Error generating reply to file transfer request" ); + xt_free_node( reply ); + return XT_HANDLED; + } + + /* Request is fine. */ + + imcb_log( ic, "File transfer request from %s for %s (%zd kb). ", xt_find_attr( node, "from" ), name, size/1024 ); + + imcb_log( ic, "Accept the DCC transfer if you'd like the file. If you don't, issue the 'transfers reject' command."); + + tf = g_new0( struct jabber_transfer, 1 ); + + tf->ini_jid = g_strdup( ini_jid ); + tf->tgt_jid = g_strdup( tgt_jid ); + tf->iq_id = g_strdup( iq_id ); + tf->sid = g_strdup( sid ); + tf->ic = ic; + tf->ft = ft; + tf->ft->data = tf; + tf->ft->accept = jabber_si_answer_request; + tf->ft->free = jabber_si_free_transfer; + tf->ft->finished = jabber_si_finished; + tf->ft->canceled = jabber_si_canceled; + + jd->filetransfers = g_slist_prepend( jd->filetransfers, tf ); + + return XT_HANDLED; +} + +/* + * imcb called the accept callback which probably means that the user accepted this file transfer. + * We send our response to the initiator. + * In the next step, the initiator will send us a request for the given stream type. + * (currently that can only be a SOCKS5 bytestream) + */ +void jabber_si_answer_request( file_transfer_t *ft ) { + struct jabber_transfer *tf = ft->data; + struct xt_node *node, *sinode, *reply; + + /* generate response, start with the SI tag */ + sinode = xt_new_node( "si", NULL, NULL ); + xt_add_attr( sinode, "xmlns", XMLNS_SI ); + xt_add_attr( sinode, "profile", XMLNS_FILETRANSFER ); + xt_add_attr( sinode, "id", tf->sid ); + + /* now the file tag */ + node = xt_new_node( "file", NULL, NULL ); + xt_add_attr( node, "xmlns", XMLNS_FILETRANSFER ); + + xt_add_child( sinode, node ); + + /* and finally the feature tag */ + node = xt_new_node( "field", NULL, NULL ); + xt_add_attr( node, "var", "stream-method" ); + xt_add_attr( node, "type", "list-single" ); + + /* Currently all we can do. One could also implement in-band (IBB) */ + xt_add_child( node, xt_new_node( "value", XMLNS_BYTESTREAMS, NULL ) ); + + node = xt_new_node( "x", NULL, node ); + xt_add_attr( node, "xmlns", XMLNS_XDATA ); + xt_add_attr( node, "type", "submit" ); + + node = xt_new_node( "feature", NULL, node ); + xt_add_attr( node, "xmlns", XMLNS_FEATURE ); + + xt_add_child( sinode, node ); + + reply = jabber_make_packet( "iq", "result", tf->ini_jid, sinode ); + xt_add_attr( reply, "id", tf->iq_id ); + + if( !jabber_write_packet( tf->ic, reply ) ) + imcb_log( tf->ic, "WARNING: Error generating reply to file transfer request" ); + else + tf->accepted = TRUE; + xt_free_node( reply ); +} diff --git a/protocols/jabber/stream.c b/protocols/jabber/stream.c new file mode 100644 index 00000000..c88a72fd --- /dev/null +++ b/protocols/jabber/stream.c @@ -0,0 +1,593 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Jabber module - stream handling * +* * +* Copyright 2007 Uli Meis * +* * +* 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 + +/* Some structs for the SOCKS5 handshake */ + +struct bs_handshake_data { + + struct jabber_transfer *tf; + + /* element and elements */ + struct xt_node *qnode, *shnode; + + enum + { + BS_PHASE_CONNECT, + BS_PHASE_CONNECTED, + BS_PHASE_REQUEST, + BS_PHASE_REPLY, + BS_PHASE_REPLY_HAVE_LEN + } phase; + + /* SHA1( SID + Initiator JID + Target JID) */ + char *pseudoadr; + + void (*parentfree) ( file_transfer_t *ft ); + + gint connect_timeout; +}; + +struct socks5_hdr +{ + unsigned char ver; + union + { + unsigned char cmd; + unsigned char rep; + } cmdrep; + unsigned char rsv; + unsigned char atyp; +}; + +struct socks5_message +{ + struct socks5_hdr hdr; + unsigned char addrlen; + unsigned char address[64]; +}; + +/* connect() timeout in seconds. */ +#define JABBER_BS_CONTIMEOUT 15 + +/* shouldn't matter if it's mostly too much, kernel's smart about that + * and will only reserve some address space */ +#define JABBER_BS_BUFSIZE 65536 + +gboolean jabber_bs_handshake( gpointer data, gint fd, b_input_condition cond ); + +gboolean jabber_bs_handshake_abort( struct bs_handshake_data *bhd, char *format, ... ); + +void jabber_bs_answer_request( struct bs_handshake_data *bhd ); + +gboolean jabber_bs_read( gpointer data, gint fd, b_input_condition cond ); + +void jabber_bs_out_of_data( file_transfer_t *ft ); + +void jabber_bs_canceled( file_transfer_t *ft , char *reason ); + + +void jabber_bs_free_transfer( file_transfer_t *ft) { + struct jabber_transfer *tf = ft->data; + struct bs_handshake_data *bhd = tf->streamhandle; + void (*parentfree) ( file_transfer_t *ft ); + + parentfree = bhd->parentfree; + + if ( tf->watch_in ) + b_event_remove( tf->watch_in ); + + if( tf->watch_out ) + b_event_remove( tf->watch_out ); + + g_free( bhd->pseudoadr ); + xt_free_node( bhd->qnode ); + g_free( bhd ); + + parentfree( ft ); +} + +/* + * Parses an incoming bytestream request and calls jabber_bs_handshake on success. + */ +int jabber_bs_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode) +{ + char *sid, *ini_jid, *tgt_jid, *mode, *iq_id; + struct jabber_data *jd = ic->proto_data; + struct jabber_transfer *tf = NULL; + GSList *tflist; + struct bs_handshake_data *bhd; + + sha1_state_t sha; + char hash_hex[41]; + unsigned char hash[20]; + int i; + + if( !(iq_id = xt_find_attr( node, "id" ) ) || + !(ini_jid = xt_find_attr( node, "from" ) ) || + !(tgt_jid = xt_find_attr( node, "to" ) ) || + !(sid = xt_find_attr( qnode, "sid" ) ) ) + { + imcb_log( ic, "WARNING: Received incomplete SI bytestream request"); + return XT_HANDLED; + } + + if( ( mode = xt_find_attr( qnode, "mode" ) ) && + ( strcmp( mode, "tcp" ) != 0 ) ) + { + imcb_log( ic, "WARNING: Received SI Request for unsupported bytestream mode %s", xt_find_attr( qnode, "mode" ) ); + return XT_HANDLED; + } + + /* Let's see if we can find out what this bytestream should be for... */ + + for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) + { + struct jabber_transfer *tft = tflist->data; + if( ( strcmp( tft->sid, sid ) == 0 ) && + ( strcmp( tft->ini_jid, ini_jid ) == 0 ) && + ( strcmp( tft->tgt_jid, tgt_jid ) == 0 ) ) + { + tf = tft; + break; + } + } + + if (!tf) + { + imcb_log( ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid ); + return XT_HANDLED; + } + + /* iq_id and canceled can be reused since SI is done */ + g_free( tf->iq_id ); + tf->iq_id = g_strdup( iq_id ); + + tf->ft->canceled = jabber_bs_canceled; + + /* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */ + sha1_init( &sha ); + sha1_append( &sha, (unsigned char*) sid, strlen( sid ) ); + sha1_append( &sha, (unsigned char*) ini_jid, strlen( ini_jid ) ); + sha1_append( &sha, (unsigned char*) tgt_jid, strlen( tgt_jid ) ); + sha1_finish( &sha, hash ); + + for( i = 0; i < 20; i ++ ) + sprintf( hash_hex + i * 2, "%02x", hash[i] ); + + bhd = g_new0( struct bs_handshake_data, 1 ); + bhd->tf = tf; + bhd->qnode = xt_dup( qnode ); + bhd->shnode = bhd->qnode->children; + bhd->phase = BS_PHASE_CONNECT; + bhd->pseudoadr = g_strdup( hash_hex ); + tf->streamhandle = bhd; + bhd->parentfree = tf->ft->free; + tf->ft->free = jabber_bs_free_transfer; + + jabber_bs_handshake( bhd, 0, 0 ); + + return XT_HANDLED; +} + +/* + * This function is scheduled in bs_handshake via b_timeout_add after a (non-blocking) connect(). + */ +gboolean jabber_bs_connect_timeout( gpointer data, gint fd, b_input_condition cond ) +{ + struct bs_handshake_data *bhd = data; + + bhd->connect_timeout = 0; + + jabber_bs_handshake_abort( bhd, "no connection after %d seconds", JABBER_BS_CONTIMEOUT ); + + return FALSE; +} + +/* + * This is what a protocol handshake can look like in cooperative multitasking :) + * Might be confusing at first because it's called from different places and is recursing. + * (places being the event thread, bs_request, bs_handshake_abort, and itself) + * + * All in all, it turned out quite nice :) + */ +gboolean jabber_bs_handshake( gpointer data, gint fd, b_input_condition cond ) +{ + +/* very useful */ +#define ASSERTSOCKOP(op, msg) \ + if( (op) == -1 ) \ + return jabber_bs_handshake_abort( bhd , msg ": %s", strerror( errno ) ); + + struct bs_handshake_data *bhd = data; + struct pollfd pfd = { .fd = fd, .events = POLLHUP|POLLERR }; + short revents; + + if ( bhd->connect_timeout ) + { + b_event_remove( bhd->connect_timeout ); + bhd->connect_timeout = 0; + } + + + /* we need the real io condition */ + if ( poll( &pfd, 1, 0 ) == -1 ) + { + imcb_log( bhd->tf->ic, "poll() failed, weird!" ); + revents = 0; + }; + + revents = pfd.revents; + + if( revents & POLLERR ) + { + int sockerror; + socklen_t errlen = sizeof( sockerror ); + + if ( getsockopt( fd, SOL_SOCKET, SO_ERROR, &sockerror, &errlen ) ) + return jabber_bs_handshake_abort( bhd, "getsockopt() failed, unknown socket error during SOCKS5 handshake (weird!)" ); + + if ( bhd->phase == BS_PHASE_CONNECTED ) + return jabber_bs_handshake_abort( bhd, "connect() failed: %s", strerror( sockerror ) ); + + return jabber_bs_handshake_abort( bhd, "Socket error during SOCKS5 handshake(weird!): %s", strerror( sockerror ) ); + } + + if( revents & POLLHUP ) + return jabber_bs_handshake_abort( bhd, "Remote end closed connection" ); + + + switch( bhd->phase ) + { + case BS_PHASE_CONNECT: + { + struct xt_node *c; + char *host, *port; + struct addrinfo hints, *rp; + + if( ( c = bhd->shnode = xt_find_node( bhd->shnode, "streamhost" ) ) && + ( port = xt_find_attr( c, "port" ) ) && + ( host = xt_find_attr( c, "host" ) ) && + xt_find_attr( c, "jid" ) ) + { + memset( &hints, 0, sizeof( struct addrinfo ) ); + hints.ai_socktype = SOCK_STREAM; + + if ( getaddrinfo( host, port, &hints, &rp ) != 0 ) + return jabber_bs_handshake_abort( bhd, "getaddrinfo() failed: %s", strerror( errno ) ); + + ASSERTSOCKOP( bhd->tf->fd = fd = socket( rp->ai_family, rp->ai_socktype, 0 ), "Opening socket" ); + + sock_make_nonblocking( fd ); + + imcb_log( bhd->tf->ic, "Transferring file %s: Connecting to streamhost %s:%s", bhd->tf->ft->file_name, host, port ); + + if( ( connect( fd, rp->ai_addr, rp->ai_addrlen ) == -1 ) && + ( errno != EINPROGRESS ) ) + return jabber_bs_handshake_abort( bhd , "connect() failed: %s", strerror( errno ) ); + + freeaddrinfo( rp ); + + bhd->phase = BS_PHASE_CONNECTED; + + bhd->tf->watch_out = b_input_add( fd, GAIM_INPUT_WRITE, jabber_bs_handshake, bhd ); + + /* since it takes forever(3mins?) till connect() fails on itself we schedule a timeout */ + bhd->connect_timeout = b_timeout_add( JABBER_BS_CONTIMEOUT * 1000, jabber_bs_connect_timeout, bhd ); + + bhd->tf->watch_in = 0; + return FALSE; + } else + return jabber_bs_handshake_abort( bhd, c ? "incomplete streamhost entry: host=%s port=%s jid=%s" : NULL, + host, port, xt_find_attr( c, "jid" ) ); + } + case BS_PHASE_CONNECTED: + { + struct { + unsigned char ver; + unsigned char nmethods; + unsigned char method; + } socks5_hello = { + .ver = 5, + .nmethods = 1, + .method = 0x00 /* no auth */ + /* one could also implement username/password. If you know + * a jabber client or proxy that actually does it, tell me. + */ + }; + + ASSERTSOCKOP( send( fd, &socks5_hello, sizeof( socks5_hello ) , 0 ), "Sending auth request" ); + + bhd->phase = BS_PHASE_REQUEST; + + bhd->tf->watch_in = b_input_add( fd, GAIM_INPUT_READ, jabber_bs_handshake, bhd ); + + bhd->tf->watch_out = 0; + return FALSE; + } + case BS_PHASE_REQUEST: + { + struct socks5_message socks5_connect = + { + .hdr = + { + .ver = 5, + .cmdrep.cmd = 0x01, + .rsv = 0, + .atyp = 0x03 + }, + .addrlen = strlen( bhd->pseudoadr ) + }; + int ret; + char buf[2]; + + /* If someone's trying to be funny and sends only one byte at a time we'll fail :) */ + ASSERTSOCKOP( ret = recv( fd, buf, 2, 0 ) , "Receiving auth reply" ); + + if( !( ret == 2 ) || + !( buf[0] == 5 ) || + !( buf[1] == 0 ) ) + return jabber_bs_handshake_abort( bhd, "Auth not accepted by streamhost (reply: len=%d, ver=%d, status=%d)", + ret, buf[0], buf[1] ); + + /* copy hash into connect message */ + memcpy( socks5_connect.address, bhd->pseudoadr, socks5_connect.addrlen ); + + /* after the address comes the port, which is always 0 */ + memset( socks5_connect.address + socks5_connect.addrlen, 0, sizeof( in_port_t ) ); + + ASSERTSOCKOP( send( fd, &socks5_connect, sizeof( struct socks5_hdr ) + 1 + socks5_connect.addrlen + sizeof( in_port_t ), 0 ) , "Sending SOCKS5 Connect" ); + + bhd->phase = BS_PHASE_REPLY; + + return TRUE; + } + case BS_PHASE_REPLY: + case BS_PHASE_REPLY_HAVE_LEN: + { + /* we have to wait till we have the address length, then we know how much data is left + * (not that we'd actually care about that data, but we need to eat it all up anyway) + */ + struct socks5_message socks5_reply; + int ret; + int expectedbytes = + sizeof( struct socks5_hdr ) + 1 + + ( bhd->phase == BS_PHASE_REPLY_HAVE_LEN ? socks5_reply.addrlen + sizeof( in_port_t ) : 0 ); + + /* notice the peek, we're doing this till enough is there */ + ASSERTSOCKOP( ret = recv( fd, &socks5_reply, expectedbytes, MSG_PEEK ) , "Peeking for SOCKS5 CONNECT reply" ); + + if ( ret == 0 ) + return jabber_bs_handshake_abort( bhd , "peer has shutdown connection" ); + + /* come again */ + if ( ret < expectedbytes ) + return TRUE; + + if ( bhd->phase == BS_PHASE_REPLY ) + { + if( !( socks5_reply.hdr.ver == 5 ) || + !( socks5_reply.hdr.cmdrep.rep == 0 ) || + !( socks5_reply.hdr.atyp == 3 ) || + !( socks5_reply.addrlen <= 62 ) ) /* should also be 40, but who cares as long as all fits in the buffer... */ + return jabber_bs_handshake_abort( bhd, "SOCKS5 CONNECT failed (reply: ver=%d, rep=%d, atyp=%d, addrlen=%d", + socks5_reply.hdr.ver, + socks5_reply.hdr.cmdrep.rep, + socks5_reply.hdr.atyp, + socks5_reply.addrlen); + + /* and again for the rest */ + bhd->phase = BS_PHASE_REPLY_HAVE_LEN; + + /* since it's very likely that the rest is there as well, + * let's not wait for the event loop to call us again */ + return jabber_bs_handshake( bhd , fd, 0 ); + } + + /* got it all, remove it from the queue */ + ASSERTSOCKOP( ret = recv( fd, &socks5_reply, expectedbytes, 0 ) , "Dequeueing MSG_PEEK'ed data after SOCKS5 CONNECT" ); + + /* this shouldn't happen */ + if ( ret < expectedbytes ) + return jabber_bs_handshake_abort( bhd, "internal error, couldn't dequeue MSG_PEEK'ed data after SOCKS5 CONNECT" ); + + /* we're actually done now... */ + + jabber_bs_answer_request( bhd ); + + bhd->tf->watch_in = 0; + return FALSE; + } + default: + /* BUG */ + imcb_log( bhd->tf->ic, "BUG in file transfer code: undefined handshake phase" ); + + bhd->tf->watch_in = 0; + return FALSE; + } +#undef ASSERTSOCKOP +#undef JABBER_BS_ERR_CONDS +} + +/* + * If the handshake failed we can try the next streamhost, if there is one. + * An intelligent sender would probably specify himself as the first streamhost and + * a proxy as the second (Kopete is an example here). That way, a (potentially) + * slow proxy is only used if neccessary. + */ +gboolean jabber_bs_handshake_abort( struct bs_handshake_data *bhd, char *format, ... ) +{ + struct jabber_transfer *tf = bhd->tf; + struct xt_node *reply, *iqnode; + + if( bhd->shnode ) + { + if( format ) { + va_list params; + va_start( params, format ); + char error[128]; + + if( vsnprintf( error, 128, format, params ) < 0 ) + sprintf( error, "internal error parsing error string (BUG)" ); + va_end( params ); + + imcb_log( tf->ic, "Transferring file %s: connection to streamhost %s:%s failed (%s)", + tf->ft->file_name, + xt_find_attr( bhd->shnode, "host" ), + xt_find_attr( bhd->shnode, "port" ), + error ); + } + + /* Alright, this streamhost failed, let's try the next... */ + bhd->phase = BS_PHASE_CONNECT; + bhd->shnode = bhd->shnode->next; + + /* the if is not neccessary but saves us one recursion */ + if( bhd->shnode ) + return jabber_bs_handshake( bhd, 0, 0 ); + } + + /* out of stream hosts */ + + iqnode = jabber_make_packet( "iq", "result", tf->ini_jid, NULL ); + reply = jabber_make_error_packet( iqnode, "item-not-found", "cancel" , "404" ); + xt_free_node( iqnode ); + + xt_add_attr( reply, "id", tf->iq_id ); + + if( !jabber_write_packet( tf->ic, reply ) ) + imcb_log( tf->ic, "WARNING: Error transmitting bytestream response" ); + xt_free_node( reply ); + + imcb_file_canceled( tf->ft, "couldn't connect to any streamhosts" ); + + bhd->tf->watch_in = 0; + return FALSE; +} + +/* + * After the SOCKS5 handshake succeeds we need to inform the initiator which streamhost we chose. + * If he is the streamhost himself, he might already know that. However, if it's a proxy, + * the initiator will have to make a connection himself. + */ +void jabber_bs_answer_request( struct bs_handshake_data *bhd ) +{ + struct jabber_transfer *tf = bhd->tf; + struct xt_node *reply; + + imcb_log( tf->ic, "Transferring file %s: established SOCKS5 connection to %s:%s", + tf->ft->file_name, + xt_find_attr( bhd->shnode, "host" ), + xt_find_attr( bhd->shnode, "port" ) ); + + tf->ft->data = tf; + tf->ft->started = time( NULL ); + tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_read, tf ); + tf->ft->out_of_data = jabber_bs_out_of_data; + + reply = xt_new_node( "streamhost-used", NULL, NULL ); + xt_add_attr( reply, "jid", xt_find_attr( bhd->shnode, "jid" ) ); + + reply = xt_new_node( "query", NULL, reply ); + xt_add_attr( reply, "xmlns", XMLNS_BYTESTREAMS ); + + reply = jabber_make_packet( "iq", "result", tf->ini_jid, reply ); + + xt_add_attr( reply, "id", tf->iq_id ); + + if( !jabber_write_packet( tf->ic, reply ) ) + imcb_file_canceled( tf->ft, "Error transmitting bytestream response" ); + xt_free_node( reply ); +} + +/* Reads till it is unscheduled or the receiver signifies an overflow. */ +gboolean jabber_bs_read( gpointer data, gint fd, b_input_condition cond ) +{ + int ret; + struct jabber_transfer *tf = data; + char *buffer = g_malloc( JABBER_BS_BUFSIZE ); + + if (tf->receiver_overflow) + { + if( tf->watch_in ) + { + /* should never happen, BUG */ + imcb_file_canceled( tf->ft, "Bug in jabber file transfer code: read while overflow is true. Please report" ); + return FALSE; + } + } + + ret = recv( fd, buffer, JABBER_BS_BUFSIZE, 0 ); + + if( ret == -1 ) + { + /* shouldn't actually happen */ + if( errno == EAGAIN ) + return TRUE; + + imcb_file_canceled( tf->ft, "Error reading tcp socket" ); /* , strerror( errnum ) */ + + return FALSE; + } + + /* that should be all */ + if( ret == 0 ) + return FALSE; + + tf->bytesread += ret; + + buffer = g_realloc( buffer, ret ); + + if ( ( tf->receiver_overflow = imcb_file_write( tf->ft, buffer, ret ) ) ) + { + /* wait for imcb to run out of data */ + tf->watch_in = 0; + return FALSE; + } + + + return TRUE; +} + +/* imcb callback that is invoked when it runs out of data. + * We reschedule jabber_bs_read here if neccessary. */ +void jabber_bs_out_of_data( file_transfer_t *ft ) +{ + struct jabber_transfer *tf = ft->data; + + tf->receiver_overflow = FALSE; + + if ( !tf->watch_in ) + tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_read, tf ); +} + +/* Bad luck */ +void jabber_bs_canceled( file_transfer_t *ft , char *reason ) +{ + struct jabber_transfer *tf = ft->data; + + imcb_log( tf->ic, "File transfer aborted: %s", reason ); +} diff --git a/protocols/nogaim.h b/protocols/nogaim.h index 0e890464..8651754a 100644 --- a/protocols/nogaim.h +++ b/protocols/nogaim.h @@ -42,6 +42,7 @@ #include "account.h" #include "proxy.h" #include "md5.h" +#include "ft.h" #define BUF_LEN MSG_LEN #define BUF_LONG ( BUF_LEN * 2 ) diff --git a/root_commands.c b/root_commands.c index 642f5374..2da77519 100644 --- a/root_commands.c +++ b/root_commands.c @@ -974,6 +974,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; + 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 }, @@ -994,5 +1059,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 } }; -- cgit v1.2.3 From 2ff20765990c756533957e8da9c7c29dd3102e79 Mon Sep 17 00:00:00 2001 From: ulim Date: Mon, 3 Dec 2007 15:28:45 +0100 Subject: Intermediate commit. Sending seems to work. TODOs: * move from out_of_data to is_writable, eliminate buffers * implement "transfers reject [id]" * documentation in commands.xml * implement throughput and cummulative throughput boundaries * feature discovery before sending * implement sending over a proxy (proxy discovery, socks5 client handshake for sending, activate message) * integrate toxik-mek-ft --- dcc.c | 312 ++++++++++++-- dcc.h | 6 +- doc/user-guide/commands.xml | 43 ++ irc.c | 16 +- protocols/ft.h | 15 +- protocols/jabber/Makefile | 2 +- protocols/jabber/iq.c | 2 +- protocols/jabber/jabber.c | 1 + protocols/jabber/jabber.h | 13 +- protocols/jabber/jabber_util.c | 7 + protocols/jabber/s5bytestream.c | 906 ++++++++++++++++++++++++++++++++++++++++ protocols/jabber/si.c | 191 ++++++++- protocols/jabber/stream.c | 593 -------------------------- protocols/nogaim.h | 3 + 14 files changed, 1460 insertions(+), 650 deletions(-) create mode 100644 protocols/jabber/s5bytestream.c delete mode 100644 protocols/jabber/stream.c diff --git a/dcc.c b/dcc.c index 73fe0180..a5c77ce8 100644 --- a/dcc.c +++ b/dcc.c @@ -27,6 +27,7 @@ #include "dcc.h" #include #include +#include /* * Since that might be confusing a note on naming: @@ -75,6 +76,9 @@ static void dcc_close( file_transfer_t *file ); gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond ); gboolean dcc_listen( dcc_file_transfer_t *df, struct sockaddr_storage **saddr_ptr ); int dccs_send_request( struct dcc_file_transfer *df, char *user_nick, struct sockaddr_storage *saddr ); +gboolean dccs_recv_start( file_transfer_t *ft ); +gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond); +void dccs_recv_out_of_data( file_transfer_t *ft ); /* As defined in ft.h */ file_transfer_t *imcb_file_send_start( struct im_connection *ic, char *handle, char *file_name, size_t file_size ) @@ -103,31 +107,44 @@ gboolean imcb_file_write( file_transfer_t *file, gpointer data, size_t data_size return dccs_send_write( file, data, data_size ); } +/* As defined in ft.h */ +gboolean imcb_file_recv_start( file_transfer_t *ft ) +{ + return dccs_recv_start( ft ); +} + +dcc_file_transfer_t *dcc_alloc_transfer( char *file_name, size_t file_size, struct im_connection *ic ) +{ + file_transfer_t *file = g_new0( file_transfer_t, 1 ); + dcc_file_transfer_t *df = file->priv = g_new0( dcc_file_transfer_t, 1); + file->file_size = file_size; + file->file_name = g_strdup( file_name ); + file->local_id = local_transfer_id++; + df->ic = ic; + df->ft = file; + + return df; +} + /* This is where the sending magic starts... */ file_transfer_t *dccs_send_start( struct im_connection *ic, char *user_nick, char *file_name, size_t file_size ) { file_transfer_t *file; dcc_file_transfer_t *df; - struct sockaddr_storage **saddr; + struct sockaddr_storage *saddr; if( file_size > global.conf->max_filetransfer_size ) return NULL; - /* alloc stuff */ - file = g_new0( file_transfer_t, 1 ); - file->priv = df = g_new0( dcc_file_transfer_t, 1); - file->file_size = file_size; - file->file_name = g_strdup( file_name ); - file->local_id = local_transfer_id++; - df->ic = ic; - df->ft = file; - + df = dcc_alloc_transfer( file_name, file_size, ic ); + file = df->ft; + /* listen and request */ - if( !dcc_listen( df, saddr ) || - !dccs_send_request( df, user_nick, *saddr ) ) + if( !dcc_listen( df, &saddr ) || + !dccs_send_request( df, user_nick, saddr ) ) return NULL; - g_free( *saddr ); + g_free( saddr ); /* watch */ df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_send_proto, df ); @@ -260,25 +277,15 @@ gboolean dcc_listen( dcc_file_transfer_t *df, struct sockaddr_storage **saddr_pt } /* - * After setup, the transfer itself is handled entirely by this function. - * There are basically four things to handle: connect, receive, send, and error. + * Checks poll(), same for receiving and sending */ -gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond ) +gboolean dcc_poll( dcc_file_transfer_t *df, int fd, short *revents ) { - dcc_file_transfer_t *df = data; - file_transfer_t *file = df->ft; struct pollfd pfd = { .fd = fd, .events = POLLHUP|POLLERR|POLLIN|POLLOUT }; - short revents; - - if ( poll( &pfd, 1, 0 ) == -1 ) - { - imcb_log( df->ic, "poll() failed, weird!" ); - revents = 0; - }; - revents = pfd.revents; + ASSERTSOCKOP( poll( &pfd, 1, 0 ), "poll()" ) - if( revents & POLLERR ) + if( pfd.revents & POLLERR ) { int sockerror; socklen_t errlen = sizeof( sockerror ); @@ -289,9 +296,44 @@ gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond ) return dcc_abort( df, "Socket error: %s", strerror( sockerror ) ); } - if( revents & POLLHUP ) + if( pfd.revents & POLLHUP ) return dcc_abort( df, "Remote end closed connection" ); + *revents = pfd.revents; + + return TRUE; +} + +gboolean dcc_check_maxseg( dcc_file_transfer_t *df, int fd ) +{ +#ifdef DCC_SEND_AHEAD + /* + * use twice the maximum segment size as a maximum for calls to send(). + */ + if( max_packet_size == 0 ) + { + unsigned int mpslen = sizeof( max_packet_size ); + if( getsockopt( fd, IPPROTO_TCP, TCP_MAXSEG, &max_packet_size, &mpslen ) ) + return dcc_abort( df, "getsockopt() failed" ); + max_packet_size *= 2; + } +#endif + return TRUE; +} + +/* + * After setup, the transfer itself is handled entirely by this function. + * There are basically four things to handle: connect, receive, send, and error. + */ +gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond ) +{ + dcc_file_transfer_t *df = data; + file_transfer_t *file = df->ft; + short revents; + + if( !dcc_poll( df, fd, &revents) ) + return FALSE; + if( ( revents & POLLIN ) && ( file->status & FT_STATUS_LISTENING ) ) { @@ -304,21 +346,12 @@ gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond ) closesocket( fd ); fd = df->fd; - file->status = FT_STATUS_TRANSFERING; + file->status = FT_STATUS_TRANSFERRING; sock_make_nonblocking( fd ); -#ifdef DCC_SEND_AHEAD - /* - * use twice the maximum segment size as a maximum for calls to send(). - */ - if( max_packet_size == 0 ) - { - unsigned int mpslen = sizeof( max_packet_size ); - if( getsockopt( fd, IPPROTO_TCP, TCP_MAXSEG, &max_packet_size, &mpslen ) ) - return dcc_abort( df, "getsockopt() failed" ); - max_packet_size *= 2; - } -#endif + if ( !dcc_check_maxseg( df, fd ) ) + return FALSE; + /* IM protocol callback */ if( file->accept ) @@ -452,6 +485,113 @@ gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond ) return TRUE; } +gboolean dccs_recv_start( file_transfer_t *ft ) +{ + dcc_file_transfer_t *df = ft->priv; + struct sockaddr_storage *saddr = &df->saddr; + int fd; + socklen_t sa_len = saddr->ss_family == AF_INET ? + sizeof( struct sockaddr_in ) : sizeof( struct sockaddr_in6 ); + + if( !ft->write ) + return dcc_abort( df, "Protocol didn't register write()" ); + + ASSERTSOCKOP( fd = df->fd = socket( saddr->ss_family, SOCK_STREAM, 0 ) , "Opening Socket" ); + + sock_make_nonblocking( fd ); + + if( ( connect( fd, (struct sockaddr *)saddr, sa_len ) == -1 ) && + ( errno != EINPROGRESS ) ) + return dcc_abort( df, "Connecting" ); + + ft->status = FT_STATUS_CONNECTING; + + /* watch */ + df->watch_out = b_input_add( df->fd, GAIM_INPUT_WRITE, dccs_recv_proto, df ); + ft->out_of_data = dccs_recv_out_of_data; + + return TRUE; +} + +gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond) +{ + dcc_file_transfer_t *df = data; + file_transfer_t *ft = df->ft; + short revents; + + if( !dcc_poll( df, fd, &revents ) ) + return FALSE; + + if( ( revents & POLLOUT ) && + ( ft->status & FT_STATUS_CONNECTING ) ) + { + ft->status = FT_STATUS_TRANSFERRING; + if ( !dcc_check_maxseg( df, fd ) ) + return FALSE; + + //df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_recv_proto, df ); + + df->watch_out = 0; + return FALSE; + } + + if( revents & POLLIN ) + { + char *buffer = g_malloc( 65536 ); + int ret, done; + + ASSERTSOCKOP( ret = recv( fd, buffer, 65536, 0 ), "Receiving" ); + + if( ret == 0 ) + return dcc_abort( df, "Remote end closed connection" ); + + buffer = g_realloc( buffer, ret ); + + df->bytes_sent += ret; + + done = df->bytes_sent >= ft->file_size; + + if( ( ( df->bytes_sent - ft->bytes_transferred ) > DCC_PACKET_SIZE ) || + done ) + { + int ack, ackret; + ack = htonl( ft->bytes_transferred = df->bytes_sent ); + + ASSERTSOCKOP( ackret = send( fd, &ack, 4, 0 ), "Sending DCC ACK" ); + + if ( ackret != 4 ) + return dcc_abort( df, "Error sending DCC ACK, sent %d instead of 4 bytes", ret ); + } + + if( !ft->write( df->ft, buffer, ret ) && !done ) + { + df->watch_in = 0; + return FALSE; + } + + if( done ) + { + closesocket( fd ); + dcc_finish( ft ); + + df->watch_in = 0; + return FALSE; + } + + return TRUE; + } + + return TRUE; +} + +void dccs_recv_out_of_data( file_transfer_t *ft ) +{ + dcc_file_transfer_t *df = ft->priv; + + if( !df->watch_in ) + df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_recv_proto, df ); +} + /* * Incoming data. Note that the buffer MUST NOT be freed by the caller! * We don't copy the buffer but put it in our queue. @@ -471,7 +611,7 @@ gboolean dccs_send_write( file_transfer_t *file, gpointer data, unsigned int dat df->queued_bytes += data_size; - if( ( file->status & FT_STATUS_TRANSFERING ) && + if( ( file->status & FT_STATUS_TRANSFERRING ) && #ifndef DCC_SEND_AHEAD ( file->bytes_transferred >= df->bytes_sent ) && #endif @@ -532,3 +672,91 @@ void dcc_finish( file_transfer_t *file ) dcc_close( file ); } + +/* + * DCC SEND + * + * filename can be in "" or not. If it is, " can probably be escaped... + * IP can be an unsigned int (IPV4) or something else (IPV6) + * + */ +file_transfer_t *dcc_request( struct im_connection *ic, char *line ) +{ + char *pattern = "SEND" + " (([^\"][^ ]*)|\"([^\"]|\\\")*\")" + " (([0-9]*)|([^ ]*))" + " ([0-9]*)" + " ([0-9]*)\001"; + regmatch_t pmatch[9]; + regex_t re; + file_transfer_t *ft; + dcc_file_transfer_t *df; + + if( regcomp( &re, pattern, REG_EXTENDED ) ) + return NULL; + if( regexec( &re, line, 9, pmatch, 0 ) ) + return NULL; + + if( ( pmatch[1].rm_so > 0 ) && + ( pmatch[4].rm_so > 0 ) && + ( pmatch[7].rm_so > 0 ) && + ( pmatch[8].rm_so > 0 ) ) + { + char *input = g_strdup( line ); + char *filename, *host, *port; + size_t filesize; + struct addrinfo hints, *rp; + + /* "filename" or filename */ + if ( pmatch[2].rm_so > 0 ) + { + input[pmatch[2].rm_eo] = '\0'; + filename = input + pmatch[2].rm_so; + } else + { + input[pmatch[3].rm_eo] = '\0'; + filename = input + pmatch[3].rm_so; + } + + input[pmatch[4].rm_eo] = '\0'; + + /* number means ipv4, something else means ipv6 */ + if ( pmatch[5].rm_so > 0 ) + { + struct in_addr ipaddr = { htonl( atoi( input + pmatch[5].rm_so ) ) }; + host = inet_ntoa( ipaddr ); + } else + { + /* Contains non-numbers, hopefully an IPV6 address */ + host = input + pmatch[6].rm_so; + } + + input[pmatch[7].rm_eo] = '\0'; + input[pmatch[8].rm_eo] = '\0'; + + port = input + pmatch[7].rm_so; + filesize = atoll( input + pmatch[8].rm_so ); + + memset( &hints, 0, sizeof ( struct addrinfo ) ); + if ( getaddrinfo( host, port, &hints, &rp ) ) + { + g_free( input ); + return NULL; + } + + df = dcc_alloc_transfer( filename, filesize, ic ); + ft = df->ft; + ft->sending = TRUE; + memcpy( &df->saddr, rp->ai_addr, rp->ai_addrlen ); + + freeaddrinfo( rp ); + g_free( input ); + + df->ic->irc->file_transfers = g_slist_prepend( df->ic->irc->file_transfers, ft ); + + return ft; + } + + return NULL; +} + diff --git a/dcc.h b/dcc.h index 205107c5..d5805a1f 100644 --- a/dcc.h +++ b/dcc.h @@ -111,9 +111,12 @@ typedef struct dcc_file_transfer { */ size_t bytes_sent; - /* imcb's handle */ + /* imc's handle */ file_transfer_t *ft; + /* if we're receiving, this is the sender's socket address */ + struct sockaddr_storage saddr; + } dcc_file_transfer_t; file_transfer_t *dccs_send_start( struct im_connection *ic, char *user_nick, char *file_name, size_t file_size ); @@ -122,4 +125,5 @@ void dcc_canceled( file_transfer_t *file, char *reason ); gboolean dccs_send_write( file_transfer_t *file, gpointer data, unsigned int data_size ); +file_transfer_t *dcc_request( struct im_connection *ic, char *line ); #endif diff --git a/doc/user-guide/commands.xml b/doc/user-guide/commands.xml index ac9bdf11..8c874014 100644 --- a/doc/user-guide/commands.xml +++ b/doc/user-guide/commands.xml @@ -860,4 +860,47 @@ + + + Monitor, cancel, or reject file transfers + transfers [<cancel> id | <reject>] + + + + Without parameters the currently pending file transfers and their status will be listed. Available actions are cancel and reject. See help transfers <action> for more information. + + + + transfers + + + + + Cancels the file transfer with the given id + transfers <cancel> id + + + Cancels the file transfer with the given id + + + + transfers cancel 1 + Canceling file transfer for test + + + + + Rejects all incoming transfers + transfers <reject> + + + 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? + + + + transfers reject + + + + diff --git a/irc.c b/irc.c index 5531addb..96f75073 100644 --- a/irc.c +++ b/irc.c @@ -27,6 +27,10 @@ #include "bitlbee.h" #include "crypting.h" #include "ipc.h" +#include "dcc.h" + +#include +#include static gboolean irc_userping( gpointer _irc, int fd, b_input_condition cond ); @@ -980,9 +984,19 @@ int irc_send( irc_t *irc, char *nick, char *s, int flags ) } return( 1 ); } + else if( g_strncasecmp( s + 1, "DCC", 3 ) == 0 ) + { + if( u && u->ic && u->ic->acc->prpl->transfer_request ) + { + file_transfer_t *ft = dcc_request( u->ic, s + 5 ); + if ( ft ) + u->ic->acc->prpl->transfer_request( u->ic, ft, u->handle ); + } + return( 1 ); + } else { - irc_usermsg( irc, "Non-ACTION CTCP's aren't supported" ); + irc_usermsg( irc, "Supported CTCPs are ACTION, VERSION, PING, TYPING, DCC" ); return( 0 ); } } diff --git a/protocols/ft.h b/protocols/ft.h index 0ff44873..d41eb6c1 100644 --- a/protocols/ft.h +++ b/protocols/ft.h @@ -28,9 +28,10 @@ typedef enum { FT_STATUS_LISTENING = 1, - FT_STATUS_TRANSFERING = 2, + FT_STATUS_TRANSFERRING = 2, FT_STATUS_FINISHED = 4, - FT_STATUS_CANCELED = 8 + FT_STATUS_CANCELED = 8, + FT_STATUS_CONNECTING = 16 } file_status_t; /* @@ -60,6 +61,10 @@ typedef enum { * \------------------------/ */ typedef struct file_transfer { + + /* Are we sending something? */ + int sending; + /* * The current status of this file transfer. */ @@ -130,6 +135,11 @@ typedef struct file_transfer { */ void (*out_of_data) ( struct file_transfer *file ); + /* + * When sending files, protocols register this function to receive data. + */ + gboolean (*write) (struct file_transfer *file, char *buffer, int len ); + } file_transfer_t; /* @@ -150,4 +160,5 @@ void imcb_file_canceled( file_transfer_t *file, char *reason ); */ gboolean imcb_file_write( file_transfer_t *file, gpointer data, size_t data_size ); +gboolean imcb_file_recv_start( file_transfer_t *ft ); #endif diff --git a/protocols/jabber/Makefile b/protocols/jabber/Makefile index 47c832ae..21d7ef07 100644 --- a/protocols/jabber/Makefile +++ b/protocols/jabber/Makefile @@ -9,7 +9,7 @@ -include ../../Makefile.settings # [SH] Program variables -objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o sasl.o xmltree.o si.o stream.o +objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o sasl.o xmltree.o si.o s5bytestream.o CFLAGS += -Wall LFLAGS += -r diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c index df0102b8..77def222 100644 --- a/protocols/jabber/iq.c +++ b/protocols/jabber/iq.c @@ -167,7 +167,7 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ) } else if( strcmp( s, XMLNS_BYTESTREAMS ) == 0 ) { /* Bytestream Request (stage 2 of file transfer) */ - return jabber_bs_request( ic, node, c ); + return jabber_bs_recv_request( ic, node, c ); } else { xt_free_node( reply ); diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c index b0651a59..1f4f42ea 100644 --- a/protocols/jabber/jabber.c +++ b/protocols/jabber/jabber.c @@ -501,6 +501,7 @@ void jabber_initmodule() ret->keepalive = jabber_keepalive; ret->send_typing = jabber_send_typing; ret->handle_cmp = g_strcasecmp; + ret->transfer_request = jabber_si_transfer_request; register_protocol( ret ); } diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h index 0cb2b733..cb52d396 100644 --- a/protocols/jabber/jabber.h +++ b/protocols/jabber/jabber.h @@ -147,6 +147,7 @@ struct jabber_transfer size_t bytesread, byteswritten; int receiver_overflow; int fd; + struct sockaddr_storage saddr; }; #define JABBER_XMLCONSOLE_HANDLE "xmlconsole" @@ -200,10 +201,14 @@ int jabber_add_to_roster( struct im_connection *ic, char *handle, char *name ); int jabber_remove_from_roster( struct im_connection *ic, char *handle ); /* si.c */ -int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, struct xt_node *sinode); - -/* stream.c */ -int jabber_bs_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode); +int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, struct xt_node *sinode ); +void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who ); +void jabber_si_free_transfer( file_transfer_t *ft); + +/* s5bytestream.c */ +int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode); +gboolean jabber_bs_send_start( struct jabber_transfer *tf ); +gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, int len ); /* message.c */ xt_status jabber_pkt_message( struct xt_node *node, gpointer data ); diff --git a/protocols/jabber/jabber_util.c b/protocols/jabber/jabber_util.c index 0c5b813e..6bb65878 100644 --- a/protocols/jabber/jabber_util.c +++ b/protocols/jabber/jabber_util.c @@ -264,7 +264,14 @@ char *jabber_normalize( const char *orig ) len = strlen( orig ); new = g_new( char, len + 1 ); for( i = 0; i < len; i ++ ) + { + /* don't normalize the resource */ + if( orig[i] == '/' ) + break; new[i] = tolower( orig[i] ); + } + for( ; i < len; i ++ ) + new[i] = orig[i]; new[i] = 0; return new; diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c new file mode 100644 index 00000000..e2f32bd0 --- /dev/null +++ b/protocols/jabber/s5bytestream.c @@ -0,0 +1,906 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Jabber module - SOCKS5 Bytestreams ( XEP-0065 ) * +* * +* Copyright 2007 Uli Meis * +* * +* 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 + +struct bs_transfer { + + struct jabber_transfer *tf; + + /* element and elements */ + struct xt_node *qnode, *shnode; + + enum + { + BS_PHASE_CONNECT, + BS_PHASE_CONNECTED, + BS_PHASE_REQUEST, + BS_PHASE_REPLY + } phase; + + /* SHA1( SID + Initiator JID + Target JID) */ + char *pseudoadr; + + gint connect_timeout; +}; + +struct socks5_message +{ + unsigned char ver; + union + { + unsigned char cmd; + unsigned char rep; + } cmdrep; + unsigned char rsv; + unsigned char atyp; + unsigned char addrlen; + unsigned char address[40]; + in_port_t port; +} __attribute__ ((packed)); + +/* connect() timeout in seconds. */ +#define JABBER_BS_CONTIMEOUT 15 +/* listen timeout */ +#define JABBER_BS_LISTEN_TIMEOUT 90 + +/* very useful */ +#define ASSERTSOCKOP(op, msg) \ + if( (op) == -1 ) \ + return jabber_bs_abort( bt , msg ": %s", strerror( errno ) ); + +#define JABBER_BS_BUFSIZE 65536 + +gboolean jabber_bs_abort( struct bs_transfer *bt, char *format, ... ); +void jabber_bs_canceled( file_transfer_t *ft , char *reason ); +void jabber_bs_free_transfer( file_transfer_t *ft); +gboolean jabber_bs_connect_timeout( gpointer data, gint fd, b_input_condition cond ); +gboolean jabber_bs_poll( struct bs_transfer *bt, int fd, short *revents ); +gboolean jabber_bs_peek( struct bs_transfer *bt, void *buffer, int buflen ); + +void jabber_bs_recv_answer_request( struct bs_transfer *bt ); +gboolean jabber_bs_recv_read( gpointer data, gint fd, b_input_condition cond ); +void jabber_bs_recv_out_of_data( file_transfer_t *ft ); +gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition cond ); +gboolean jabber_bs_recv_handshake_abort( struct bs_transfer *bt, char *error ); +int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode); + +gboolean jabber_bs_send_handshake_abort( struct bs_transfer *bt, char *error ); +gboolean jabber_bs_send_request( struct jabber_transfer *tf, char *host, char *port ); +gboolean jabber_bs_send_handshake( gpointer data, gint fd, b_input_condition cond ); +gboolean jabber_bs_send_listen( struct bs_transfer *bt, struct sockaddr_storage *saddr, char *host, char *port ); + +/* + * Frees a bs_transfer struct and calls the SI free function + */ +void jabber_bs_free_transfer( file_transfer_t *ft) { + struct jabber_transfer *tf = ft->data; + struct bs_transfer *bt = tf->streamhandle; + + if ( tf->watch_in ) + b_event_remove( tf->watch_in ); + + if( tf->watch_out ) + b_event_remove( tf->watch_out ); + + g_free( bt->pseudoadr ); + xt_free_node( bt->qnode ); + g_free( bt ); +//iq_id + jabber_si_free_transfer( ft ); +} + +gboolean jabber_bs_peek( struct bs_transfer *bt, void *buffer, int buflen ) +{ + int ret; + int fd = bt->tf->fd; + + ASSERTSOCKOP( ret = recv( fd, buffer, buflen, MSG_PEEK ), "MSG_PEEK'ing" ); + + if( ret == 0 ) + return jabber_bs_abort( bt, "Remote end closed connection" ); + + if( ret < buflen ) + return ret; + + ASSERTSOCKOP( ret = recv( fd, buffer, buflen, 0 ), "Dequeuing after MSG_PEEK" ); + + if( ret != buflen ) + return jabber_bs_abort( bt, "recv returned less than previous recv with MSG_PEEK" ); + + return ret; +} + + +/* + * This function is scheduled in bs_handshake via b_timeout_add after a (non-blocking) connect(). + */ +gboolean jabber_bs_connect_timeout( gpointer data, gint fd, b_input_condition cond ) +{ + struct bs_transfer *bt = data; + + bt->connect_timeout = 0; + + jabber_bs_abort( bt, "no connection after %d seconds", bt->tf->ft->sending ? JABBER_BS_LISTEN_TIMEOUT : JABBER_BS_CONTIMEOUT ); + + return FALSE; +} + +gboolean jabber_bs_poll( struct bs_transfer *bt, int fd, short *revents ) +{ + struct pollfd pfd = { .fd = fd, .events = POLLHUP|POLLERR }; + + if ( bt->connect_timeout ) + { + b_event_remove( bt->connect_timeout ); + bt->connect_timeout = 0; + } + + ASSERTSOCKOP( poll( &pfd, 1, 0 ), "poll()" ) + + if( pfd.revents & POLLERR ) + { + int sockerror; + socklen_t errlen = sizeof( sockerror ); + + if ( getsockopt( fd, SOL_SOCKET, SO_ERROR, &sockerror, &errlen ) ) + return jabber_bs_abort( bt, "getsockopt() failed, unknown socket error during SOCKS5 handshake (weird!)" ); + + if ( bt->phase == BS_PHASE_CONNECTED ) + return jabber_bs_abort( bt, "connect failed: %s", strerror( sockerror ) ); + + return jabber_bs_abort( bt, "Socket error during SOCKS5 handshake(weird!): %s", strerror( sockerror ) ); + } + + if( pfd.revents & POLLHUP ) + return jabber_bs_abort( bt, "Remote end closed connection" ); + + *revents = pfd.revents; + + return TRUE; +} + +gboolean jabber_bs_abort( struct bs_transfer *bt, char *format, ... ) +{ + va_list params; + va_start( params, format ); + char error[128]; + + if( vsnprintf( error, 128, format, params ) < 0 ) + sprintf( error, "internal error parsing error string (BUG)" ); + va_end( params ); + if( bt->tf->ft->sending ) + return jabber_bs_recv_handshake_abort( bt, error ); + else + return jabber_bs_send_handshake_abort( bt, error ); +} + +/* Bad luck */ +void jabber_bs_canceled( file_transfer_t *ft , char *reason ) +{ + struct jabber_transfer *tf = ft->data; + + imcb_log( tf->ic, "File transfer aborted: %s", reason ); +} + +/* + * Parses an incoming bytestream request and calls jabber_bs_handshake on success. + */ +int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode) +{ + char *sid, *ini_jid, *tgt_jid, *mode, *iq_id; + struct jabber_data *jd = ic->proto_data; + struct jabber_transfer *tf = NULL; + GSList *tflist; + struct bs_transfer *bt; + + sha1_state_t sha; + char hash_hex[41]; + unsigned char hash[20]; + int i; + + if( !(iq_id = xt_find_attr( node, "id" ) ) || + !(ini_jid = xt_find_attr( node, "from" ) ) || + !(tgt_jid = xt_find_attr( node, "to" ) ) || + !(sid = xt_find_attr( qnode, "sid" ) ) ) + { + imcb_log( ic, "WARNING: Received incomplete SI bytestream request"); + return XT_HANDLED; + } + + if( ( mode = xt_find_attr( qnode, "mode" ) ) && + ( strcmp( mode, "tcp" ) != 0 ) ) + { + imcb_log( ic, "WARNING: Received SI Request for unsupported bytestream mode %s", xt_find_attr( qnode, "mode" ) ); + return XT_HANDLED; + } + + /* Let's see if we can find out what this bytestream should be for... */ + + for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) + { + struct jabber_transfer *tft = tflist->data; + if( ( strcmp( tft->sid, sid ) == 0 ) && + ( strcmp( tft->ini_jid, ini_jid ) == 0 ) && + ( strcmp( tft->tgt_jid, tgt_jid ) == 0 ) ) + { + tf = tft; + break; + } + } + + if (!tf) + { + imcb_log( ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid ); + return XT_HANDLED; + } + + /* iq_id and canceled can be reused since SI is done */ + g_free( tf->iq_id ); + tf->iq_id = g_strdup( iq_id ); + + tf->ft->canceled = jabber_bs_canceled; + + /* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */ + sha1_init( &sha ); + sha1_append( &sha, (unsigned char*) sid, strlen( sid ) ); + sha1_append( &sha, (unsigned char*) ini_jid, strlen( ini_jid ) ); + sha1_append( &sha, (unsigned char*) tgt_jid, strlen( tgt_jid ) ); + sha1_finish( &sha, hash ); + + for( i = 0; i < 20; i ++ ) + sprintf( hash_hex + i * 2, "%02x", hash[i] ); + + bt = g_new0( struct bs_transfer, 1 ); + bt->tf = tf; + bt->qnode = xt_dup( qnode ); + bt->shnode = bt->qnode->children; + bt->phase = BS_PHASE_CONNECT; + bt->pseudoadr = g_strdup( hash_hex ); + tf->streamhandle = bt; + tf->ft->free = jabber_bs_free_transfer; + + jabber_bs_recv_handshake( bt, 0, 0 ); + + return XT_HANDLED; +} +/* + * This is what a protocol handshake can look like in cooperative multitasking :) + * Might be confusing at first because it's called from different places and is recursing. + * (places being the event thread, bs_request, bs_handshake_abort, and itself) + * + * All in all, it turned out quite nice :) + */ +gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition cond ) +{ + + struct bs_transfer *bt = data; + short revents; + + if ( !jabber_bs_poll( bt, fd, &revents ) ) + return FALSE; + + switch( bt->phase ) + { + case BS_PHASE_CONNECT: + { + struct xt_node *c; + char *host, *port; + struct addrinfo hints, *rp; + + if( ( c = bt->shnode = xt_find_node( bt->shnode, "streamhost" ) ) && + ( port = xt_find_attr( c, "port" ) ) && + ( host = xt_find_attr( c, "host" ) ) && + xt_find_attr( c, "jid" ) ) + { + memset( &hints, 0, sizeof( struct addrinfo ) ); + hints.ai_socktype = SOCK_STREAM; + + if ( getaddrinfo( host, port, &hints, &rp ) != 0 ) + return jabber_bs_abort( bt, "getaddrinfo() failed: %s", strerror( errno ) ); + + ASSERTSOCKOP( bt->tf->fd = fd = socket( rp->ai_family, rp->ai_socktype, 0 ), "Opening socket" ); + + sock_make_nonblocking( fd ); + + imcb_log( bt->tf->ic, "Transferring file %s: Connecting to streamhost %s:%s", bt->tf->ft->file_name, host, port ); + + if( ( connect( fd, rp->ai_addr, rp->ai_addrlen ) == -1 ) && + ( errno != EINPROGRESS ) ) + return jabber_bs_abort( bt , "connect() failed: %s", strerror( errno ) ); + + freeaddrinfo( rp ); + + bt->phase = BS_PHASE_CONNECTED; + + bt->tf->watch_out = b_input_add( fd, GAIM_INPUT_WRITE, jabber_bs_recv_handshake, bt ); + + /* since it takes forever(3mins?) till connect() fails on itself we schedule a timeout */ + bt->connect_timeout = b_timeout_add( JABBER_BS_CONTIMEOUT * 1000, jabber_bs_connect_timeout, bt ); + + bt->tf->watch_in = 0; + return FALSE; + } else + return jabber_bs_abort( bt, c ? "incomplete streamhost entry: host=%s port=%s jid=%s" : NULL, + host, port, xt_find_attr( c, "jid" ) ); + } + case BS_PHASE_CONNECTED: + { + struct { + unsigned char ver; + unsigned char nmethods; + unsigned char method; + } socks5_hello = { + .ver = 5, + .nmethods = 1, + .method = 0x00 /* no auth */ + /* one could also implement username/password. If you know + * a jabber client or proxy that actually does it, tell me. + */ + }; + + ASSERTSOCKOP( send( fd, &socks5_hello, sizeof( socks5_hello ) , 0 ), "Sending auth request" ); + + bt->phase = BS_PHASE_REQUEST; + + bt->tf->watch_in = b_input_add( fd, GAIM_INPUT_READ, jabber_bs_recv_handshake, bt ); + + bt->tf->watch_out = 0; + return FALSE; + } + case BS_PHASE_REQUEST: + { + struct socks5_message socks5_connect = + { + .ver = 5, + .cmdrep.cmd = 0x01, + .rsv = 0, + .atyp = 0x03, + .addrlen = strlen( bt->pseudoadr ), + .port = 0 + }; + int ret; + char buf[2]; + + /* If someone's trying to be funny and sends only one byte at a time we'll fail :) */ + ASSERTSOCKOP( ret = recv( fd, buf, 2, 0 ) , "Receiving auth reply" ); + + if( !( ret == 2 ) || + !( buf[0] == 5 ) || + !( buf[1] == 0 ) ) + return jabber_bs_abort( bt, "Auth not accepted by streamhost (reply: len=%d, ver=%d, status=%d)", + ret, buf[0], buf[1] ); + + /* copy hash into connect message */ + memcpy( socks5_connect.address, bt->pseudoadr, socks5_connect.addrlen ); + + ASSERTSOCKOP( send( fd, &socks5_connect, sizeof( struct socks5_message ), 0 ) , "Sending SOCKS5 Connect" ); + + bt->phase = BS_PHASE_REPLY; + + return TRUE; + } + case BS_PHASE_REPLY: + { + struct socks5_message socks5_reply; + int ret; + + if ( !( ret = jabber_bs_peek( bt, &socks5_reply, sizeof( struct socks5_message ) ) ) ) + return FALSE; + + if ( ret < sizeof( socks5_reply ) ) + return TRUE; + + if( !( socks5_reply.ver == 5 ) || + !( socks5_reply.cmdrep.rep == 0 ) || + !( socks5_reply.atyp == 3 ) || + !( socks5_reply.addrlen == 40 ) ) + return jabber_bs_abort( bt, "SOCKS5 CONNECT failed (reply: ver=%d, rep=%d, atyp=%d, addrlen=%d", + socks5_reply.ver, + socks5_reply.cmdrep.rep, + socks5_reply.atyp, + socks5_reply.addrlen); + + jabber_bs_recv_answer_request( bt ); + + // reset in answer_request bt->tf->watch_in = 0; + return FALSE; + } + default: + /* BUG */ + imcb_log( bt->tf->ic, "BUG in file transfer code: undefined handshake phase" ); + + bt->tf->watch_in = 0; + return FALSE; + } +} + +/* + * If the handshake failed we can try the next streamhost, if there is one. + * An intelligent sender would probably specify himself as the first streamhost and + * a proxy as the second (Kopete is an example here). That way, a (potentially) + * slow proxy is only used if neccessary. + */ +gboolean jabber_bs_recv_handshake_abort( struct bs_transfer *bt, char *error ) +{ + struct jabber_transfer *tf = bt->tf; + struct xt_node *reply, *iqnode; + + if( bt->shnode ) + { + imcb_log( tf->ic, "Transferring file %s: connection to streamhost %s:%s failed (%s)", + tf->ft->file_name, + xt_find_attr( bt->shnode, "host" ), + xt_find_attr( bt->shnode, "port" ), + error ); + + /* Alright, this streamhost failed, let's try the next... */ + bt->phase = BS_PHASE_CONNECT; + bt->shnode = bt->shnode->next; + + /* the if is not neccessary but saves us one recursion */ + if( bt->shnode ) + return jabber_bs_recv_handshake( bt, 0, 0 ); + } + + /* out of stream hosts */ + + iqnode = jabber_make_packet( "iq", "result", tf->ini_jid, NULL ); + reply = jabber_make_error_packet( iqnode, "item-not-found", "cancel" , "404" ); + xt_free_node( iqnode ); + + xt_add_attr( reply, "id", tf->iq_id ); + + if( !jabber_write_packet( tf->ic, reply ) ) + imcb_log( tf->ic, "WARNING: Error transmitting bytestream response" ); + xt_free_node( reply ); + + imcb_file_canceled( tf->ft, "couldn't connect to any streamhosts" ); + + bt->tf->watch_in = 0; + return FALSE; +} + +/* + * After the SOCKS5 handshake succeeds we need to inform the initiator which streamhost we chose. + * If he is the streamhost himself, he might already know that. However, if it's a proxy, + * the initiator will have to make a connection himself. + */ +void jabber_bs_recv_answer_request( struct bs_transfer *bt ) +{ + struct jabber_transfer *tf = bt->tf; + struct xt_node *reply; + + imcb_log( tf->ic, "Transferring file %s: established SOCKS5 connection to %s:%s", + tf->ft->file_name, + xt_find_attr( bt->shnode, "host" ), + xt_find_attr( bt->shnode, "port" ) ); + + tf->ft->data = tf; + tf->ft->started = time( NULL ); + tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_recv_read, tf ); + tf->ft->out_of_data = jabber_bs_recv_out_of_data; + + reply = xt_new_node( "streamhost-used", NULL, NULL ); + xt_add_attr( reply, "jid", xt_find_attr( bt->shnode, "jid" ) ); + + reply = xt_new_node( "query", NULL, reply ); + xt_add_attr( reply, "xmlns", XMLNS_BYTESTREAMS ); + + reply = jabber_make_packet( "iq", "result", tf->ini_jid, reply ); + + xt_add_attr( reply, "id", tf->iq_id ); + + if( !jabber_write_packet( tf->ic, reply ) ) + imcb_file_canceled( tf->ft, "Error transmitting bytestream response" ); + xt_free_node( reply ); +} + +/* Reads till it is unscheduled or the receiver signifies an overflow. */ +gboolean jabber_bs_recv_read( gpointer data, gint fd, b_input_condition cond ) +{ + int ret; + struct jabber_transfer *tf = data; + struct bs_transfer *bt = tf->streamhandle; + char *buffer = g_malloc( JABBER_BS_BUFSIZE ); + + if (tf->receiver_overflow) + { + if( tf->watch_in ) + { + /* should never happen, BUG */ + imcb_file_canceled( tf->ft, "Bug in jabber file transfer code: read while overflow is true. Please report" ); + return FALSE; + } + } + + ASSERTSOCKOP( ret = recv( fd, buffer, JABBER_BS_BUFSIZE, 0 ) , "Receiving" ); + + /* that should be all */ + if( ret == 0 ) + return FALSE; + + tf->bytesread += ret; + + buffer = g_realloc( buffer, ret ); + + if ( ( tf->receiver_overflow = imcb_file_write( tf->ft, buffer, ret ) ) ) + { + /* wait for imcb to run out of data */ + tf->watch_in = 0; + return FALSE; + } + + return TRUE; +} + +/* imcb callback that is invoked when it runs out of data. + * We reschedule jabber_bs_read here if neccessary. */ +void jabber_bs_recv_out_of_data( file_transfer_t *ft ) +{ + struct jabber_transfer *tf = ft->data; + + tf->receiver_overflow = FALSE; + + if ( !tf->watch_in ) + tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_recv_read, tf ); +} + +/* signal ood and be done */ +gboolean jabber_bs_send_can_write( gpointer data, gint fd, b_input_condition cond ) +{ + struct bs_transfer *bt = data; + + bt->tf->ft->out_of_data( bt->tf->ft ); + + bt->tf->watch_out = 0; + return FALSE; +} + +/* try to send the stuff. If you can't return false and wait for writable */ +gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, int len ) +{ + struct jabber_transfer *tf = ft->data; + struct bs_transfer *bt = tf->streamhandle; + int ret; + + if ( ( ( ret = send( tf->fd, buffer, len, 0 ) ) == -1 ) && + ( errno != EAGAIN ) ) + return jabber_bs_abort( bt, "send failed on socket with: %s", strerror( errno ) ); + + if( ret == 0 ) + return jabber_bs_abort( bt, "Remote end closed connection" ); + + if( ret == -1 ) + { + bt->tf->watch_out = b_input_add( tf->fd, GAIM_INPUT_WRITE, jabber_bs_send_can_write, bt ); + return FALSE; + } + + return TRUE; +} + +static xt_status jabber_bs_send_handle_reply(struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) { + struct jabber_transfer *tf = NULL; + struct jabber_data *jd = ic->proto_data; + struct bs_transfer *bt; + GSList *tflist; + struct xt_node *c; + char *sid, *jid; + + if( !( c = xt_find_node( node->children, "query" ) ) || + !( c = xt_find_node( c->children, "streamhost-used" ) ) || + !( jid = xt_find_attr( c, "jid" ) ) ) + + { + imcb_log( ic, "WARNING: Received incomplete bytestream reply" ); + return XT_HANDLED; + } + + if( !( c = xt_find_node( orig->children, "query" ) ) || + !( sid = xt_find_attr( c, "sid" ) ) ) + { + imcb_log( ic, "WARNING: Error parsing request corresponding to the incoming bytestream reply" ); + return XT_HANDLED; + } + + /* Let's see if we can find out what this bytestream should be for... */ + + for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) + { + struct jabber_transfer *tft = tflist->data; + if( ( strcmp( tft->sid, sid ) == 0 ) ) + { + tf = tft; + break; + } + } + + if( !tf ) + { + imcb_log( ic, "WARNING: Received SOCKS5 bytestream reply to unknown request" ); + return XT_HANDLED; + } + + bt = tf->streamhandle; + + tf->accepted = TRUE; + + if( bt->phase == BS_PHASE_REPLY ) + { + tf->ft->started = time( NULL ); + tf->ft->out_of_data( tf->ft ); + } + + //bt->tf->watch_out = b_input_add( tf->fd, GAIM_INPUT_WRITE, jabber_bs_send_write, tf ); + + return XT_HANDLED; +} + +gboolean jabber_bs_send_start( struct jabber_transfer *tf ) +{ + char host[INET6_ADDRSTRLEN], port[6]; + struct bs_transfer *bt; + sha1_state_t sha; + char hash_hex[41]; + unsigned char hash[20]; + int i; + + /* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */ + sha1_init( &sha ); + sha1_append( &sha, (unsigned char*) tf->sid, strlen( tf->sid ) ); + sha1_append( &sha, (unsigned char*) tf->ini_jid, strlen( tf->ini_jid ) ); + sha1_append( &sha, (unsigned char*) tf->tgt_jid, strlen( tf->tgt_jid ) ); + sha1_finish( &sha, hash ); + + for( i = 0; i < 20; i ++ ) + sprintf( hash_hex + i * 2, "%02x", hash[i] ); + + bt = g_new0( struct bs_transfer, 1 ); + bt->tf = tf; + //bt->qnode = xt_dup( qnode ); + //bt->shnode = bt->qnode->children; + bt->phase = BS_PHASE_CONNECT; + bt->pseudoadr = g_strdup( hash_hex ); + tf->streamhandle = bt; + tf->ft->free = jabber_bs_free_transfer; + tf->ft->canceled = jabber_bs_canceled; + + if ( !jabber_bs_send_listen( bt, &tf->saddr, host, port ) ) + return FALSE; + + bt->tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_send_handshake, bt ); + bt->connect_timeout = b_timeout_add( JABBER_BS_LISTEN_TIMEOUT * 1000, jabber_bs_connect_timeout, bt ); + return jabber_bs_send_request( tf, host, port ); +} + +gboolean jabber_bs_send_request( struct jabber_transfer *tf, char *host, char *port ) +{ + struct xt_node *sh, *query, *iq; + + sh = xt_new_node( "streamhost", NULL, NULL ); + xt_add_attr( sh, "jid", tf->ini_jid ); + xt_add_attr( sh, "host", host ); + xt_add_attr( sh, "port", port ); + + query = xt_new_node( "query", NULL, NULL ); + xt_add_attr( query, "xmlns", XMLNS_BYTESTREAMS ); + xt_add_attr( query, "sid", tf->sid ); + xt_add_attr( query, "mode", "tcp" ); + xt_add_child( query, sh ); + + iq = jabber_make_packet( "iq", "set", tf->tgt_jid, query ); + xt_add_attr( iq, "from", tf->ini_jid ); + + //xt_free_node( query ); + + jabber_cache_add( tf->ic, iq, jabber_bs_send_handle_reply ); + + if( !jabber_write_packet( tf->ic, iq ) ) + imcb_file_canceled( tf->ft, "Error transmitting bytestream request" ); + return TRUE; +} + +gboolean jabber_bs_send_handshake_abort(struct bs_transfer *bt, char *error ) +{ + struct jabber_transfer *tf = bt->tf; + + imcb_log( tf->ic, "Transferring file %s: SOCKS5 handshake failed: %s", + tf->ft->file_name, + error ); + + imcb_file_canceled( tf->ft, error ); + + return FALSE; +} + +/* + * Creates a listening socket and returns it in saddr_ptr. + */ +gboolean jabber_bs_send_listen( struct bs_transfer *bt, struct sockaddr_storage *saddr, char *host, char *port ) +{ + struct jabber_transfer *tf = bt->tf; + int fd; + char hostname[ HOST_NAME_MAX + 1 ]; + struct addrinfo hints, *rp; + socklen_t ssize = sizeof( struct sockaddr_storage ); + + /* won't be long till someone asks for this to be configurable :) */ + + ASSERTSOCKOP( gethostname( hostname, sizeof( hostname ) ), "gethostname()" ); + + memset( &hints, 0, sizeof( struct addrinfo ) ); + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_NUMERICSERV; + + if ( getaddrinfo( hostname, "0", &hints, &rp ) != 0 ) + return jabber_bs_abort( bt, "getaddrinfo()" ); + + memcpy( saddr, rp->ai_addr, rp->ai_addrlen ); + + ASSERTSOCKOP( fd = tf->fd = socket( saddr->ss_family, SOCK_STREAM, 0 ), "Opening socket" ); + + ASSERTSOCKOP( bind( fd, ( struct sockaddr *)saddr, rp->ai_addrlen ), "Binding socket" ); + + freeaddrinfo( rp ); + + ASSERTSOCKOP( listen( fd, 1 ), "Making socket listen" ); + + if ( !inet_ntop( saddr->ss_family, saddr->ss_family == AF_INET ? + ( void * )&( ( struct sockaddr_in * ) saddr )->sin_addr.s_addr : ( void * )&( ( struct sockaddr_in6 * ) saddr )->sin6_addr.s6_addr + , host, INET6_ADDRSTRLEN ) ) + return jabber_bs_abort( bt, "inet_ntop failed on listening socket" ); + + ASSERTSOCKOP( getsockname( fd, ( struct sockaddr *)saddr, &ssize ), "Getting socket name" ); + + if( saddr->ss_family == AF_INET ) + sprintf( port, "%d", ntohs( ( ( struct sockaddr_in *) saddr )->sin_port ) ); + else + sprintf( port, "%d", ntohs( ( ( struct sockaddr_in6 *) saddr )->sin6_port ) ); + + return TRUE; +} + +/* + * SOCKS5BYTESTREAM protocol for the sender + */ +gboolean jabber_bs_send_handshake( gpointer data, gint fd, b_input_condition cond ) +{ + struct bs_transfer *bt = data; + struct jabber_transfer *tf = bt->tf; + short revents; + + if ( !jabber_bs_poll( bt, fd, &revents ) ) + return FALSE; + + switch( bt->phase ) + { + case BS_PHASE_CONNECT: + { + struct sockaddr_storage clt_addr; + socklen_t ssize = sizeof( clt_addr ); + + /* Connect */ + + ASSERTSOCKOP( tf->fd = accept( fd, (struct sockaddr *) &clt_addr, &ssize ), "Accepting connection" ); + + closesocket( fd ); + fd = tf->fd; + sock_make_nonblocking( fd ); + + bt->phase = BS_PHASE_CONNECTED; + + bt->tf->watch_in = b_input_add( fd, GAIM_INPUT_READ, jabber_bs_send_handshake, bt ); + return FALSE; + } + case BS_PHASE_CONNECTED: + { + int ret, have_noauth=FALSE; + struct { + unsigned char ver; + unsigned char method; + } socks5_auth_reply = { .ver = 5, .method = 0 }; + struct { + unsigned char ver; + unsigned char nmethods; + unsigned char method; + } socks5_hello; + + if( !( ret = jabber_bs_peek( bt, &socks5_hello, sizeof( socks5_hello ) ) ) ) + return FALSE; + + if( ret < sizeof( socks5_hello ) ) + return TRUE; + + if( !( socks5_hello.ver == 5 ) || + !( socks5_hello.nmethods >= 1 ) || + !( socks5_hello.nmethods < 32 ) ) + return jabber_bs_abort( bt, "Invalid auth request ver=%d nmethods=%d method=%d", socks5_hello.ver, socks5_hello.nmethods, socks5_hello.method ); + + have_noauth = socks5_hello.method == 0; + + if( socks5_hello.nmethods > 1 ) + { + char mbuf[32]; + int i; + ASSERTSOCKOP( ret = recv( fd, mbuf, socks5_hello.nmethods - 1, 0 ) , "Receiving auth methods" ); + if( ret < ( socks5_hello.nmethods - 1 ) ) + return jabber_bs_abort( bt, "Partial auth request"); + for( i = 0 ; !have_noauth && ( i < socks5_hello.nmethods - 1 ) ; i ++ ) + if( mbuf[i] == 0 ) + have_noauth = TRUE; + } + + if( !have_noauth ) + return jabber_bs_abort( bt, "Auth request didn't include no authentication" ); + + ASSERTSOCKOP( send( fd, &socks5_auth_reply, sizeof( socks5_auth_reply ) , 0 ), "Sending auth reply" ); + + bt->phase = BS_PHASE_REQUEST; + + return TRUE; + } + case BS_PHASE_REQUEST: + { + struct socks5_message socks5_connect; + int msgsize = sizeof( struct socks5_message ); + + if( !jabber_bs_peek( bt, &socks5_connect, msgsize ) ) + return FALSE; + + if( !( socks5_connect.ver == 5) || + !( socks5_connect.cmdrep.cmd == 1 ) || + !( socks5_connect.atyp == 3 ) || + !(socks5_connect.addrlen == 40 ) ) + return jabber_bs_abort( bt, "Invalid SOCKS5 Connect message (addrlen=%d, ver=%d, cmd=%d, atyp=%d)", socks5_connect.addrlen, socks5_connect.ver, socks5_connect.cmdrep.cmd, socks5_connect.atyp ); + if( !( memcmp( socks5_connect.address, bt->pseudoadr, 40 ) == 0 ) ) + return jabber_bs_abort( bt, "SOCKS5 Connect message contained wrong digest"); + + socks5_connect.cmdrep.rep = 0; + + ASSERTSOCKOP( send( fd, &socks5_connect, msgsize, 0 ), "Sending connect reply" ); + + bt->phase = BS_PHASE_REPLY; + + /* don't start sending till the streamhost-used message comes in */ + if( tf->accepted ) + { + tf->ft->started = time( NULL ); + tf->ft->out_of_data( tf->ft ); + } + + tf->watch_in = 0; + return FALSE; + + } + default: + /* BUG */ + imcb_log( bt->tf->ic, "BUG in file transfer code: undefined handshake phase" ); + + bt->tf->watch_in = 0; + return FALSE; + } +} +#undef ASSERTSOCKOP diff --git a/protocols/jabber/si.c b/protocols/jabber/si.c index d16f723a..598cbd03 100644 --- a/protocols/jabber/si.c +++ b/protocols/jabber/si.c @@ -25,8 +25,9 @@ #include "sha1.h" void jabber_si_answer_request( file_transfer_t *ft ); +int jabber_si_send_request(struct im_connection *ic, char *who, struct jabber_transfer *tf ); -/* imcb callback */ +/* file_transfer free() callback */ void jabber_si_free_transfer( file_transfer_t *ft) { struct jabber_transfer *tf = ft->data; @@ -49,7 +50,7 @@ void jabber_si_free_transfer( file_transfer_t *ft) g_free( tf->sid ); } -/* imcb callback */ +/* file_transfer finished() callback */ void jabber_si_finished( file_transfer_t *ft ) { struct jabber_transfer *tf = ft->data; @@ -57,7 +58,7 @@ void jabber_si_finished( file_transfer_t *ft ) imcb_log( tf->ic, "File %s transferred successfully!" , ft->file_name ); } -/* imcb callback */ +/* file_transfer canceled() callback */ void jabber_si_canceled( file_transfer_t *ft, char *reason ) { struct jabber_transfer *tf = ft->data; @@ -77,6 +78,29 @@ void jabber_si_canceled( file_transfer_t *ft, char *reason ) } +void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who ) +{ + struct jabber_transfer *tf; + struct jabber_data *jd = ic->proto_data; + + imcb_log( ic, "Incoming file from %s : %s %zd bytes", ic->irc->nick, ft->file_name, ft->file_size ); + + tf = g_new0( struct jabber_transfer, 1 ); + + tf->ic = ic; + tf->ft = ft; + tf->ft->data = tf; + tf->ft->free = jabber_si_free_transfer; + tf->ft->finished = jabber_si_finished; + ft->write = jabber_bs_send_write; + + jd->filetransfers = g_slist_prepend( jd->filetransfers, tf ); + + jabber_si_send_request( ic, who, tf ); + + imcb_file_recv_start( ft ); +} + /* * First function that gets called when a file transfer request comes in. * A lot to parse. @@ -135,6 +159,9 @@ int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, st requestok = TRUE; break; } + + if ( !requestok ) + imcb_log( ic, "WARNING: Unsupported file transfer request from %s", ini_jid); } if ( requestok ) @@ -159,8 +186,7 @@ int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, st } *s = '/'; - } else - imcb_log( ic, "WARNING: Unsupported file transfer request from %s", ini_jid); + } if ( !requestok ) { @@ -244,3 +270,158 @@ void jabber_si_answer_request( file_transfer_t *ft ) { tf->accepted = TRUE; xt_free_node( reply ); } + +static xt_status jabber_si_handle_response(struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ + struct xt_node *c, *d; + char *ini_jid, *tgt_jid; + GSList *tflist; + struct jabber_transfer *tf=NULL; + struct jabber_data *jd = ic->proto_data; + char *sid; + + if( !( tgt_jid = xt_find_attr( node, "from" ) ) || + !( ini_jid = xt_find_attr( node, "to" ) ) ) + { + imcb_log( ic, "Invalid SI response from=%s to=%s", tgt_jid, ini_jid ); + return XT_HANDLED; + } + + imcb_log( ic, "GOT RESPONSE TO FILE" ); + /* All this means we expect something like this: ( I think ) + * + * + * + * + * + * + * + */ + if( !( tgt_jid = xt_find_attr( node, "from" ) ) || + !( ini_jid = xt_find_attr( node, "to" ) ) || + !( c = xt_find_node( node->children, "si" ) ) || + !( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_SI ) == 0 ) || + !( sid = xt_find_attr( c, "id" ) )|| + !( d = xt_find_node( c->children, "file" ) ) || + !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_FILETRANSFER ) == 0 ) || + !( d = xt_find_node( c->children, "feature" ) ) || + !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_FEATURE ) == 0 ) || + !( d = xt_find_node( d->children, "x" ) ) || + !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_XDATA ) == 0 ) || + !( strcmp( xt_find_attr( d, "type" ), "submit" ) == 0 ) || + !( d = xt_find_node( d->children, "field" ) ) || + !( strcmp( xt_find_attr( d, "var" ), "stream-method" ) == 0 ) || + !( d = xt_find_node( d->children, "value" ) ) ) + { + imcb_log( ic, "WARNING: Received incomplete Stream Initiation response" ); + return XT_HANDLED; + } + + if( !( strcmp( d->text, XMLNS_BYTESTREAMS ) == 0 ) ) { + /* since we should only have advertised what we can do and the peer should + * only have chosen what we offered, this should never happen */ + imcb_log( ic, "WARNING: Received invalid Stream Initiation response, method %s", d->text ); + + return XT_HANDLED; + } + + /* Let's see if we can find out what this bytestream should be for... */ + + for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) + { + struct jabber_transfer *tft = tflist->data; + if( ( strcmp( tft->sid, sid ) == 0 ) ) + { + tf = tft; + break; + } + } + + if (!tf) + { + imcb_log( ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid ); + return XT_HANDLED; + } + + tf->ini_jid = g_strdup( ini_jid ); + tf->tgt_jid = g_strdup( tgt_jid ); + + jabber_bs_send_start( tf ); + + return XT_HANDLED; +} + +int jabber_si_send_request(struct im_connection *ic, char *who, struct jabber_transfer *tf ) +{ + struct xt_node *node, *sinode; + struct jabber_buddy *bud; + + /* who knows how many bits the future holds :) */ + char filesizestr[ 1 + ( int ) ( 0.301029995663981198f * sizeof( size_t ) * 8 ) ]; + + const char *methods[] = + { + XMLNS_BYTESTREAMS, + //XMLNS_IBB, + NULL + }; + const char **m; + char *s; + + /* Maybe we should hash this? */ + tf->sid = g_strdup_printf( "BitlBeeJabberSID%d", tf->ft->local_id ); + + if( ( s = strchr( who, '=' ) ) && jabber_chat_by_name( ic, s + 1 ) ) + bud = jabber_buddy_by_ext_jid( ic, who, 0 ); + else + bud = jabber_buddy_by_jid( ic, who, 0 ); + + /* start with the SI tag */ + sinode = xt_new_node( "si", NULL, NULL ); + xt_add_attr( sinode, "xmlns", XMLNS_SI ); + xt_add_attr( sinode, "profile", XMLNS_FILETRANSFER ); + xt_add_attr( sinode, "id", tf->sid ); + +/* if( mimetype ) + xt_add_attr( node, "mime-type", mimetype ); */ + + /* now the file tag */ +/* if( desc ) + node = xt_new_node( "desc", descr, NULL ); */ + node = xt_new_node( "range", NULL, NULL ); + + sprintf( filesizestr, "%zd", tf->ft->file_size ); + node = xt_new_node( "file", NULL, node ); + xt_add_attr( node, "xmlns", XMLNS_FILETRANSFER ); + xt_add_attr( node, "name", tf->ft->file_name ); + xt_add_attr( node, "size", filesizestr ); +/* if (hash) + xt_add_attr( node, "hash", hash ); + if (date) + xt_add_attr( node, "date", date ); */ + + xt_add_child( sinode, node ); + + /* and finally the feature tag */ + node = xt_new_node( "field", NULL, NULL ); + xt_add_attr( node, "var", "stream-method" ); + xt_add_attr( node, "type", "list-single" ); + + for ( m = methods ; *m ; m ++ ) + xt_add_child( node, xt_new_node( "option", NULL, xt_new_node( "value", (char *)*m, NULL ) ) ); + + node = xt_new_node( "x", NULL, node ); + xt_add_attr( node, "xmlns", XMLNS_XDATA ); + xt_add_attr( node, "type", "form" ); + + node = xt_new_node( "feature", NULL, node ); + xt_add_attr( node, "xmlns", XMLNS_FEATURE ); + + xt_add_child( sinode, node ); + + /* and we are there... */ + node = jabber_make_packet( "iq", "set", bud ? bud->full_jid : who, sinode ); + jabber_cache_add( ic, node, jabber_si_handle_response ); + + return jabber_write_packet( ic, node ); +} diff --git a/protocols/jabber/stream.c b/protocols/jabber/stream.c deleted file mode 100644 index c88a72fd..00000000 --- a/protocols/jabber/stream.c +++ /dev/null @@ -1,593 +0,0 @@ -/***************************************************************************\ -* * -* BitlBee - An IRC to IM gateway * -* Jabber module - stream handling * -* * -* Copyright 2007 Uli Meis * -* * -* 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 - -/* Some structs for the SOCKS5 handshake */ - -struct bs_handshake_data { - - struct jabber_transfer *tf; - - /* element and elements */ - struct xt_node *qnode, *shnode; - - enum - { - BS_PHASE_CONNECT, - BS_PHASE_CONNECTED, - BS_PHASE_REQUEST, - BS_PHASE_REPLY, - BS_PHASE_REPLY_HAVE_LEN - } phase; - - /* SHA1( SID + Initiator JID + Target JID) */ - char *pseudoadr; - - void (*parentfree) ( file_transfer_t *ft ); - - gint connect_timeout; -}; - -struct socks5_hdr -{ - unsigned char ver; - union - { - unsigned char cmd; - unsigned char rep; - } cmdrep; - unsigned char rsv; - unsigned char atyp; -}; - -struct socks5_message -{ - struct socks5_hdr hdr; - unsigned char addrlen; - unsigned char address[64]; -}; - -/* connect() timeout in seconds. */ -#define JABBER_BS_CONTIMEOUT 15 - -/* shouldn't matter if it's mostly too much, kernel's smart about that - * and will only reserve some address space */ -#define JABBER_BS_BUFSIZE 65536 - -gboolean jabber_bs_handshake( gpointer data, gint fd, b_input_condition cond ); - -gboolean jabber_bs_handshake_abort( struct bs_handshake_data *bhd, char *format, ... ); - -void jabber_bs_answer_request( struct bs_handshake_data *bhd ); - -gboolean jabber_bs_read( gpointer data, gint fd, b_input_condition cond ); - -void jabber_bs_out_of_data( file_transfer_t *ft ); - -void jabber_bs_canceled( file_transfer_t *ft , char *reason ); - - -void jabber_bs_free_transfer( file_transfer_t *ft) { - struct jabber_transfer *tf = ft->data; - struct bs_handshake_data *bhd = tf->streamhandle; - void (*parentfree) ( file_transfer_t *ft ); - - parentfree = bhd->parentfree; - - if ( tf->watch_in ) - b_event_remove( tf->watch_in ); - - if( tf->watch_out ) - b_event_remove( tf->watch_out ); - - g_free( bhd->pseudoadr ); - xt_free_node( bhd->qnode ); - g_free( bhd ); - - parentfree( ft ); -} - -/* - * Parses an incoming bytestream request and calls jabber_bs_handshake on success. - */ -int jabber_bs_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode) -{ - char *sid, *ini_jid, *tgt_jid, *mode, *iq_id; - struct jabber_data *jd = ic->proto_data; - struct jabber_transfer *tf = NULL; - GSList *tflist; - struct bs_handshake_data *bhd; - - sha1_state_t sha; - char hash_hex[41]; - unsigned char hash[20]; - int i; - - if( !(iq_id = xt_find_attr( node, "id" ) ) || - !(ini_jid = xt_find_attr( node, "from" ) ) || - !(tgt_jid = xt_find_attr( node, "to" ) ) || - !(sid = xt_find_attr( qnode, "sid" ) ) ) - { - imcb_log( ic, "WARNING: Received incomplete SI bytestream request"); - return XT_HANDLED; - } - - if( ( mode = xt_find_attr( qnode, "mode" ) ) && - ( strcmp( mode, "tcp" ) != 0 ) ) - { - imcb_log( ic, "WARNING: Received SI Request for unsupported bytestream mode %s", xt_find_attr( qnode, "mode" ) ); - return XT_HANDLED; - } - - /* Let's see if we can find out what this bytestream should be for... */ - - for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) - { - struct jabber_transfer *tft = tflist->data; - if( ( strcmp( tft->sid, sid ) == 0 ) && - ( strcmp( tft->ini_jid, ini_jid ) == 0 ) && - ( strcmp( tft->tgt_jid, tgt_jid ) == 0 ) ) - { - tf = tft; - break; - } - } - - if (!tf) - { - imcb_log( ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid ); - return XT_HANDLED; - } - - /* iq_id and canceled can be reused since SI is done */ - g_free( tf->iq_id ); - tf->iq_id = g_strdup( iq_id ); - - tf->ft->canceled = jabber_bs_canceled; - - /* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */ - sha1_init( &sha ); - sha1_append( &sha, (unsigned char*) sid, strlen( sid ) ); - sha1_append( &sha, (unsigned char*) ini_jid, strlen( ini_jid ) ); - sha1_append( &sha, (unsigned char*) tgt_jid, strlen( tgt_jid ) ); - sha1_finish( &sha, hash ); - - for( i = 0; i < 20; i ++ ) - sprintf( hash_hex + i * 2, "%02x", hash[i] ); - - bhd = g_new0( struct bs_handshake_data, 1 ); - bhd->tf = tf; - bhd->qnode = xt_dup( qnode ); - bhd->shnode = bhd->qnode->children; - bhd->phase = BS_PHASE_CONNECT; - bhd->pseudoadr = g_strdup( hash_hex ); - tf->streamhandle = bhd; - bhd->parentfree = tf->ft->free; - tf->ft->free = jabber_bs_free_transfer; - - jabber_bs_handshake( bhd, 0, 0 ); - - return XT_HANDLED; -} - -/* - * This function is scheduled in bs_handshake via b_timeout_add after a (non-blocking) connect(). - */ -gboolean jabber_bs_connect_timeout( gpointer data, gint fd, b_input_condition cond ) -{ - struct bs_handshake_data *bhd = data; - - bhd->connect_timeout = 0; - - jabber_bs_handshake_abort( bhd, "no connection after %d seconds", JABBER_BS_CONTIMEOUT ); - - return FALSE; -} - -/* - * This is what a protocol handshake can look like in cooperative multitasking :) - * Might be confusing at first because it's called from different places and is recursing. - * (places being the event thread, bs_request, bs_handshake_abort, and itself) - * - * All in all, it turned out quite nice :) - */ -gboolean jabber_bs_handshake( gpointer data, gint fd, b_input_condition cond ) -{ - -/* very useful */ -#define ASSERTSOCKOP(op, msg) \ - if( (op) == -1 ) \ - return jabber_bs_handshake_abort( bhd , msg ": %s", strerror( errno ) ); - - struct bs_handshake_data *bhd = data; - struct pollfd pfd = { .fd = fd, .events = POLLHUP|POLLERR }; - short revents; - - if ( bhd->connect_timeout ) - { - b_event_remove( bhd->connect_timeout ); - bhd->connect_timeout = 0; - } - - - /* we need the real io condition */ - if ( poll( &pfd, 1, 0 ) == -1 ) - { - imcb_log( bhd->tf->ic, "poll() failed, weird!" ); - revents = 0; - }; - - revents = pfd.revents; - - if( revents & POLLERR ) - { - int sockerror; - socklen_t errlen = sizeof( sockerror ); - - if ( getsockopt( fd, SOL_SOCKET, SO_ERROR, &sockerror, &errlen ) ) - return jabber_bs_handshake_abort( bhd, "getsockopt() failed, unknown socket error during SOCKS5 handshake (weird!)" ); - - if ( bhd->phase == BS_PHASE_CONNECTED ) - return jabber_bs_handshake_abort( bhd, "connect() failed: %s", strerror( sockerror ) ); - - return jabber_bs_handshake_abort( bhd, "Socket error during SOCKS5 handshake(weird!): %s", strerror( sockerror ) ); - } - - if( revents & POLLHUP ) - return jabber_bs_handshake_abort( bhd, "Remote end closed connection" ); - - - switch( bhd->phase ) - { - case BS_PHASE_CONNECT: - { - struct xt_node *c; - char *host, *port; - struct addrinfo hints, *rp; - - if( ( c = bhd->shnode = xt_find_node( bhd->shnode, "streamhost" ) ) && - ( port = xt_find_attr( c, "port" ) ) && - ( host = xt_find_attr( c, "host" ) ) && - xt_find_attr( c, "jid" ) ) - { - memset( &hints, 0, sizeof( struct addrinfo ) ); - hints.ai_socktype = SOCK_STREAM; - - if ( getaddrinfo( host, port, &hints, &rp ) != 0 ) - return jabber_bs_handshake_abort( bhd, "getaddrinfo() failed: %s", strerror( errno ) ); - - ASSERTSOCKOP( bhd->tf->fd = fd = socket( rp->ai_family, rp->ai_socktype, 0 ), "Opening socket" ); - - sock_make_nonblocking( fd ); - - imcb_log( bhd->tf->ic, "Transferring file %s: Connecting to streamhost %s:%s", bhd->tf->ft->file_name, host, port ); - - if( ( connect( fd, rp->ai_addr, rp->ai_addrlen ) == -1 ) && - ( errno != EINPROGRESS ) ) - return jabber_bs_handshake_abort( bhd , "connect() failed: %s", strerror( errno ) ); - - freeaddrinfo( rp ); - - bhd->phase = BS_PHASE_CONNECTED; - - bhd->tf->watch_out = b_input_add( fd, GAIM_INPUT_WRITE, jabber_bs_handshake, bhd ); - - /* since it takes forever(3mins?) till connect() fails on itself we schedule a timeout */ - bhd->connect_timeout = b_timeout_add( JABBER_BS_CONTIMEOUT * 1000, jabber_bs_connect_timeout, bhd ); - - bhd->tf->watch_in = 0; - return FALSE; - } else - return jabber_bs_handshake_abort( bhd, c ? "incomplete streamhost entry: host=%s port=%s jid=%s" : NULL, - host, port, xt_find_attr( c, "jid" ) ); - } - case BS_PHASE_CONNECTED: - { - struct { - unsigned char ver; - unsigned char nmethods; - unsigned char method; - } socks5_hello = { - .ver = 5, - .nmethods = 1, - .method = 0x00 /* no auth */ - /* one could also implement username/password. If you know - * a jabber client or proxy that actually does it, tell me. - */ - }; - - ASSERTSOCKOP( send( fd, &socks5_hello, sizeof( socks5_hello ) , 0 ), "Sending auth request" ); - - bhd->phase = BS_PHASE_REQUEST; - - bhd->tf->watch_in = b_input_add( fd, GAIM_INPUT_READ, jabber_bs_handshake, bhd ); - - bhd->tf->watch_out = 0; - return FALSE; - } - case BS_PHASE_REQUEST: - { - struct socks5_message socks5_connect = - { - .hdr = - { - .ver = 5, - .cmdrep.cmd = 0x01, - .rsv = 0, - .atyp = 0x03 - }, - .addrlen = strlen( bhd->pseudoadr ) - }; - int ret; - char buf[2]; - - /* If someone's trying to be funny and sends only one byte at a time we'll fail :) */ - ASSERTSOCKOP( ret = recv( fd, buf, 2, 0 ) , "Receiving auth reply" ); - - if( !( ret == 2 ) || - !( buf[0] == 5 ) || - !( buf[1] == 0 ) ) - return jabber_bs_handshake_abort( bhd, "Auth not accepted by streamhost (reply: len=%d, ver=%d, status=%d)", - ret, buf[0], buf[1] ); - - /* copy hash into connect message */ - memcpy( socks5_connect.address, bhd->pseudoadr, socks5_connect.addrlen ); - - /* after the address comes the port, which is always 0 */ - memset( socks5_connect.address + socks5_connect.addrlen, 0, sizeof( in_port_t ) ); - - ASSERTSOCKOP( send( fd, &socks5_connect, sizeof( struct socks5_hdr ) + 1 + socks5_connect.addrlen + sizeof( in_port_t ), 0 ) , "Sending SOCKS5 Connect" ); - - bhd->phase = BS_PHASE_REPLY; - - return TRUE; - } - case BS_PHASE_REPLY: - case BS_PHASE_REPLY_HAVE_LEN: - { - /* we have to wait till we have the address length, then we know how much data is left - * (not that we'd actually care about that data, but we need to eat it all up anyway) - */ - struct socks5_message socks5_reply; - int ret; - int expectedbytes = - sizeof( struct socks5_hdr ) + 1 + - ( bhd->phase == BS_PHASE_REPLY_HAVE_LEN ? socks5_reply.addrlen + sizeof( in_port_t ) : 0 ); - - /* notice the peek, we're doing this till enough is there */ - ASSERTSOCKOP( ret = recv( fd, &socks5_reply, expectedbytes, MSG_PEEK ) , "Peeking for SOCKS5 CONNECT reply" ); - - if ( ret == 0 ) - return jabber_bs_handshake_abort( bhd , "peer has shutdown connection" ); - - /* come again */ - if ( ret < expectedbytes ) - return TRUE; - - if ( bhd->phase == BS_PHASE_REPLY ) - { - if( !( socks5_reply.hdr.ver == 5 ) || - !( socks5_reply.hdr.cmdrep.rep == 0 ) || - !( socks5_reply.hdr.atyp == 3 ) || - !( socks5_reply.addrlen <= 62 ) ) /* should also be 40, but who cares as long as all fits in the buffer... */ - return jabber_bs_handshake_abort( bhd, "SOCKS5 CONNECT failed (reply: ver=%d, rep=%d, atyp=%d, addrlen=%d", - socks5_reply.hdr.ver, - socks5_reply.hdr.cmdrep.rep, - socks5_reply.hdr.atyp, - socks5_reply.addrlen); - - /* and again for the rest */ - bhd->phase = BS_PHASE_REPLY_HAVE_LEN; - - /* since it's very likely that the rest is there as well, - * let's not wait for the event loop to call us again */ - return jabber_bs_handshake( bhd , fd, 0 ); - } - - /* got it all, remove it from the queue */ - ASSERTSOCKOP( ret = recv( fd, &socks5_reply, expectedbytes, 0 ) , "Dequeueing MSG_PEEK'ed data after SOCKS5 CONNECT" ); - - /* this shouldn't happen */ - if ( ret < expectedbytes ) - return jabber_bs_handshake_abort( bhd, "internal error, couldn't dequeue MSG_PEEK'ed data after SOCKS5 CONNECT" ); - - /* we're actually done now... */ - - jabber_bs_answer_request( bhd ); - - bhd->tf->watch_in = 0; - return FALSE; - } - default: - /* BUG */ - imcb_log( bhd->tf->ic, "BUG in file transfer code: undefined handshake phase" ); - - bhd->tf->watch_in = 0; - return FALSE; - } -#undef ASSERTSOCKOP -#undef JABBER_BS_ERR_CONDS -} - -/* - * If the handshake failed we can try the next streamhost, if there is one. - * An intelligent sender would probably specify himself as the first streamhost and - * a proxy as the second (Kopete is an example here). That way, a (potentially) - * slow proxy is only used if neccessary. - */ -gboolean jabber_bs_handshake_abort( struct bs_handshake_data *bhd, char *format, ... ) -{ - struct jabber_transfer *tf = bhd->tf; - struct xt_node *reply, *iqnode; - - if( bhd->shnode ) - { - if( format ) { - va_list params; - va_start( params, format ); - char error[128]; - - if( vsnprintf( error, 128, format, params ) < 0 ) - sprintf( error, "internal error parsing error string (BUG)" ); - va_end( params ); - - imcb_log( tf->ic, "Transferring file %s: connection to streamhost %s:%s failed (%s)", - tf->ft->file_name, - xt_find_attr( bhd->shnode, "host" ), - xt_find_attr( bhd->shnode, "port" ), - error ); - } - - /* Alright, this streamhost failed, let's try the next... */ - bhd->phase = BS_PHASE_CONNECT; - bhd->shnode = bhd->shnode->next; - - /* the if is not neccessary but saves us one recursion */ - if( bhd->shnode ) - return jabber_bs_handshake( bhd, 0, 0 ); - } - - /* out of stream hosts */ - - iqnode = jabber_make_packet( "iq", "result", tf->ini_jid, NULL ); - reply = jabber_make_error_packet( iqnode, "item-not-found", "cancel" , "404" ); - xt_free_node( iqnode ); - - xt_add_attr( reply, "id", tf->iq_id ); - - if( !jabber_write_packet( tf->ic, reply ) ) - imcb_log( tf->ic, "WARNING: Error transmitting bytestream response" ); - xt_free_node( reply ); - - imcb_file_canceled( tf->ft, "couldn't connect to any streamhosts" ); - - bhd->tf->watch_in = 0; - return FALSE; -} - -/* - * After the SOCKS5 handshake succeeds we need to inform the initiator which streamhost we chose. - * If he is the streamhost himself, he might already know that. However, if it's a proxy, - * the initiator will have to make a connection himself. - */ -void jabber_bs_answer_request( struct bs_handshake_data *bhd ) -{ - struct jabber_transfer *tf = bhd->tf; - struct xt_node *reply; - - imcb_log( tf->ic, "Transferring file %s: established SOCKS5 connection to %s:%s", - tf->ft->file_name, - xt_find_attr( bhd->shnode, "host" ), - xt_find_attr( bhd->shnode, "port" ) ); - - tf->ft->data = tf; - tf->ft->started = time( NULL ); - tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_read, tf ); - tf->ft->out_of_data = jabber_bs_out_of_data; - - reply = xt_new_node( "streamhost-used", NULL, NULL ); - xt_add_attr( reply, "jid", xt_find_attr( bhd->shnode, "jid" ) ); - - reply = xt_new_node( "query", NULL, reply ); - xt_add_attr( reply, "xmlns", XMLNS_BYTESTREAMS ); - - reply = jabber_make_packet( "iq", "result", tf->ini_jid, reply ); - - xt_add_attr( reply, "id", tf->iq_id ); - - if( !jabber_write_packet( tf->ic, reply ) ) - imcb_file_canceled( tf->ft, "Error transmitting bytestream response" ); - xt_free_node( reply ); -} - -/* Reads till it is unscheduled or the receiver signifies an overflow. */ -gboolean jabber_bs_read( gpointer data, gint fd, b_input_condition cond ) -{ - int ret; - struct jabber_transfer *tf = data; - char *buffer = g_malloc( JABBER_BS_BUFSIZE ); - - if (tf->receiver_overflow) - { - if( tf->watch_in ) - { - /* should never happen, BUG */ - imcb_file_canceled( tf->ft, "Bug in jabber file transfer code: read while overflow is true. Please report" ); - return FALSE; - } - } - - ret = recv( fd, buffer, JABBER_BS_BUFSIZE, 0 ); - - if( ret == -1 ) - { - /* shouldn't actually happen */ - if( errno == EAGAIN ) - return TRUE; - - imcb_file_canceled( tf->ft, "Error reading tcp socket" ); /* , strerror( errnum ) */ - - return FALSE; - } - - /* that should be all */ - if( ret == 0 ) - return FALSE; - - tf->bytesread += ret; - - buffer = g_realloc( buffer, ret ); - - if ( ( tf->receiver_overflow = imcb_file_write( tf->ft, buffer, ret ) ) ) - { - /* wait for imcb to run out of data */ - tf->watch_in = 0; - return FALSE; - } - - - return TRUE; -} - -/* imcb callback that is invoked when it runs out of data. - * We reschedule jabber_bs_read here if neccessary. */ -void jabber_bs_out_of_data( file_transfer_t *ft ) -{ - struct jabber_transfer *tf = ft->data; - - tf->receiver_overflow = FALSE; - - if ( !tf->watch_in ) - tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_read, tf ); -} - -/* Bad luck */ -void jabber_bs_canceled( file_transfer_t *ft , char *reason ) -{ - struct jabber_transfer *tf = ft->data; - - imcb_log( tf->ic, "File transfer aborted: %s", reason ); -} diff --git a/protocols/nogaim.h b/protocols/nogaim.h index 8651754a..f17c5a1e 100644 --- a/protocols/nogaim.h +++ b/protocols/nogaim.h @@ -228,6 +228,9 @@ struct prpl { /* Mainly for AOL, since they think "Bung hole" == "Bu ngho le". *sigh* * - Most protocols will just want to set this to g_strcasecmp().*/ int (* handle_cmp) (const char *who1, const char *who2); + + /* Incoming transfer request */ + void (* transfer_request) (struct im_connection *, file_transfer_t *ft, char *handle ); }; /* im_api core stuff. */ -- cgit v1.2.3 From dce390357114e30a424106c99e49cef1e682e1af Mon Sep 17 00:00:00 2001 From: ulim Date: Tue, 4 Dec 2007 01:48:57 +0100 Subject: Send and receive seems to work now! Also adopted the new buffering strategy, only one buffer of 2k per transfer now. --- dcc.c | 207 ++++++++++------------------------------ dcc.h | 47 +-------- protocols/ft.h | 31 +++--- protocols/jabber/jabber.h | 3 +- protocols/jabber/s5bytestream.c | 137 ++++++++++++++------------ protocols/jabber/si.c | 23 ++--- 6 files changed, 160 insertions(+), 288 deletions(-) diff --git a/dcc.c b/dcc.c index a5c77ce8..24673085 100644 --- a/dcc.c +++ b/dcc.c @@ -60,16 +60,7 @@ unsigned int local_transfer_id=1; */ unsigned int receivedchunks=0, receiveddata=0; -/* - * If using DCC SEND AHEAD this value will be set before the first transfer starts. - * Not that in this case it degenerates to the maximum message size to send() and - * has nothing to do with packets. - */ -#ifdef DCC_SEND_AHEAD -int max_packet_size = DCC_PACKET_SIZE; -#else int max_packet_size = 0; -#endif static void dcc_finish( file_transfer_t *file ); static void dcc_close( file_transfer_t *file ); @@ -78,7 +69,7 @@ gboolean dcc_listen( dcc_file_transfer_t *df, struct sockaddr_storage **saddr_pt int dccs_send_request( struct dcc_file_transfer *df, char *user_nick, struct sockaddr_storage *saddr ); gboolean dccs_recv_start( file_transfer_t *ft ); gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond); -void dccs_recv_out_of_data( file_transfer_t *ft ); +gboolean dccs_recv_write_request( file_transfer_t *ft ); /* As defined in ft.h */ file_transfer_t *imcb_file_send_start( struct im_connection *ic, char *handle, char *file_name, size_t file_size ) @@ -101,12 +92,6 @@ void imcb_file_canceled( file_transfer_t *file, char *reason ) dcc_close( file ); } -/* As defined in ft.h */ -gboolean imcb_file_write( file_transfer_t *file, gpointer data, size_t data_size ) -{ - return dccs_send_write( file, data, data_size ); -} - /* As defined in ft.h */ gboolean imcb_file_recv_start( file_transfer_t *ft ) { @@ -138,6 +123,8 @@ file_transfer_t *dccs_send_start( struct im_connection *ic, char *user_nick, cha df = dcc_alloc_transfer( file_name, file_size, ic ); file = df->ft; + file->write = dccs_send_write; + file->sending = TRUE; /* listen and request */ if( !dcc_listen( df, &saddr ) || @@ -194,13 +181,8 @@ int dccs_send_request( struct dcc_file_transfer *df, char *user_nick, struct soc { struct sockaddr_in *saddr_ipv4 = ( struct sockaddr_in *) saddr; - /* - * this is so ridiculous. We're supposed to convert the address to - * host byte order!!! Let's exclude anyone running big endian just - * for the fun of it... - */ sprintf( ipaddr, "%d", - htonl( saddr_ipv4->sin_addr.s_addr ) ); + ntohl( saddr_ipv4->sin_addr.s_addr ) ); port = saddr_ipv4->sin_port; } else { @@ -226,9 +208,6 @@ int dccs_send_request( struct dcc_file_transfer *df, char *user_nick, struct soc g_free( cmd ); - /* message is sortof redundant cause the users client probably informs him about that. remove? */ - imcb_log( df->ic, "Transferring file %s: Chose local address %s for DCC connection", df->ft->file_name, ipaddr ); - return TRUE; } @@ -304,9 +283,11 @@ gboolean dcc_poll( dcc_file_transfer_t *df, int fd, short *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 ) { -#ifdef DCC_SEND_AHEAD /* * use twice the maximum segment size as a maximum for calls to send(). */ @@ -317,7 +298,6 @@ gboolean dcc_check_maxseg( dcc_file_transfer_t *df, int fd ) return dcc_abort( df, "getsockopt() failed" ); max_packet_size *= 2; } -#endif return TRUE; } @@ -353,9 +333,9 @@ gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond ) 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 ); @@ -399,89 +379,9 @@ gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond ) return FALSE; } -#ifndef DCC_SEND_AHEAD - /* reschedule writer if neccessary */ - if( file->bytes_transferred >= df->bytes_sent && - df->watch_out == 0 && - df->queued_bytes > 0 ) { - df->watch_out = b_input_add( fd, GAIM_INPUT_WRITE, dcc_send_proto, df ); - } -#endif return TRUE; } - if( revents & POLLOUT ) - { - struct dcc_buffer *dccb; - int ret; - char *msg; - - if( df->queued_bytes == 0 ) - { - /* shouldn't happen */ - imcb_log( df->ic, "WARNING: DCC SEND: write called with empty queue" ); - - df->watch_out = 0; - return FALSE; - } - - /* start where we left off */ - if( !( df->queued_buffers ) || - !( dccb = df->queued_buffers->data ) ) - return dcc_abort( df, "BUG in DCC SEND: queued data but no buffers" ); - - msg = dccb->b + df->buffer_pos; - - int msgsize = MIN( -#ifndef DCC_SEND_AHEAD - file->bytes_transferred + MAX_PACKET_SIZE - df->bytes_sent, -#else - max_packet_size, -#endif - dccb->len - df->buffer_pos ); - - if ( msgsize == 0 ) - { - df->watch_out = 0; - return FALSE; - } - - ASSERTSOCKOP( ret = send( fd, msg, msgsize, 0 ), "Sending data" ); - - if( ret == 0 ) - return dcc_abort( df, "Remote end closed connection" ); - - df->bytes_sent += ret; - df->queued_bytes -= ret; - df->buffer_pos += ret; - - if( df->buffer_pos == dccb->len ) - { - df->buffer_pos = 0; - df->queued_buffers = g_slist_remove( df->queued_buffers, dccb ); - g_free( dccb->b ); - g_free( dccb ); - } - - if( ( df->queued_bytes < DCC_QUEUE_THRESHOLD_LOW ) && file->out_of_data ) - file->out_of_data( file ); - - if( df->queued_bytes > 0 ) - { - /* Who knows how long the event loop cycle will take, - * let's just try to send some more now. */ -#ifndef DCC_SEND_AHEAD - if( df->bytes_sent < ( file->bytes_transferred + max_packet_size ) ) -#endif - return dccs_send_proto( df, fd, cond ); - } - - df->watch_out = 0; - return FALSE; - } - - /* Send buffer full, come back later */ - return TRUE; } @@ -508,7 +408,7 @@ gboolean dccs_recv_start( file_transfer_t *ft ) /* watch */ df->watch_out = b_input_add( df->fd, GAIM_INPUT_WRITE, dccs_recv_proto, df ); - ft->out_of_data = dccs_recv_out_of_data; + ft->write_request = dccs_recv_write_request; return TRUE; } @@ -537,16 +437,13 @@ gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond) if( revents & POLLIN ) { - char *buffer = g_malloc( 65536 ); int ret, done; - ASSERTSOCKOP( ret = recv( fd, buffer, 65536, 0 ), "Receiving" ); + ASSERTSOCKOP( ret = recv( fd, ft->buffer, sizeof( ft->buffer ), 0 ), "Receiving" ); if( ret == 0 ) return dcc_abort( df, "Remote end closed connection" ); - buffer = g_realloc( buffer, ret ); - df->bytes_sent += ret; done = df->bytes_sent >= ft->file_size; @@ -560,14 +457,11 @@ gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond) ASSERTSOCKOP( ackret = send( fd, &ack, 4, 0 ), "Sending DCC ACK" ); if ( ackret != 4 ) - return dcc_abort( df, "Error sending DCC ACK, sent %d instead of 4 bytes", ret ); + return dcc_abort( df, "Error sending DCC ACK, sent %d instead of 4 bytes", ackret ); } - if( !ft->write( df->ft, buffer, ret ) && !done ) - { - df->watch_in = 0; + if( !ft->write( df->ft, ft->buffer, ret ) ) return FALSE; - } if( done ) { @@ -578,50 +472,63 @@ gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond) return FALSE; } - return TRUE; + df->watch_in = 0; + return FALSE; } return TRUE; } -void dccs_recv_out_of_data( file_transfer_t *ft ) +gboolean dccs_recv_write_request( file_transfer_t *ft ) { dcc_file_transfer_t *df = ft->priv; - if( !df->watch_in ) - df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_recv_proto, df ); + 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. Note that the buffer MUST NOT be freed by the caller! - * We don't copy the buffer but put it in our queue. + * Incoming data. * - * */ -gboolean dccs_send_write( file_transfer_t *file, gpointer data, unsigned int data_size ) + */ +gboolean dccs_send_write( file_transfer_t *file, char *data, unsigned int data_len ) { dcc_file_transfer_t *df = file->priv; - struct dcc_buffer *dccb = g_new0( struct dcc_buffer, 1 ); + int ret; + + receivedchunks++; receiveddata += data_len; - receivedchunks++; receiveddata += data_size; + if( df->watch_out ) + return dcc_abort( df, "BUG: write() called while watching" ); - dccb->b = data; - dccb->len = data_size; + ASSERTSOCKOP( ret = send( df->fd, data, data_len, 0 ), "Sending data" ); - df->queued_buffers = g_slist_append( df->queued_buffers, dccb ); + if( ret == 0 ) + return dcc_abort( df, "Remote end closed connection" ); - df->queued_bytes += data_size; + /* 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( ( file->status & FT_STATUS_TRANSFERRING ) && -#ifndef DCC_SEND_AHEAD - ( file->bytes_transferred >= df->bytes_sent ) && -#endif - ( df->watch_out == 0 ) && - ( df->queued_bytes > 0 ) ) - { - df->watch_out = b_input_add( df->fd, GAIM_INPUT_WRITE, dccs_send_proto, df ); - } - - return df->queued_bytes > DCC_QUEUE_THRESHOLD_HIGH; + 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; } /* @@ -642,20 +549,6 @@ static void dcc_close( file_transfer_t *file ) if( df->watch_out ) b_event_remove( df->watch_out ); - if( df->queued_buffers ) - { - struct dcc_buffer *dccb; - GSList *gslist = df->queued_buffers; - - for( ; gslist ; gslist = g_slist_next( gslist ) ) - { - dccb = gslist->data; - g_free( dccb->b ); - g_free( dccb ); - } - g_slist_free( df->queued_buffers ); - } - df->ic->irc->file_transfers = g_slist_remove( df->ic->irc->file_transfers, file ); g_free( df ); diff --git a/dcc.h b/dcc.h index d5805a1f..e53823e7 100644 --- a/dcc.h +++ b/dcc.h @@ -43,33 +43,9 @@ #ifndef _DCC_H #define _DCC_H -/* don't wait for acknowledgments */ -#define DCC_SEND_AHEAD - -/* This multiplier specifies how many bytes we - * can go ahead within one event loop cycle. Notice that all in all, - * we can easily be more ahead if the event loop shoots often enough. - * (or the receiver processes slow enough) - * - * Setting this value too high will cause send buffer overflows. - */ -#define DCC_SEND_AHEAD_MUL 10 - -/* - * queue thresholds for the out of data and overflow conditions - */ -#define DCC_QUEUE_THRESHOLD_LOW 2048 -#define DCC_QUEUE_THRESHOLD_HIGH 65536 - -/* only used in non-ahead mode */ +/* Send an ACK after receiving this amount of data */ #define DCC_PACKET_SIZE 1024 -/* stores buffers handed over by IM protocols */ -struct dcc_buffer { - char *b; - int len; -}; - typedef struct dcc_file_transfer { struct im_connection *ic; @@ -87,25 +63,6 @@ typedef struct dcc_file_transfer { gint watch_in; /* readable */ gint watch_out; /* writable */ - /* - * The total number of queued bytes. The following equality should always hold: - * - * queued_bytes = sum(queued_buffers.len) - buffer_pos - */ - unsigned int queued_bytes; - - /* - * A list of dcc_buffer structures. - * These are provided by the protocols directly so that no copying is neccessary. - */ - GSList *queued_buffers; - - /* - * current position within the first buffer. - * Is non-null if the whole buffer couldn't be sent at once. - */ - int buffer_pos; - /* * The total amount of bytes that have been sent to the irc client. */ @@ -123,7 +80,7 @@ file_transfer_t *dccs_send_start( struct im_connection *ic, char *user_nick, cha void dcc_canceled( file_transfer_t *file, char *reason ); -gboolean dccs_send_write( file_transfer_t *file, gpointer data, unsigned int data_size ); +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/protocols/ft.h b/protocols/ft.h index d41eb6c1..d35580d0 100644 --- a/protocols/ft.h +++ b/protocols/ft.h @@ -26,6 +26,13 @@ #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, @@ -130,15 +137,24 @@ typedef struct file_transfer { void (*canceled) ( struct file_transfer *file, char *reason ); /* - * If set, called when the transfer queue is running empty and - * more data can be added. + * 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. */ - void (*out_of_data) ( struct file_transfer *file ); + 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. */ - gboolean (*write) (struct file_transfer *file, char *buffer, int len ); + char buffer[FT_BUFFER_SIZE]; } file_transfer_t; @@ -153,12 +169,5 @@ file_transfer_t *imcb_file_send_start( struct im_connection *ic, char *user_nick */ void imcb_file_canceled( file_transfer_t *file, char *reason ); -/* - * The given buffer is queued for transfer and MUST NOT be freed by the caller. - * When the method returns false the caller should not invoke this method again - * until out_of_data has been called. - */ -gboolean imcb_file_write( file_transfer_t *file, gpointer data, size_t data_size ); - gboolean imcb_file_recv_start( file_transfer_t *ft ); #endif diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h index cb52d396..45082fee 100644 --- a/protocols/jabber/jabber.h +++ b/protocols/jabber/jabber.h @@ -145,7 +145,6 @@ struct jabber_transfer int accepted; size_t bytesread, byteswritten; - int receiver_overflow; int fd; struct sockaddr_storage saddr; }; @@ -208,7 +207,7 @@ void jabber_si_free_transfer( file_transfer_t *ft); /* s5bytestream.c */ int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode); gboolean jabber_bs_send_start( struct jabber_transfer *tf ); -gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, int len ); +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 ); diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c index e2f32bd0..de173d19 100644 --- a/protocols/jabber/s5bytestream.c +++ b/protocols/jabber/s5bytestream.c @@ -71,8 +71,6 @@ struct socks5_message if( (op) == -1 ) \ return jabber_bs_abort( bt , msg ": %s", strerror( errno ) ); -#define JABBER_BS_BUFSIZE 65536 - gboolean jabber_bs_abort( struct bs_transfer *bt, char *format, ... ); void jabber_bs_canceled( file_transfer_t *ft , char *reason ); void jabber_bs_free_transfer( file_transfer_t *ft); @@ -82,7 +80,7 @@ gboolean jabber_bs_peek( struct bs_transfer *bt, void *buffer, int buflen ); void jabber_bs_recv_answer_request( struct bs_transfer *bt ); gboolean jabber_bs_recv_read( gpointer data, gint fd, b_input_condition cond ); -void jabber_bs_recv_out_of_data( file_transfer_t *ft ); +gboolean jabber_bs_recv_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); @@ -108,7 +106,7 @@ void jabber_bs_free_transfer( file_transfer_t *ft) { g_free( bt->pseudoadr ); xt_free_node( bt->qnode ); g_free( bt ); -//iq_id + jabber_si_free_transfer( ft ); } @@ -325,7 +323,7 @@ gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition con sock_make_nonblocking( fd ); - imcb_log( bt->tf->ic, "Transferring file %s: Connecting to streamhost %s:%s", bt->tf->ft->file_name, host, port ); + imcb_log( bt->tf->ic, "File %s: Connecting to streamhost %s:%s", bt->tf->ft->file_name, host, port ); if( ( connect( fd, rp->ai_addr, rp->ai_addrlen ) == -1 ) && ( errno != EINPROGRESS ) ) @@ -425,7 +423,6 @@ gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition con jabber_bs_recv_answer_request( bt ); - // reset in answer_request bt->tf->watch_in = 0; return FALSE; } default: @@ -440,8 +437,10 @@ gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition con /* * If the handshake failed we can try the next streamhost, if there is one. * An intelligent sender would probably specify himself as the first streamhost and - * a proxy as the second (Kopete is an example here). That way, a (potentially) - * slow proxy is only used if neccessary. + * 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 ) { @@ -493,15 +492,15 @@ void jabber_bs_recv_answer_request( struct bs_transfer *bt ) struct jabber_transfer *tf = bt->tf; struct xt_node *reply; - imcb_log( tf->ic, "Transferring file %s: established SOCKS5 connection to %s:%s", + imcb_log( tf->ic, "File %s: established SOCKS5 connection to %s:%s", tf->ft->file_name, xt_find_attr( bt->shnode, "host" ), xt_find_attr( bt->shnode, "port" ) ); tf->ft->data = tf; tf->ft->started = time( NULL ); - tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_recv_read, tf ); - tf->ft->out_of_data = jabber_bs_recv_out_of_data; + 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", xt_find_attr( bt->shnode, "jid" ) ); @@ -518,90 +517,107 @@ void jabber_bs_recv_answer_request( struct bs_transfer *bt ) xt_free_node( reply ); } -/* Reads till it is unscheduled or the receiver signifies an overflow. */ +/* + * 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 jabber_transfer *tf = data; - struct bs_transfer *bt = tf->streamhandle; - char *buffer = g_malloc( JABBER_BS_BUFSIZE ); + struct bs_transfer *bt = data; + struct jabber_transfer *tf = bt->tf; - if (tf->receiver_overflow) + if( fd != 0 ) /* called via event thread */ + { + tf->watch_in = 0; + ASSERTSOCKOP( ret = recv( fd, tf->ft->buffer, sizeof( tf->ft->buffer ), 0 ) , "Receiving" ); + } + else { - if( tf->watch_in ) + /* 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 ) ) { - /* should never happen, BUG */ - imcb_file_canceled( tf->ft, "Bug in jabber file transfer code: read while overflow is true. Please report" ); + tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_recv_read, bt ); return FALSE; } } - ASSERTSOCKOP( ret = recv( fd, buffer, JABBER_BS_BUFSIZE, 0 ) , "Receiving" ); - - /* that should be all */ + /* shouldn't happen since we know the file size */ if( ret == 0 ) - return FALSE; + return jabber_bs_abort( bt, "Remote end closed connection" ); tf->bytesread += ret; - buffer = g_realloc( buffer, ret ); + tf->ft->write( tf->ft, tf->ft->buffer, ret ); - if ( ( tf->receiver_overflow = imcb_file_write( tf->ft, buffer, ret ) ) ) - { - /* wait for imcb to run out of data */ - tf->watch_in = 0; - return FALSE; - } - - return TRUE; + return FALSE; } -/* imcb callback that is invoked when it runs out of data. - * We reschedule jabber_bs_read here if neccessary. */ -void jabber_bs_recv_out_of_data( file_transfer_t *ft ) +/* + * 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; - tf->receiver_overflow = FALSE; + if( tf->watch_in ) + { + imcb_file_canceled( ft, "BUG in jabber file transfer: write_request called when already watching for input" ); + return FALSE; + } + + jabber_bs_recv_read( tf->streamhandle, 0 , 0 ); - if ( !tf->watch_in ) - tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_recv_read, tf ); + return TRUE; } -/* signal ood and be done */ +/* + * 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->ft->out_of_data( bt->tf->ft ); - bt->tf->watch_out = 0; + + bt->tf->ft->write_request( bt->tf->ft ); + return FALSE; } -/* try to send the stuff. If you can't return false and wait for writable */ -gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, int len ) +/* + * 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 ( ( ( ret = send( tf->fd, buffer, len, 0 ) ) == -1 ) && - ( errno != EAGAIN ) ) - return jabber_bs_abort( bt, "send failed on socket with: %s", strerror( errno ) ); + if( tf->watch_out ) + return jabber_bs_abort( bt, "BUG: write() called while watching " ); - if( ret == 0 ) - return jabber_bs_abort( bt, "Remote end closed connection" ); + ASSERTSOCKOP( ret = send( tf->fd, buffer, len, 0 ), "Sending" ); + + tf->byteswritten += ret; - if( ret == -1 ) - { - bt->tf->watch_out = b_input_add( tf->fd, GAIM_INPUT_WRITE, jabber_bs_send_can_write, bt ); - return FALSE; - } + /* TODO: this should really not be fatal */ + if( ret < len ) + return jabber_bs_abort( bt, "send() sent %d instead of %d (send buffer too big!)", ret, len ); + + bt->tf->watch_out = b_input_add( tf->fd, GAIM_INPUT_WRITE, jabber_bs_send_can_write, bt ); return TRUE; } +/* + * Handles the reply by the receiver containing the used streamhost. + */ static xt_status jabber_bs_send_handle_reply(struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) { struct jabber_transfer *tf = NULL; struct jabber_data *jd = ic->proto_data; @@ -650,12 +666,11 @@ static xt_status jabber_bs_send_handle_reply(struct im_connection *ic, struct xt if( bt->phase == BS_PHASE_REPLY ) { + /* handshake went through, let's start transferring */ tf->ft->started = time( NULL ); - tf->ft->out_of_data( tf->ft ); + tf->ft->write_request( tf->ft ); } - //bt->tf->watch_out = b_input_add( tf->fd, GAIM_INPUT_WRITE, jabber_bs_send_write, tf ); - return XT_HANDLED; } @@ -680,8 +695,6 @@ gboolean jabber_bs_send_start( struct jabber_transfer *tf ) bt = g_new0( struct bs_transfer, 1 ); bt->tf = tf; - //bt->qnode = xt_dup( qnode ); - //bt->shnode = bt->qnode->children; bt->phase = BS_PHASE_CONNECT; bt->pseudoadr = g_strdup( hash_hex ); tf->streamhandle = bt; @@ -714,8 +727,6 @@ gboolean jabber_bs_send_request( struct jabber_transfer *tf, char *host, char *p iq = jabber_make_packet( "iq", "set", tf->tgt_jid, query ); xt_add_attr( iq, "from", tf->ini_jid ); - //xt_free_node( query ); - jabber_cache_add( tf->ic, iq, jabber_bs_send_handle_reply ); if( !jabber_write_packet( tf->ic, iq ) ) @@ -884,11 +895,13 @@ gboolean jabber_bs_send_handshake( gpointer data, gint fd, b_input_condition con bt->phase = BS_PHASE_REPLY; - /* don't start sending till the streamhost-used message comes in */ + 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->started = time( NULL ); - tf->ft->out_of_data( tf->ft ); + tf->ft->write_request( tf->ft ); } tf->watch_in = 0; diff --git a/protocols/jabber/si.c b/protocols/jabber/si.c index 598cbd03..ffde6418 100644 --- a/protocols/jabber/si.c +++ b/protocols/jabber/si.c @@ -83,7 +83,7 @@ void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, struct jabber_transfer *tf; struct jabber_data *jd = ic->proto_data; - imcb_log( ic, "Incoming file from %s : %s %zd bytes", ic->irc->nick, ft->file_name, ft->file_size ); + 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 ); @@ -223,7 +223,7 @@ int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, st } /* - * imcb called the accept callback which probably means that the user accepted this file transfer. + * 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) @@ -274,11 +274,10 @@ void jabber_si_answer_request( file_transfer_t *ft ) { 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; + char *ini_jid, *tgt_jid, *iq_id; GSList *tflist; struct jabber_transfer *tf=NULL; struct jabber_data *jd = ic->proto_data; - char *sid; if( !( tgt_jid = xt_find_attr( node, "from" ) ) || !( ini_jid = xt_find_attr( node, "to" ) ) ) @@ -287,11 +286,10 @@ static xt_status jabber_si_handle_response(struct im_connection *ic, struct xt_n return XT_HANDLED; } - imcb_log( ic, "GOT RESPONSE TO FILE" ); /* All this means we expect something like this: ( I think ) - * + * * - * + * [ ] <-- not neccessary * * * @@ -299,11 +297,11 @@ static xt_status jabber_si_handle_response(struct im_connection *ic, struct xt_n */ 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 ) || - !( sid = xt_find_attr( c, "id" ) )|| - !( d = xt_find_node( c->children, "file" ) ) || - !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_FILETRANSFER ) == 0 ) || +/* !( d = xt_find_node( c->children, "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" ) ) || @@ -330,7 +328,7 @@ static xt_status jabber_si_handle_response(struct im_connection *ic, struct xt_n for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) { struct jabber_transfer *tft = tflist->data; - if( ( strcmp( tft->sid, sid ) == 0 ) ) + if( ( strcmp( tft->iq_id, iq_id ) == 0 ) ) { tf = tft; break; @@ -346,6 +344,8 @@ static xt_status jabber_si_handle_response(struct im_connection *ic, struct xt_n 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; @@ -422,6 +422,7 @@ int jabber_si_send_request(struct im_connection *ic, char *who, struct jabber_tr /* 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 ); } -- cgit v1.2.3 From 793cc254ad2479d95d00266d6cb7ab2bcd158834 Mon Sep 17 00:00:00 2001 From: ulim Date: Tue, 4 Dec 2007 02:21:45 +0100 Subject: Follow rename of jabber_chat_by_jid from upstrem --- protocols/jabber/si.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/jabber/si.c b/protocols/jabber/si.c index ffde6418..0b94f81b 100644 --- a/protocols/jabber/si.c +++ b/protocols/jabber/si.c @@ -371,7 +371,7 @@ int jabber_si_send_request(struct im_connection *ic, char *who, struct jabber_tr /* Maybe we should hash this? */ tf->sid = g_strdup_printf( "BitlBeeJabberSID%d", tf->ft->local_id ); - if( ( s = strchr( who, '=' ) ) && jabber_chat_by_name( ic, s + 1 ) ) + 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 ); -- cgit v1.2.3 From dc0ba9c85539533349353713162f94077fb27be3 Mon Sep 17 00:00:00 2001 From: kenobi Date: Tue, 18 Dec 2007 03:07:59 +0100 Subject: sending via proxy --- dcc.c | 1 - protocols/jabber/iq.c | 162 +++++++++++++++++++++- protocols/jabber/jabber.h | 16 ++- protocols/jabber/s5bytestream.c | 298 ++++++++++++++++++++++++++++++---------- protocols/jabber/si.c | 10 ++ 5 files changed, 410 insertions(+), 77 deletions(-) diff --git a/dcc.c b/dcc.c index 24673085..2fa3f745 100644 --- a/dcc.c +++ b/dcc.c @@ -124,7 +124,6 @@ file_transfer_t *dccs_send_start( struct im_connection *ic, char *user_nick, cha df = dcc_alloc_transfer( file_name, file_size, ic ); file = df->ft; file->write = dccs_send_write; - file->sending = TRUE; /* listen and request */ if( !dcc_listen( df, &saddr ) || diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c index 8cf6c7f1..8bd1111a 100644 --- a/protocols/jabber/iq.c +++ b/protocols/jabber/iq.c @@ -80,7 +80,7 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ) strftime( buf, sizeof( buf ) - 1, "%Z", localtime( &time_ep ) ); xt_add_child( reply, xt_new_node( "tz", buf, NULL ) ); } - else if( strcmp( s, XMLNS_DISCOVER ) == 0 ) + else if( strcmp( s, XMLNS_DISCO_INFO ) == 0 ) { const char *features[] = { XMLNS_VERSION, XMLNS_TIME, @@ -564,3 +564,163 @@ 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, "Couldnt find the man: %s", bare_jid); + return 0; + } + + if( bud->features ) /* been here already */ + return XT_HANDLED; + + node = xt_new_node( "query", NULL, NULL ); + xt_add_attr( node, "xmlns", XMLNS_DISCO_INFO ); + + if( !( query = jabber_make_packet( "iq", "get", bare_jid, node ) ) ) + { + imcb_log( ic, "WARNING: Couldn't generate feature query" ); + xt_free_node( node ); + } + + jabber_cache_add( ic, query, jabber_iq_parse_features ); + + return jabber_write_packet( ic, query ); +} + +xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ + struct xt_node *c; + struct jabber_buddy *bud; + char *feature; + + if( !( c = xt_find_node( node->children, "query" ) ) || + !( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_DISCO_INFO ) == 0 ) ) + { + imcb_log( ic, "WARNING: Received incomplete IQ-result packet for discover" ); + return XT_HANDLED; + } + if( ( bud = jabber_buddy_by_jid( ic, xt_find_attr( node, "from") , 0 ) ) == NULL ) + { + /* Who cares about the unknown... */ + imcb_log( ic, "Couldnt find the man: %s", xt_find_attr( node, "from")); + return 0; + } + + c = c->children; + while( ( c = xt_find_node( c, "feature" ) ) ) { + feature = xt_find_attr( c, "var" ); + bud->features = g_slist_append(bud->features, g_strdup(feature) ); + c = c->next; + } + + return XT_HANDLED; +} + +xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); + +xt_status jabber_iq_query_server( struct im_connection *ic, char *jid, char *xmlns ) +{ + struct xt_node *node, *query; + struct jabber_data *jd = ic->proto_data; + + node = xt_new_node( "query", NULL, NULL ); + xt_add_attr( node, "xmlns", xmlns ); + + if( !( query = jabber_make_packet( "iq", "get", jid, node ) ) ) + { + imcb_log( ic, "WARNING: Couldn't generate server query" ); + xt_free_node( node ); + } + + jd->have_streamhosts--; + jabber_cache_add( ic, query, jabber_iq_parse_server_features ); + + return jabber_write_packet( ic, query ); +} + +/* + * Query the server for "items", query each "item" for identities, query each "item" that's a proxy for it's bytestream info + */ +xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ + struct xt_node *c; + struct jabber_data *jd = ic->proto_data; + + if( !( c = xt_find_node( node->children, "query" ) ) || + !xt_find_attr( node, "from" ) ) + { + imcb_log( ic, "WARNING: Received incomplete IQ-result packet for discover" ); + return XT_HANDLED; + } + + jd->have_streamhosts++; + + if( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_DISCO_ITEMS ) == 0 ) + { + char *item, *itemjid; + + /* answer from server */ + + c = c->children; + while( ( c = xt_find_node( c, "item" ) ) ) + { + item = xt_find_attr( c, "name" ); + itemjid = xt_find_attr( c, "jid" ); + + jabber_iq_query_server( ic, itemjid, XMLNS_DISCO_INFO ); + + c = c->next; + } + } else if( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_DISCO_INFO ) == 0 ) + { + char *category, *type; + + /* answer from potential proxy */ + + c = c->children; + while( ( c = xt_find_node( c, "identity" ) ) ) + { + category = xt_find_attr( c, "category" ); + type = xt_find_attr( c, "type" ); + + if( type && ( strcmp( type, "bytestreams" ) == 0 ) && + category && ( strcmp( category, "proxy" ) == 0 ) ) + jabber_iq_query_server( ic, xt_find_attr( node, "from" ), XMLNS_BYTESTREAMS ); + + c = c->next; + } + } else if( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_BYTESTREAMS ) == 0 ) + { + char *host, *jid; + int port; + + /* answer from proxy */ + + if( ( c = xt_find_node( c->children, "streamhost" ) ) && + ( host = xt_find_attr( c, "host" ) ) && + ( port = atoi( xt_find_attr( c, "port" ) ) ) && + ( jid = xt_find_attr( c, "jid" ) ) ) + { + jabber_streamhost_t *sh = g_new0( jabber_streamhost_t, 1 ); + sh->jid = g_strdup( jid ); + sh->host = g_strdup( host ); + sprintf( sh->port, "%u", port ); + + imcb_log( ic, "Proxy found: jid %s host %s port %u", jid, host, port ); + jd->streamhosts = g_slist_append( jd->streamhosts, sh ); + } + } + + if( jd->have_streamhosts == 0 ) + jd->have_streamhosts++; + return XT_HANDLED; +} diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h index c518f541..9edb844e 100644 --- a/protocols/jabber/jabber.h +++ b/protocols/jabber/jabber.h @@ -56,6 +56,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; + struct jabber_data { struct im_connection *ic; @@ -82,6 +90,8 @@ struct jabber_data GHashTable *buddies; GSList *filetransfers; + GSList *streamhosts; + int have_streamhosts; }; struct jabber_away_state @@ -110,6 +120,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; @@ -181,7 +192,8 @@ struct jabber_transfer #define XMLNS_DELAY "jabber:x:delay" /* XEP-0091 */ #define XMLNS_XDATA "jabber:x:data" /* XEP-0004 */ #define XMLNS_CHATSTATES "http://jabber.org/protocol/chatstates" /* XEP-0085 */ -#define XMLNS_DISCOVER "http://jabber.org/protocol/disco#info" /* XEP-0030 */ +#define XMLNS_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_FEATURE "http://jabber.org/protocol/feature-neg" /* XEP-0020 */ @@ -198,6 +210,8 @@ 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 ); diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c index de173d19..4770feda 100644 --- a/protocols/jabber/s5bytestream.c +++ b/protocols/jabber/s5bytestream.c @@ -29,8 +29,8 @@ struct bs_transfer { struct jabber_transfer *tf; - /* element and elements */ - struct xt_node *qnode, *shnode; + jabber_streamhost_t *sh; + GSList *streamhosts; enum { @@ -73,7 +73,7 @@ struct socks5_message 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); +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 ); @@ -83,12 +83,14 @@ 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); +int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode ); gboolean jabber_bs_send_handshake_abort( struct bs_transfer *bt, char *error ); -gboolean jabber_bs_send_request( struct jabber_transfer *tf, char *host, char *port ); +gboolean jabber_bs_send_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 @@ -96,6 +98,7 @@ gboolean jabber_bs_send_listen( struct bs_transfer *bt, struct sockaddr_storage void jabber_bs_free_transfer( file_transfer_t *ft) { struct jabber_transfer *tf = ft->data; struct bs_transfer *bt = tf->streamhandle; + jabber_streamhost_t *sh; if ( tf->watch_in ) b_event_remove( tf->watch_in ); @@ -104,12 +107,25 @@ void jabber_bs_free_transfer( file_transfer_t *ft) { b_event_remove( tf->watch_out ); g_free( bt->pseudoadr ); - xt_free_node( bt->qnode ); + + 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; @@ -146,6 +162,10 @@ gboolean jabber_bs_connect_timeout( gpointer data, gint fd, b_input_condition co 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 }; @@ -180,6 +200,9 @@ gboolean jabber_bs_poll( struct bs_transfer *bt, int fd, short *revents ) return TRUE; } +/* + * Used for receive and send path. + */ gboolean jabber_bs_abort( struct bs_transfer *bt, char *format, ... ) { va_list params; @@ -190,9 +213,9 @@ gboolean jabber_bs_abort( struct bs_transfer *bt, char *format, ... ) sprintf( error, "internal error parsing error string (BUG)" ); va_end( params ); if( bt->tf->ft->sending ) - return jabber_bs_recv_handshake_abort( bt, error ); - else return jabber_bs_send_handshake_abort( bt, error ); + else + return jabber_bs_recv_handshake_abort( bt, error ); } /* Bad luck */ @@ -213,6 +236,8 @@ int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, stru 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]; @@ -235,6 +260,30 @@ int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, stru 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) ) @@ -273,8 +322,8 @@ int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, stru bt = g_new0( struct bs_transfer, 1 ); bt->tf = tf; - bt->qnode = xt_dup( qnode ); - bt->shnode = bt->qnode->children; + bt->streamhosts = shlist; + bt->sh = shlist->data; bt->phase = BS_PHASE_CONNECT; bt->pseudoadr = g_strdup( hash_hex ); tf->streamhandle = bt; @@ -284,6 +333,7 @@ int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, stru 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. @@ -304,45 +354,35 @@ gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition con { case BS_PHASE_CONNECT: { - struct xt_node *c; - char *host, *port; struct addrinfo hints, *rp; - if( ( c = bt->shnode = xt_find_node( bt->shnode, "streamhost" ) ) && - ( port = xt_find_attr( c, "port" ) ) && - ( host = xt_find_attr( c, "host" ) ) && - xt_find_attr( c, "jid" ) ) - { - memset( &hints, 0, sizeof( struct addrinfo ) ); - hints.ai_socktype = SOCK_STREAM; + memset( &hints, 0, sizeof( struct addrinfo ) ); + hints.ai_socktype = SOCK_STREAM; - if ( getaddrinfo( host, port, &hints, &rp ) != 0 ) - return jabber_bs_abort( bt, "getaddrinfo() failed: %s", strerror( errno ) ); + if ( getaddrinfo( bt->sh->host, bt->sh->port, &hints, &rp ) != 0 ) + return jabber_bs_abort( bt, "getaddrinfo() failed: %s", strerror( errno ) ); - ASSERTSOCKOP( bt->tf->fd = fd = socket( rp->ai_family, rp->ai_socktype, 0 ), "Opening socket" ); + ASSERTSOCKOP( bt->tf->fd = fd = socket( rp->ai_family, rp->ai_socktype, 0 ), "Opening socket" ); - sock_make_nonblocking( fd ); + sock_make_nonblocking( fd ); - imcb_log( bt->tf->ic, "File %s: Connecting to streamhost %s:%s", bt->tf->ft->file_name, host, port ); + 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 ) ); + if( ( connect( fd, rp->ai_addr, rp->ai_addrlen ) == -1 ) && + ( errno != EINPROGRESS ) ) + return jabber_bs_abort( bt , "connect() failed: %s", strerror( errno ) ); - freeaddrinfo( rp ); + freeaddrinfo( rp ); - bt->phase = BS_PHASE_CONNECTED; - - bt->tf->watch_out = b_input_add( fd, GAIM_INPUT_WRITE, jabber_bs_recv_handshake, bt ); + 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 ); + /* since it takes forever(3mins?) till connect() fails on itself we schedule a timeout */ + bt->connect_timeout = b_timeout_add( JABBER_BS_CONTIMEOUT * 1000, jabber_bs_connect_timeout, bt ); - bt->tf->watch_in = 0; - return FALSE; - } else - return jabber_bs_abort( bt, c ? "incomplete streamhost entry: host=%s port=%s jid=%s" : NULL, - host, port, xt_find_attr( c, "jid" ) ); + bt->tf->watch_in = 0; + return FALSE; } case BS_PHASE_CONNECTED: { @@ -421,7 +461,10 @@ gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition con socks5_reply.atyp, socks5_reply.addrlen); - jabber_bs_recv_answer_request( bt ); + if( bt->tf->ft->sending ) + jabber_bs_send_activate( bt ); + else + jabber_bs_recv_answer_request( bt ); return FALSE; } @@ -446,24 +489,24 @@ 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 ); - if( bt->shnode ) + /* 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 ) { - imcb_log( tf->ic, "Transferring file %s: connection to streamhost %s:%s failed (%s)", - tf->ft->file_name, - xt_find_attr( bt->shnode, "host" ), - xt_find_attr( bt->shnode, "port" ), - error ); - - /* Alright, this streamhost failed, let's try the next... */ - bt->phase = BS_PHASE_CONNECT; - bt->shnode = bt->shnode->next; - - /* the if is not neccessary but saves us one recursion */ - if( bt->shnode ) - return jabber_bs_recv_handshake( bt, 0, 0 ); + bt->sh = shlist->next->data; + return jabber_bs_recv_handshake( bt, 0, 0 ); } + /* out of stream hosts */ iqnode = jabber_make_packet( "iq", "result", tf->ini_jid, NULL ); @@ -494,16 +537,15 @@ void jabber_bs_recv_answer_request( struct bs_transfer *bt ) imcb_log( tf->ic, "File %s: established SOCKS5 connection to %s:%s", tf->ft->file_name, - xt_find_attr( bt->shnode, "host" ), - xt_find_attr( bt->shnode, "port" ) ); + bt->sh->host, + bt->sh->port ); tf->ft->data = tf; - tf->ft->started = time( NULL ); tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_recv_read, bt ); tf->ft->write_request = jabber_bs_recv_write_request; reply = xt_new_node( "streamhost-used", NULL, NULL ); - xt_add_attr( reply, "jid", xt_find_attr( bt->shnode, "jid" ) ); + xt_add_attr( reply, "jid", bt->sh->jid ); reply = xt_new_node( "query", NULL, reply ); xt_add_attr( reply, "xmlns", XMLNS_BYTESTREAMS ); @@ -550,6 +592,9 @@ gboolean jabber_bs_recv_read( gpointer data, gint fd, b_input_condition cond ) if( ret == 0 ) return jabber_bs_abort( bt, "Remote end closed connection" ); + if( tf->bytesread == 0 ) + tf->ft->started = time( NULL ); + tf->bytesread += ret; tf->ft->write( tf->ft, tf->ft->buffer, ret ); @@ -602,8 +647,12 @@ gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, unsigned int l if( tf->watch_out ) return jabber_bs_abort( bt, "BUG: write() called while watching " ); + /* TODO: catch broken pipe */ ASSERTSOCKOP( ret = send( tf->fd, buffer, len, 0 ), "Sending" ); + if( tf->byteswritten == 0 ) + tf->ft->started = time( NULL ); + tf->byteswritten += ret; /* TODO: this should really not be fatal */ @@ -664,24 +713,110 @@ static xt_status jabber_bs_send_handle_reply(struct im_connection *ic, struct xt tf->accepted = TRUE; - if( bt->phase == BS_PHASE_REPLY ) + if( strcmp( jid, tf->ini_jid ) == 0 ) { - /* handshake went through, let's start transferring */ - tf->ft->started = time( NULL ); - tf->ft->write_request( tf->ft ); + /* we're streamhost and target */ + if( bt->phase == BS_PHASE_REPLY ) + { + /* handshake went through, let's start transferring */ + tf->ft->write_request( tf->ft ); + } + } else + { + /* using a proxy */ + GSList *shlist; + for( shlist = jd->streamhosts ; shlist ; shlist = g_slist_next( shlist ) ) + { + jabber_streamhost_t *sh = shlist->data; + if( strcmp( sh->jid, jid ) == 0 ) + { + bt->sh = sh; + jabber_bs_recv_handshake( bt, 0, 0 ); + return XT_HANDLED; + } + } + + imcb_log( ic, "WARNING: Received SOCKS5 bytestream reply with unknown streamhost %s", jid ); } return XT_HANDLED; } +/* + * Tell the proxy to activate the stream. Looks like this: + * + * + * + * tgt_jid + * + * + */ +void jabber_bs_send_activate( struct bs_transfer *bt ) +{ + struct xt_node *node; + + node = xt_new_node( "activate", bt->tf->tgt_jid, NULL ); + node = xt_new_node( "query", NULL, node ); + xt_add_attr( node, "xmlns", XMLNS_BYTESTREAMS ); + xt_add_attr( node, "sid", bt->tf->sid ); + node = jabber_make_packet( "iq", "set", bt->sh->jid, node ); + + jabber_cache_add( bt->tf->ic, node, jabber_bs_send_handle_activate ); + + jabber_write_packet( bt->tf->ic, node ); +} + +/* + * The proxy has activated the bytestream. + * We can finally start pushing some data out. + */ +static xt_status jabber_bs_send_handle_activate( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ + char *sid; + GSList *tflist; + struct jabber_transfer *tf; + struct xt_node *query; + struct jabber_data *jd = ic->proto_data; + + query = xt_find_node( orig->children, "query" ); + sid = xt_find_attr( query, "sid" ); + + for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) + { + struct jabber_transfer *tft = tflist->data; + if( ( strcmp( tft->sid, sid ) == 0 ) ) + { + tf = tft; + break; + } + } + + if( !tf ) + { + imcb_log( ic, "WARNING: Received SOCKS5 bytestream activation for unknown stream" ); + return XT_HANDLED; + } + + /* handshake went through, let's start transferring */ + tf->ft->write_request( tf->ft ); + + return XT_HANDLED; +} + +/* + * Starts a bytestream. + */ gboolean jabber_bs_send_start( struct jabber_transfer *tf ) { - char host[INET6_ADDRSTRLEN], port[6]; + char host[INET6_ADDRSTRLEN]; struct bs_transfer *bt; sha1_state_t sha; char hash_hex[41]; unsigned char hash[20]; - int i; + int i,ret; + struct jabber_data *jd = tf->ic->proto_data; + jabber_streamhost_t sh; + GSList *streamhosts = jd->streamhosts; /* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */ sha1_init( &sha ); @@ -701,28 +836,43 @@ gboolean jabber_bs_send_start( struct jabber_transfer *tf ) tf->ft->free = jabber_bs_free_transfer; tf->ft->canceled = jabber_bs_canceled; - if ( !jabber_bs_send_listen( bt, &tf->saddr, host, port ) ) + if ( !jabber_bs_send_listen( bt, &tf->saddr, sh.host = host, sh.port ) ) return FALSE; bt->tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_send_handshake, bt ); bt->connect_timeout = b_timeout_add( JABBER_BS_LISTEN_TIMEOUT * 1000, jabber_bs_connect_timeout, bt ); - return jabber_bs_send_request( tf, host, port ); + + sh.jid = tf->ini_jid; + + /* temporarily add listen address to streamhosts, send the request and remove it */ + streamhosts = g_slist_prepend( streamhosts, &sh ); + ret = jabber_bs_send_request( tf, streamhosts); + streamhosts = g_slist_remove( streamhosts, &sh ); + + return ret; } -gboolean jabber_bs_send_request( struct jabber_transfer *tf, char *host, char *port ) +gboolean jabber_bs_send_request( struct jabber_transfer *tf, GSList *streamhosts ) { - struct xt_node *sh, *query, *iq; - - sh = xt_new_node( "streamhost", NULL, NULL ); - xt_add_attr( sh, "jid", tf->ini_jid ); - xt_add_attr( sh, "host", host ); - xt_add_attr( sh, "port", port ); + 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" ); - xt_add_child( query, sh ); + + 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 ); @@ -738,6 +888,7 @@ gboolean jabber_bs_send_handshake_abort(struct bs_transfer *bt, char *error ) { struct jabber_transfer *tf = bt->tf; + /* TODO: did the receiver get here somehow??? */ imcb_log( tf->ic, "Transferring file %s: SOCKS5 handshake failed: %s", tf->ft->file_name, error ); @@ -900,7 +1051,6 @@ gboolean jabber_bs_send_handshake( gpointer data, gint fd, b_input_condition con if( tf->accepted ) { /* streamhost-used message came already in(possible?), let's start sending */ - tf->ft->started = time( NULL ); tf->ft->write_request( tf->ft ); } diff --git a/protocols/jabber/si.c b/protocols/jabber/si.c index 0b94f81b..1d649da3 100644 --- a/protocols/jabber/si.c +++ b/protocols/jabber/si.c @@ -82,6 +82,7 @@ void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, { struct jabber_transfer *tf; struct jabber_data *jd = ic->proto_data; + char *server = jd->server; imcb_log( ic, "Trying to send %s(%zd bytes) to %s", ft->file_name, ft->file_size, who ); @@ -96,8 +97,17 @@ void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, jd->filetransfers = g_slist_prepend( jd->filetransfers, tf ); + /* query the buddy's features */ + jabber_iq_query_features( ic, who ); + + /* query proxies from the server */ + if( !jd->have_streamhosts ) + jabber_iq_query_server( ic, server, XMLNS_DISCO_ITEMS ); + + /* send the request to our buddy */ jabber_si_send_request( ic, who, tf ); + /* and start the receive logic */ imcb_file_recv_start( ft ); } -- cgit v1.2.3 From eded1f703a8f5d2272b9d294d8e3dfb48fa302b4 Mon Sep 17 00:00:00 2001 From: kenobi Date: Wed, 19 Dec 2007 00:59:35 +0100 Subject: Merged in 280..288 from upstream (e.g. PING) --- account.c | 7 ++++++- doc/CREDITS | 1 + doc/user-guide/commands.xml | 12 ++++++------ doc/user-guide/quickstart.xml | 4 ++-- irc_commands.c | 2 +- lib/misc.c | 10 ++++++++++ lib/misc.h | 2 ++ lib/ssl_gnutls.c | 6 ++++-- nick.c | 19 ++++++++++++++----- protocols/jabber/conference.c | 21 +++++++++++++++++++++ protocols/jabber/io.c | 4 ++-- protocols/jabber/iq.c | 12 +++++++++++- protocols/jabber/jabber.c | 16 +++++++++++++++- protocols/jabber/jabber.h | 2 ++ protocols/msn/msn.c | 2 +- protocols/oscar/aim.h | 2 +- protocols/oscar/oscar.c | 28 +++++----------------------- protocols/yahoo/yahoo.c | 2 +- root_commands.c | 22 +++++++++------------- tests/check_nick.c | 12 +++++++++--- 20 files changed, 123 insertions(+), 63 deletions(-) diff --git a/account.c b/account.c index 388d04d0..4eb78faa 100644 --- a/account.c +++ b/account.c @@ -94,10 +94,15 @@ char *set_eval_account( set_t *set, char *value ) { g_free( acc->server ); if( *value ) + { acc->server = g_strdup( value ); + return value; + } else + { acc->server = NULL; - return value; + return g_strdup( set->def ); + } } else if( strcmp( set->key, "auto_connect" ) == 0 ) { diff --git a/doc/CREDITS b/doc/CREDITS index f805c251..d53f6de0 100644 --- a/doc/CREDITS +++ b/doc/CREDITS @@ -54,6 +54,7 @@ The authors thank the following people: - Ed Schouten, for reporting bugs. - Greg (gropeep.org), for updating the Yahoo! module to fix some issues that were there for quite some time already. +- misc@mandriva.org for lots of Jabber contributions. - And all other users who help us by sending useful bug reports, positive feedback, nice patches and cool addons. Mentioning you all would make diff --git a/doc/user-guide/commands.xml b/doc/user-guide/commands.xml index 8c874014..090acff3 100644 --- a/doc/user-guide/commands.xml +++ b/doc/user-guide/commands.xml @@ -16,7 +16,7 @@ - account add <protocol> <username> <password> [<server>] + account add <protocol> <username> <password> @@ -25,7 +25,7 @@ - account add jabber <handle@server.tld> <password> [<servertag>] + account add jabber <handle@server.tld> <password> @@ -49,16 +49,16 @@ - account add oscar <handle> <password> [<servername>] + account add oscar <handle> <password> - Specifying a server is required for OSCAR, since OSCAR can be used for both ICQ- and AIM-connections. Although these days it's supposed to be possible to connect to ICQ via AIM-servers and vice versa, we like to stick with this separation for now. For ICQ connections, the servername is login.icq.com, for AIM connections it's login.oscar.aol.com. + OSCAR is the protocol used to connect to AIM and/or ICQ. The servers will automatically detect if you're using a numeric or non-numeric username so there's no need to tell which network you want to connect to. - account add oscar 72696705 hobbelmeeuw login.icq.com + account add oscar 72696705 hobbelmeeuw Account successfully added @@ -594,7 +594,7 @@ - Can be set for Jabber- and OSCAR-connections. For OSCAR, this must be set to login.icq.com if it's an ICQ connection, or login.oscar.aol.com if it's an AIM connection. For Jabber, you have to set this if the servername isn't equal to the part after the @ in the Jabber handle. + Can be set for Jabber- and OSCAR-connections. For Jabber, you might have to set this if the servername isn't equal to the part after the @ in the Jabber handle. For OSCAR this shouldn't be necessary anymore in recent BitlBee versions. diff --git a/doc/user-guide/quickstart.xml b/doc/user-guide/quickstart.xml index 520f240f..fcb06c6b 100644 --- a/doc/user-guide/quickstart.xml +++ b/doc/user-guide/quickstart.xml @@ -37,12 +37,12 @@ For instance, suppose you have an ICQ account with UIN 72696705 - account add oscar 72696705 QuickStart login.icq.com + account add jabber bitlbee@jabber.org QuickStart Account successfully added -Other available IM protocols are jabber, msn, and yahoo. Oscar is the protocol used by ICQ and AOL. For oscar, you need to specify the IM-server as a fourth argument (for msn and yahoo there is no fourth argument). For AOL Instant Messenger, the server name is login.oscar.aol.com. For ICQ, the server name is login.icq.com. +Other available IM protocols are msn, oscar, and yahoo. Oscar is the protocol used by ICQ and AOL. diff --git a/irc_commands.c b/irc_commands.c index 287a126f..65f0d6c6 100644 --- a/irc_commands.c +++ b/irc_commands.c @@ -206,7 +206,7 @@ static void irc_cmd_invite( irc_t *irc, char **cmd ) if( u && c && ( u->ic == c->ic ) ) if( c->ic && c->ic->acc->prpl->chat_invite ) { - c->ic->acc->prpl->chat_invite( c, "", u->handle ); + c->ic->acc->prpl->chat_invite( c, u->handle, NULL ); irc_reply( irc, 341, "%s %s", nick, channel ); return; } diff --git a/lib/misc.c b/lib/misc.c index c977029f..d6795ec9 100644 --- a/lib/misc.c +++ b/lib/misc.c @@ -44,6 +44,8 @@ #include #endif +#include "ssl_client.h" + void strip_linefeed(gchar *text) { int i, j; @@ -590,3 +592,11 @@ char *word_wrap( char *msg, int line_len ) return g_string_free( ret, FALSE ); } + +gboolean ssl_sockerr_again( void *ssl ) +{ + if( ssl ) + return ssl_errno == SSL_AGAIN; + else + return sockerr_again(); +} diff --git a/lib/misc.h b/lib/misc.h index 1d76f7f2..e0468d73 100644 --- a/lib/misc.h +++ b/lib/misc.h @@ -65,4 +65,6 @@ G_MODULE_EXPORT struct ns_srv_reply *srv_lookup( char *service, char *protocol, G_MODULE_EXPORT char *word_wrap( char *msg, int line_len ); +G_MODULE_EXPORT gboolean ssl_sockerr_again( void *ssl ); + #endif diff --git a/lib/ssl_gnutls.c b/lib/ssl_gnutls.c index 221a2862..b964ab49 100644 --- a/lib/ssl_gnutls.c +++ b/lib/ssl_gnutls.c @@ -222,8 +222,10 @@ void ssl_disconnect( void *conn_ ) closesocket( conn->fd ); - gnutls_deinit( conn->session ); - gnutls_certificate_free_credentials( conn->xcred ); + if( conn->session ) + gnutls_deinit( conn->session ); + if( conn->xcred ) + gnutls_certificate_free_credentials( conn->xcred ); g_free( conn ); } diff --git a/nick.c b/nick.c index 88c3faea..4b05f4a7 100644 --- a/nick.c +++ b/nick.c @@ -153,10 +153,10 @@ void nick_del( account_t *acc, const char *handle ) /* Character maps, _lc_[x] == _uc_[x] (but uppercase), according to the RFC's. With one difference, we allow dashes. */ -static char *nick_lc_chars = "0123456789abcdefghijklmnopqrstuvwxyz{}^-_|"; -static char *nick_uc_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ[]~-_\\"; +static char *nick_lc_chars = "0123456789abcdefghijklmnopqrstuvwxyz{}^`-_|"; +static char *nick_uc_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ[]~`-_\\"; -void nick_strip( char * nick ) +void nick_strip( char *nick ) { int i, j; @@ -169,6 +169,15 @@ void nick_strip( char * nick ) j++; } } + if( isdigit( nick[0] ) ) + { + char *orig; + + orig = g_strdup( nick ); + g_snprintf( nick, MAX_NICK_LENGTH, "_%s", orig ); + g_free( orig ); + j ++; + } while( j <= MAX_NICK_LENGTH ) nick[j++] = '\0'; } @@ -177,8 +186,8 @@ int nick_ok( const char *nick ) { const char *s; - /* Empty/long nicks are not allowed */ - if( !*nick || strlen( nick ) > MAX_NICK_LENGTH ) + /* Empty/long nicks are not allowed, nor numbers at [0] */ + if( !*nick || isdigit( nick[0] ) || strlen( nick ) > MAX_NICK_LENGTH ) return( 0 ); for( s = nick; *s; s ++ ) diff --git a/protocols/jabber/conference.c b/protocols/jabber/conference.c index c5bc0e68..074412ec 100644 --- a/protocols/jabber/conference.c +++ b/protocols/jabber/conference.c @@ -175,6 +175,27 @@ int jabber_chat_leave( struct groupchat *c, const char *reason ) return 1; } +void jabber_chat_invite( struct groupchat *c, char *who, char *message ) +{ + struct xt_node *node; + struct im_connection *ic = c->ic; + struct jabber_chat *jc = c->data; + + node = xt_new_node( "reason", message, NULL ); + + node = xt_new_node( "invite", NULL, node ); + xt_add_attr( node, "to", who ); + + node = xt_new_node( "x", NULL, node ); + xt_add_attr( node, "xmlns", XMLNS_MUC_USER ); + + node = jabber_make_packet( "message", NULL, jc->name, node ); + + jabber_write_packet( ic, node ); + + xt_free_node( node ); +} + /* Not really the same syntax as the normal pkt_ functions, but this isn't called by the xmltree parser directly and this way I can add some extra parameters so we won't have to repeat too many things done by the caller diff --git a/protocols/jabber/io.c b/protocols/jabber/io.c index 61cd142e..29561b86 100644 --- a/protocols/jabber/io.c +++ b/protocols/jabber/io.c @@ -119,7 +119,7 @@ static gboolean jabber_write_queue( struct im_connection *ic ) return TRUE; } - else if( st == 0 || ( st < 0 && !sockerr_again() ) ) + else if( st == 0 || ( st < 0 && !ssl_sockerr_again( jd->ssl ) ) ) { /* Set fd to -1 to make sure we won't write to it anymore. */ closesocket( jd->fd ); /* Shouldn't be necessary after errors? */ @@ -230,7 +230,7 @@ static gboolean jabber_read_callback( gpointer data, gint fd, b_input_condition } } } - else if( st == 0 || ( st < 0 && !sockerr_again() ) ) + else if( st == 0 || ( st < 0 && !ssl_sockerr_again( jd->ssl ) ) ) { closesocket( jd->fd ); jd->fd = -1; diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c index 8bd1111a..2f0959b0 100644 --- a/protocols/jabber/iq.c +++ b/protocols/jabber/iq.c @@ -49,7 +49,8 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ) } else if( strcmp( type, "get" ) == 0 ) { - if( !( c = xt_find_node( node->children, "query" ) ) || + if( !( ( c = xt_find_node( node->children, "query" ) ) || + ( c = xt_find_node( node->children, "ping" ) ) ) || /* O_o WHAT is wrong with just ????? */ !( s = xt_find_attr( c, "xmlns" ) ) ) { imcb_log( ic, "WARNING: Received incomplete IQ-%s packet", type ); @@ -80,12 +81,21 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ) strftime( buf, sizeof( buf ) - 1, "%Z", localtime( &time_ep ) ); xt_add_child( reply, xt_new_node( "tz", buf, NULL ) ); } + else if( strcmp( s, XMLNS_PING ) == 0 ) + { + xt_free_node( reply ); + reply = jabber_make_packet( "iq", "result", xt_find_attr( node, "from" ), NULL ); + if( ( s = xt_find_attr( node, "id" ) ) ) + xt_add_attr( reply, "id", s ); + pack = 0; + } else if( strcmp( s, XMLNS_DISCO_INFO ) == 0 ) { const char *features[] = { XMLNS_VERSION, XMLNS_TIME, XMLNS_CHATSTATES, XMLNS_MUC, + XMLNS_PING, XMLNS_SI, XMLNS_BYTESTREAMS, XMLNS_FILETRANSFER, diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c index 98d2dadf..1d3225dd 100644 --- a/protocols/jabber/jabber.c +++ b/protocols/jabber/jabber.c @@ -422,6 +422,20 @@ static void jabber_chat_leave_( struct groupchat *c ) jabber_chat_leave( c, NULL ); } +static void jabber_chat_invite_( struct groupchat *c, char *who, char *msg ) +{ + struct jabber_chat *jc = c->data; + gchar *msg_alt = NULL; + + if( msg == NULL ) + msg_alt = g_strdup_printf( "%s invited you to %s", c->ic->acc->user, jc->name ); + + if( c && who ) + jabber_chat_invite( c, who, msg ? msg : msg_alt ); + + g_free( msg_alt ); +} + static void jabber_keepalive( struct im_connection *ic ) { /* Just any whitespace character is enough as a keepalive for XMPP sessions. */ @@ -493,7 +507,7 @@ void jabber_initmodule() ret->remove_buddy = jabber_remove_buddy; ret->chat_msg = jabber_chat_msg_; ret->chat_topic = jabber_chat_topic_; -// ret->chat_invite = jabber_chat_invite; + ret->chat_invite = jabber_chat_invite_; ret->chat_leave = jabber_chat_leave_; ret->chat_join = jabber_chat_join_; ret->keepalive = jabber_keepalive; diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h index 9edb844e..3251b49b 100644 --- a/protocols/jabber/jabber.h +++ b/protocols/jabber/jabber.h @@ -188,6 +188,7 @@ struct jabber_transfer #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 */ @@ -292,5 +293,6 @@ int jabber_chat_topic( struct groupchat *c, char *topic ); int jabber_chat_leave( struct groupchat *c, const char *reason ); void jabber_chat_pkt_presence( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node ); void jabber_chat_pkt_message( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node ); +void jabber_chat_invite( struct groupchat *c, char *who, char *message ); #endif diff --git a/protocols/msn/msn.c b/protocols/msn/msn.c index df04e30d..aa05dbdd 100644 --- a/protocols/msn/msn.c +++ b/protocols/msn/msn.c @@ -240,7 +240,7 @@ static void msn_chat_msg( struct groupchat *c, char *message, int flags ) already severely broken) disappeared here! */ } -static void msn_chat_invite( struct groupchat *c, char *msg, char *who ) +static void msn_chat_invite( struct groupchat *c, char *who, char *message ) { struct msn_switchboard *sb = msn_sb_by_chat( c ); char buf[1024]; diff --git a/protocols/oscar/aim.h b/protocols/oscar/aim.h index 81ea5f9e..9516996c 100644 --- a/protocols/oscar/aim.h +++ b/protocols/oscar/aim.h @@ -93,7 +93,7 @@ typedef guint16 flap_seqnum_t; * the client to connect to it. * */ -#define AIM_DEFAULT_LOGIN_SERVER "login.oscar.aol.com" +#define AIM_DEFAULT_LOGIN_SERVER "login.messaging.aol.com" #define AIM_LOGIN_PORT 5190 /* diff --git a/protocols/oscar/oscar.c b/protocols/oscar/oscar.c index 96983738..c4683046 100644 --- a/protocols/oscar/oscar.c +++ b/protocols/oscar/oscar.c @@ -340,7 +340,7 @@ static void oscar_init(account_t *acc) { set_t *s; - s = set_add( &acc->set, "server", NULL, set_eval_account, acc ); + s = set_add( &acc->set, "server", AIM_DEFAULT_LOGIN_SERVER, set_eval_account, acc ); s->flags |= ACC_SET_NOSAVE | ACC_SET_OFFLINE_ONLY; if (isdigit(acc->user[0])) { @@ -355,15 +355,7 @@ static void oscar_login(account_t *acc) { struct im_connection *ic = imcb_new(acc); struct oscar_data *odata = ic->proto_data = g_new0(struct oscar_data, 1); - if (isdigit(acc->user[0])) { - odata->icq = TRUE; - /* This is odd but it's necessary for a proper do_import and do_export. - We don't do those anymore, but let's stick with it, just in case - it accidentally fixes something else too... */ - /* ic->acc->pass[8] = 0; - Not touching this anymore now that it belongs to account_t! - Let's hope nothing will break. ;-) */ - } else { + if (!isdigit(acc->user[0])) { ic->flags |= OPT_DOES_HTML; } @@ -384,24 +376,14 @@ static void oscar_login(account_t *acc) { return; } - if (acc->server == NULL) { - imcb_error(ic, "No servername specified"); - imc_logout(ic, FALSE); - return; - } - - if (g_strcasecmp(acc->server, "login.icq.com") != 0 && - g_strcasecmp(acc->server, "login.oscar.aol.com") != 0) { - imcb_log(ic, "Warning: Unknown OSCAR server: `%s'. Please review your configuration if the connection fails.",acc->server); - } - imcb_log(ic, _("Signon: %s"), ic->acc->user); aim_conn_addhandler(sess, conn, 0x0017, 0x0007, gaim_parse_login, 0); aim_conn_addhandler(sess, conn, 0x0017, 0x0003, gaim_parse_auth_resp, 0); conn->status |= AIM_CONN_STATUS_INPROGRESS; - conn->fd = proxy_connect(acc->server, AIM_LOGIN_PORT, oscar_login_connect, ic); + conn->fd = proxy_connect(set_getstr(&acc->set, "server"), + AIM_LOGIN_PORT, oscar_login_connect, ic); if (conn->fd < 0) { imcb_error(ic, _("Couldn't connect to host")); imc_logout(ic, TRUE); @@ -2508,7 +2490,7 @@ void oscar_chat_msg(struct groupchat *c, char *message, int msgflags) /* return (ret >= 0); */ } -void oscar_chat_invite(struct groupchat *c, char *message, char *who) +void oscar_chat_invite(struct groupchat *c, char *who, char *message) { struct im_connection *ic = c->ic; struct oscar_data * od = (struct oscar_data *)ic->proto_data; diff --git a/protocols/yahoo/yahoo.c b/protocols/yahoo/yahoo.c index 28a72877..625f3d1c 100644 --- a/protocols/yahoo/yahoo.c +++ b/protocols/yahoo/yahoo.c @@ -305,7 +305,7 @@ static void byahoo_chat_msg( struct groupchat *c, char *message, int flags ) yahoo_conference_message( yd->y2_id, NULL, c->data, c->title, message, 1 ); } -static void byahoo_chat_invite( struct groupchat *c, char *msg, char *who ) +static void byahoo_chat_invite( struct groupchat *c, char *who, char *msg ) { struct byahoo_data *yd = (struct byahoo_data *) c->ic->proto_data; diff --git a/root_commands.c b/root_commands.c index 2da77519..eea16178 100644 --- a/root_commands.c +++ b/root_commands.c @@ -198,7 +198,7 @@ static void cmd_drop( irc_t *irc, char **cmd ) irc_usermsg( irc, "Account `%s' removed", irc->nick ); break; default: - irc_usermsg( irc, "Error: '%d'", status ); + irc_usermsg( irc, "Error: `%d'", status ); break; } } @@ -233,7 +233,11 @@ static void cmd_account( irc_t *irc, char **cmd ) a = account_add( irc, prpl, cmd[3], cmd[4] ); if( cmd[5] ) + { + irc_usermsg( irc, "Warning: Passing a servername/other flags to `account add' " + "is now deprecated. Use `account set' instead." ); set_setstr( &a->set, "server", cmd[5] ); + } irc_usermsg( irc, "Account successfully added" ); } @@ -316,7 +320,7 @@ static void cmd_account( irc_t *irc, char **cmd ) } else { - irc_usermsg( irc, "No accounts known. Use 'account add' to add one." ); + irc_usermsg( irc, "No accounts known. Use `account add' to add one." ); } } } @@ -402,9 +406,7 @@ static void cmd_account( irc_t *irc, char **cmd ) return; } - if( ( strcmp( cmd[3], "=" ) ) == 0 && cmd[4] ) - irc_usermsg( irc, "Warning: Correct syntax: \002account set \002 (without =)" ); - else if( g_strncasecmp( cmd[2], "-del", 4 ) == 0 ) + if( g_strncasecmp( cmd[2], "-del", 4 ) == 0 ) set_reset( &a->set, set_name ); else set_setstr( &a->set, set_name, cmd[3] ); @@ -744,16 +746,11 @@ static void cmd_yesno( irc_t *irc, char **cmd ) static void cmd_set( irc_t *irc, char **cmd ) { - char *set_name = NULL; + char *set_name = cmd[1]; if( cmd[1] && cmd[2] ) { - if( ( strcmp( cmd[2], "=" ) ) == 0 && cmd[3] ) - { - irc_usermsg( irc, "Warning: Correct syntax: \002set \002 (without =)" ); - return; - } - else if( g_strncasecmp( cmd[1], "-del", 4 ) == 0 ) + if( g_strncasecmp( cmd[1], "-del", 4 ) == 0 ) { set_reset( &irc->set, cmd[2] ); set_name = cmd[2]; @@ -761,7 +758,6 @@ static void cmd_set( irc_t *irc, char **cmd ) else { set_setstr( &irc->set, cmd[1], cmd[2] ); - set_name = cmd[1]; } } if( set_name ) /* else 'forgotten' on purpose.. Must show new value after changing */ diff --git a/tests/check_nick.c b/tests/check_nick.c index 714c4fdc..6c4267cd 100644 --- a/tests/check_nick.c +++ b/tests/check_nick.c @@ -14,11 +14,17 @@ START_TEST(test_nick_strip) "thisisaveryveryveryverylongnick", "thisisave:ryveryveryverylongnick", "t::::est", + "test123", + "123test", + "123", NULL }; const char *expected[] = { "test", "test", "test", "thisisaveryveryveryveryl", "thisisaveryveryveryveryl", "test", + "test123", + "_123test", + "_123", NULL }; for (i = 0; get[i]; i++) { @@ -34,8 +40,8 @@ END_TEST START_TEST(test_nick_ok_ok) { - const char *nicks[] = { "foo", "bar", "bla[", "blie]", - "BreEZaH", "\\od^~", NULL }; + const char *nicks[] = { "foo", "bar123", "bla[", "blie]", "BreEZaH", + "\\od^~", "_123", "_123test", NULL }; int i; for (i = 0; nicks[i]; i++) { @@ -48,7 +54,7 @@ END_TEST START_TEST(test_nick_ok_notok) { const char *nicks[] = { "thisisaveryveryveryveryveryveryverylongnick", - "\nillegalchar", "", "nick%", NULL }; + "\nillegalchar", "", "nick%", "123test", NULL }; int i; for (i = 0; nicks[i]; i++) { -- cgit v1.2.3 From 506e61b853ba969bc2d4d2878e87b975bd9e431c Mon Sep 17 00:00:00 2001 From: ulim Date: Sun, 17 Feb 2008 13:34:47 +0100 Subject: Removes the word "dcc" from 2 comments and one message. Thanks to vmiklos for this! --- protocols/ft.h | 4 ++-- protocols/jabber/si.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/protocols/ft.h b/protocols/ft.h index d35580d0..42333c90 100644 --- a/protocols/ft.h +++ b/protocols/ft.h @@ -47,7 +47,7 @@ typedef enum { * imcb_file_send_start() method, which will initialize most of the fields. The data field and the various * methods are zero-initialized. Instances will automatically be deleted once the transfer is completed, * canceled, or the connection to the irc client has been lost (note that also if only the irc connection - * and not the dcc connection is lost, the file transfer will still be canceled and freed). + * 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: * @@ -159,7 +159,7 @@ typedef struct file_transfer { } file_transfer_t; /* - * This starts a file transfer from bitlbee to the user (currently via DCC). + * 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 ); diff --git a/protocols/jabber/si.c b/protocols/jabber/si.c index 1d649da3..aab71c8b 100644 --- a/protocols/jabber/si.c +++ b/protocols/jabber/si.c @@ -211,7 +211,7 @@ int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, st imcb_log( ic, "File transfer request from %s for %s (%zd kb). ", xt_find_attr( node, "from" ), name, size/1024 ); - imcb_log( ic, "Accept the DCC transfer if you'd like the file. If you don't, issue the 'transfers reject' command."); + imcb_log( ic, "Accept the file transfer if you'd like the file. If you don't, issue the 'transfers reject' command."); tf = g_new0( struct jabber_transfer, 1 ); -- cgit v1.2.3 From 2625d6de47df4054f793990118fa99f3d04d694f Mon Sep 17 00:00:00 2001 From: ulim Date: Wed, 20 Feb 2008 21:55:38 +0100 Subject: some fixes related to connection timeout --- protocols/jabber/s5bytestream.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c index 4770feda..0abc2c1b 100644 --- a/protocols/jabber/s5bytestream.c +++ b/protocols/jabber/s5bytestream.c @@ -100,6 +100,12 @@ void jabber_bs_free_transfer( file_transfer_t *ft) { 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 ); @@ -347,7 +353,7 @@ gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition con struct bs_transfer *bt = data; short revents; - if ( !jabber_bs_poll( bt, fd, &revents ) ) + if ( ( fd != 0 ) && !jabber_bs_poll( bt, fd, &revents ) ) return FALSE; switch( bt->phase ) @@ -723,7 +729,17 @@ static xt_status jabber_bs_send_handle_reply(struct im_connection *ic, struct xt } } else { - /* using a proxy */ + /* using a proxy, abort listen */ + + closesocket( tf->fd ); + tf->fd = 0; + + 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 ) ) { @@ -887,13 +903,15 @@ gboolean jabber_bs_send_request( struct jabber_transfer *tf, GSList *streamhosts 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 ); - imcb_file_canceled( tf->ft, error ); + if( jd->streamhosts==NULL ) /* we're done here unless we have a proxy to try */ + imcb_file_canceled( tf->ft, error ); return FALSE; } -- cgit v1.2.3 From 6cac643f6933e431b90fcb937dec505f989e6a53 Mon Sep 17 00:00:00 2001 From: ulim Date: Mon, 14 Apr 2008 14:59:15 +0200 Subject: more verbose error logging --- dcc.c | 24 ++++++++++++++++++------ protocols/jabber/s5bytestream.c | 11 ++++++----- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/dcc.c b/dcc.c index 2fa3f745..fa5a0f76 100644 --- a/dcc.c +++ b/dcc.c @@ -583,11 +583,17 @@ file_transfer_t *dcc_request( struct im_connection *ic, char *line ) regex_t re; file_transfer_t *ft; dcc_file_transfer_t *df; - - if( regcomp( &re, pattern, REG_EXTENDED ) ) - return NULL; - if( regexec( &re, line, 9, pmatch, 0 ) ) + char errbuf[256]; + int regerrcode, gret; + + if( ( regerrcode = regcomp( &re, pattern, REG_EXTENDED ) ) || + ( regerrcode = regexec( &re, line, 9, 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[4].rm_so > 0 ) && @@ -615,7 +621,7 @@ file_transfer_t *dcc_request( struct im_connection *ic, char *line ) /* number means ipv4, something else means ipv6 */ if ( pmatch[5].rm_so > 0 ) { - struct in_addr ipaddr = { htonl( atoi( input + pmatch[5].rm_so ) ) }; + struct in_addr ipaddr = { .s_addr = htonl( atoi( input + pmatch[5].rm_so ) ) }; host = inet_ntoa( ipaddr ); } else { @@ -630,9 +636,13 @@ file_transfer_t *dcc_request( struct im_connection *ic, char *line ) filesize = atoll( input + pmatch[8].rm_so ); memset( &hints, 0, sizeof ( struct addrinfo ) ); - if ( getaddrinfo( host, port, &hints, &rp ) ) + 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; } @@ -649,6 +659,8 @@ file_transfer_t *dcc_request( struct im_connection *ic, char *line ) return ft; } + imcb_log( ic, "DCC: couldnt parse 'DCC SEND' line: %s", line ); + return NULL; } diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c index 0abc2c1b..1008f162 100644 --- a/protocols/jabber/s5bytestream.c +++ b/protocols/jabber/s5bytestream.c @@ -352,6 +352,7 @@ gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition con struct bs_transfer *bt = data; short revents; + int gret; if ( ( fd != 0 ) && !jabber_bs_poll( bt, fd, &revents ) ) return FALSE; @@ -365,8 +366,8 @@ gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition con memset( &hints, 0, sizeof( struct addrinfo ) ); hints.ai_socktype = SOCK_STREAM; - if ( getaddrinfo( bt->sh->host, bt->sh->port, &hints, &rp ) != 0 ) - return jabber_bs_abort( bt, "getaddrinfo() failed: %s", strerror( errno ) ); + 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" ); @@ -922,7 +923,7 @@ gboolean jabber_bs_send_handshake_abort(struct bs_transfer *bt, char *error ) 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; + int fd,gret; char hostname[ HOST_NAME_MAX + 1 ]; struct addrinfo hints, *rp; socklen_t ssize = sizeof( struct sockaddr_storage ); @@ -935,8 +936,8 @@ gboolean jabber_bs_send_listen( struct bs_transfer *bt, struct sockaddr_storage hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_NUMERICSERV; - if ( getaddrinfo( hostname, "0", &hints, &rp ) != 0 ) - return jabber_bs_abort( bt, "getaddrinfo()" ); + 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 ); -- cgit v1.2.3 From 0cab3888888c7c6b58af9560a0ae2c74a795727f Mon Sep 17 00:00:00 2001 From: ulim Date: Mon, 14 Apr 2008 17:33:13 +0200 Subject: more verbose error reporting --- dcc.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/dcc.c b/dcc.c index fa5a0f76..d93eef87 100644 --- a/dcc.c +++ b/dcc.c @@ -217,7 +217,7 @@ gboolean dcc_listen( dcc_file_transfer_t *df, struct sockaddr_storage **saddr_pt { file_transfer_t *file = df->ft; struct sockaddr_storage *saddr; - int fd; + int fd,gret; char hostname[ HOST_NAME_MAX + 1 ]; struct addrinfo hints, *rp; socklen_t ssize = sizeof( struct sockaddr_storage ); @@ -230,8 +230,8 @@ gboolean dcc_listen( dcc_file_transfer_t *df, struct sockaddr_storage **saddr_pt hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_NUMERICSERV; - if ( getaddrinfo( hostname, "0", &hints, &rp ) != 0 ) - return dcc_abort( df, "getaddrinfo()" ); + if ( ( gret = getaddrinfo( hostname, "0", &hints, &rp ) != 0 ) ) + return dcc_abort( df, "getaddrinfo(): %s", gai_strerror( gret ) ); saddr = g_new( struct sockaddr_storage, 1 ); @@ -389,11 +389,12 @@ 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, "Protocol didn't register write()" ); + return dcc_abort( df, "BUG: protocol didn't register write()" ); ASSERTSOCKOP( fd = df->fd = socket( saddr->ss_family, SOCK_STREAM, 0 ) , "Opening Socket" ); @@ -401,7 +402,17 @@ gboolean dccs_recv_start( file_transfer_t *ft ) if( ( connect( fd, (struct sockaddr *)saddr, sa_len ) == -1 ) && ( errno != EINPROGRESS ) ) - return dcc_abort( df, "Connecting" ); + 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; -- cgit v1.2.3 From 4358b10c11410a27af9458c92067549cafbc4c0b Mon Sep 17 00:00:00 2001 From: ulim Date: Sun, 4 May 2008 15:32:15 +0200 Subject: ulibc support, fixes "Invalid SOCKS5 Connect message" problem --- dcc.c | 14 ++++++++++++++ irc.c | 3 --- protocols/jabber/iq.c | 5 +++-- protocols/jabber/s5bytestream.c | 16 +++++++++++++++- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/dcc.c b/dcc.c index d93eef87..cd70c480 100644 --- a/dcc.c +++ b/dcc.c @@ -29,6 +29,20 @@ #include #include +/* Some ifdefs for ulibc (Thanks to Whoopie) */ +#ifndef HOST_NAME_MAX +#include +#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: * diff --git a/irc.c b/irc.c index e8b08e71..c61e289b 100644 --- a/irc.c +++ b/irc.c @@ -29,9 +29,6 @@ #include "ipc.h" #include "dcc.h" -#include -#include - static gboolean irc_userping( gpointer _irc, int fd, b_input_condition cond ); GSList *irc_connection_list = NULL; diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c index 22aa3e7c..eacc85af 100644 --- a/protocols/jabber/iq.c +++ b/protocols/jabber/iq.c @@ -611,7 +611,7 @@ xt_status jabber_iq_query_features( struct im_connection *ic, char *bare_jid ) if( ( bud = jabber_buddy_by_jid( ic, bare_jid , 0 ) ) == NULL ) { /* Who cares about the unknown... */ - imcb_log( ic, "Couldnt find the man: %s", bare_jid); + imcb_log( ic, "Couldn't find buddy: %s", bare_jid); return 0; } @@ -625,6 +625,7 @@ xt_status jabber_iq_query_features( struct im_connection *ic, char *bare_jid ) { imcb_log( ic, "WARNING: Couldn't generate feature query" ); xt_free_node( node ); + return 0; } jabber_cache_add( ic, query, jabber_iq_parse_features ); @@ -647,7 +648,7 @@ xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *no if( ( bud = jabber_buddy_by_jid( ic, xt_find_attr( node, "from") , 0 ) ) == NULL ) { /* Who cares about the unknown... */ - imcb_log( ic, "Couldnt find the man: %s", xt_find_attr( node, "from")); + imcb_log( ic, "Couldn't find buddy: %s", xt_find_attr( node, "from")); return 0; } diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c index 1008f162..15696501 100644 --- a/protocols/jabber/s5bytestream.c +++ b/protocols/jabber/s5bytestream.c @@ -25,6 +25,16 @@ #include "sha1.h" #include +/* Some ifdefs for ulibc (Thanks to Whoopie) */ +#ifndef HOST_NAME_MAX +#include +#ifdef MAXHOSTNAMELEN +#define HOST_NAME_MAX MAXHOSTNAMELEN +#else +#define HOST_NAME_MAX 255 +#endif +#endif + struct bs_transfer { struct jabber_transfer *tf; @@ -1047,10 +1057,14 @@ gboolean jabber_bs_send_handshake( gpointer data, gint fd, b_input_condition con { struct socks5_message socks5_connect; int msgsize = sizeof( struct socks5_message ); + int ret; - if( !jabber_bs_peek( bt, &socks5_connect, msgsize ) ) + 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 ) || -- cgit v1.2.3 From 29c1456dcadec0d239ffc9d88ea06695b66c435c Mon Sep 17 00:00:00 2001 From: ulim Date: Tue, 6 May 2008 02:13:37 +0200 Subject: SOCKS5 bytestream related changes. * allow the SOCKS5 server to not include the pseudo address in its reply(including it is an rfc-style SHOULD in XEP-0065) * ignore if the SOCKS5 server's reply is too short (as is the one from the jabber.cz proxy [apparently using the proxy65 code]) --- protocols/jabber/s5bytestream.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c index 15696501..996d1f24 100644 --- a/protocols/jabber/s5bytestream.c +++ b/protocols/jabber/s5bytestream.c @@ -465,18 +465,28 @@ gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition con if ( !( ret = jabber_bs_peek( bt, &socks5_reply, sizeof( struct socks5_message ) ) ) ) return FALSE; - if ( ret < sizeof( socks5_reply ) ) + 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 ) || - !( socks5_reply.atyp == 3 ) || - !( socks5_reply.addrlen == 40 ) ) + !( socks5_reply.cmdrep.rep == 0 ) ) return jabber_bs_abort( bt, "SOCKS5 CONNECT failed (reply: ver=%d, rep=%d, atyp=%d, addrlen=%d", socks5_reply.ver, socks5_reply.cmdrep.rep, socks5_reply.atyp, socks5_reply.addrlen); + + /* 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 ); @@ -742,6 +752,12 @@ static xt_status jabber_bs_send_handle_reply(struct im_connection *ic, struct xt { /* using a proxy, abort listen */ + if ( tf->watch_in ) + { + b_event_remove( tf->watch_in ); + tf->watch_in = 0; + } + closesocket( tf->fd ); tf->fd = 0; @@ -824,6 +840,8 @@ static xt_status jabber_bs_send_handle_activate( struct im_connection *ic, struc 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 ); -- cgit v1.2.3 From d56ee38d444fb9a4bc0fbf7f699eaf675e019591 Mon Sep 17 00:00:00 2001 From: ulim Date: Tue, 6 May 2008 02:20:11 +0200 Subject: timeout of transfers after 120 seconds of no progress (bytes received/sent). --- dcc.c | 31 +++++++++++++++++++++++++++++-- dcc.h | 8 ++++++++ protocols/jabber/si.c | 3 ++- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/dcc.c b/dcc.c index cd70c480..a6eb511c 100644 --- a/dcc.c +++ b/dcc.c @@ -84,6 +84,7 @@ int dccs_send_request( struct dcc_file_transfer *df, char *user_nick, struct soc 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 ) @@ -151,6 +152,8 @@ file_transfer_t *dccs_send_start( struct im_connection *ic, char *user_nick, cha 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 ); + return file; } @@ -167,8 +170,8 @@ gboolean dcc_abort( dcc_file_transfer_t *df, char *reason, ... ) if( file->canceled ) file->canceled( file, msg ); - else - imcb_log( df->ic, "DCC transfer aborted: %s", msg ); + + imcb_log( df->ic, "File %s: DCC transfer aborted: %s", file->file_name, msg ); g_free( msg ); @@ -177,6 +180,25 @@ gboolean dcc_abort( dcc_file_transfer_t *df, char *reason, ... ) return FALSE; } +gboolean dcc_progress( gpointer data, gint fd, b_input_condition cond ) +{ + struct dcc_file_transfer *df = data; + + if( df->ft->bytes_transferred == 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->ft->bytes_transferred / 1024 ); + + } + + df->progress_bytes_last = df->ft->bytes_transferred; + + return TRUE; +} + /* used extensively for socket operations */ #define ASSERTSOCKOP(op, msg) \ if( (op) == -1 ) \ @@ -434,6 +456,8 @@ gboolean dccs_recv_start( file_transfer_t *ft ) 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; } @@ -573,6 +597,9 @@ static void dcc_close( file_transfer_t *file ) 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 ); diff --git a/dcc.h b/dcc.h index e53823e7..de24dac0 100644 --- a/dcc.h +++ b/dcc.h @@ -46,6 +46,10 @@ /* 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; @@ -63,6 +67,10 @@ typedef struct dcc_file_transfer { 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. */ diff --git a/protocols/jabber/si.c b/protocols/jabber/si.c index aab71c8b..d47cf7a4 100644 --- a/protocols/jabber/si.c +++ b/protocols/jabber/si.c @@ -54,8 +54,9 @@ void jabber_si_free_transfer( file_transfer_t *ft) void jabber_si_finished( file_transfer_t *ft ) { struct jabber_transfer *tf = ft->data; + time_t diff = time( NULL ) - ft->started; - imcb_log( tf->ic, "File %s transferred successfully!" , ft->file_name ); + imcb_log( tf->ic, "File %s transferred successfully at %d kb/s!" , ft->file_name, (int) ( ft->bytes_transferred / 1024 / diff ) ); } /* file_transfer canceled() callback */ -- cgit v1.2.3 From 44961cb326fde3cb681a79eb28becb74a921a29d Mon Sep 17 00:00:00 2001 From: ulim Date: Wed, 7 May 2008 00:06:22 +0200 Subject: fix bug in new kb/s display for transfers of less than one second. --- protocols/jabber/si.c | 2 +- root_commands.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/protocols/jabber/si.c b/protocols/jabber/si.c index d47cf7a4..6e62f684 100644 --- a/protocols/jabber/si.c +++ b/protocols/jabber/si.c @@ -54,7 +54,7 @@ void jabber_si_free_transfer( file_transfer_t *ft) void jabber_si_finished( file_transfer_t *ft ) { struct jabber_transfer *tf = ft->data; - time_t diff = time( NULL ) - ft->started; + time_t diff = time( NULL ) - ft->started ? : 1; imcb_log( tf->ic, "File %s transferred successfully at %d kb/s!" , ft->file_name, (int) ( ft->bytes_transferred / 1024 / diff ) ); } diff --git a/root_commands.c b/root_commands.c index 9dfbc998..b3a77a10 100644 --- a/root_commands.c +++ b/root_commands.c @@ -1075,7 +1075,7 @@ static void cmd_transfers( irc_t *irc, char **cmd ) else { int kb_per_s = 0; - time_t diff = time( NULL ) - file->started; + time_t diff = time( NULL ) - file->started ? : 1; if ( ( file->started > 0 ) && ( file->bytes_transferred > 0 ) ) kb_per_s = file->bytes_transferred / 1024 / diff; -- cgit v1.2.3 From b043ad5e69d91730145ea49305a1fab588c83a93 Mon Sep 17 00:00:00 2001 From: ulim Date: Thu, 8 May 2008 23:20:36 +0200 Subject: allow dcc optimization in progress watcher. Apparently, irssi doesn't send any DCC ACKs if it "feels" that the sender doesn't expect any(no idea how exactly it does that). Anyway, the progress watcher used to check whether the ACKed bytes have increased which it shouldn't do if there aren't any ACKs. --- dcc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dcc.c b/dcc.c index a6eb511c..0ee894b7 100644 --- a/dcc.c +++ b/dcc.c @@ -184,17 +184,17 @@ gboolean dcc_progress( gpointer data, gint fd, b_input_condition cond ) { struct dcc_file_transfer *df = data; - if( df->ft->bytes_transferred == df->progress_bytes_last ) + 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->ft->bytes_transferred / 1024 ); + return dcc_abort( df, "Transfer stalled for %d seconds at %d kb", DCC_MAX_STALL, df->bytes_sent / 1024 ); } - df->progress_bytes_last = df->ft->bytes_transferred; + df->progress_bytes_last = df->bytes_sent; return TRUE; } -- cgit v1.2.3 From b5cfc2bdad8a68011bf9215c63293299e4e8dc5c Mon Sep 17 00:00:00 2001 From: ulim Date: Sat, 10 May 2008 12:09:19 +0200 Subject: some changes for sending. * not only query but also respect peer's features (i.e. abort ft if an important feature is not advertised) * wait for proxy discovery to complete before starting the transfer (important for sending to people with auto accept) --- protocols/jabber/jabber.h | 6 +++ protocols/jabber/si.c | 106 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 102 insertions(+), 10 deletions(-) diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h index fc315710..bc848170 100644 --- a/protocols/jabber/jabber.h +++ b/protocols/jabber/jabber.h @@ -153,8 +153,14 @@ struct jabber_transfer /* 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; diff --git a/protocols/jabber/si.c b/protocols/jabber/si.c index 6e62f684..3afa30ff 100644 --- a/protocols/jabber/si.c +++ b/protocols/jabber/si.c @@ -44,6 +44,9 @@ void jabber_si_free_transfer( file_transfer_t *ft) tf->fd = 0; } + 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 ); @@ -79,12 +82,90 @@ void jabber_si_canceled( file_transfer_t *ft, char *reason ) } +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 ) { + jabber_si_transfer_start( tf ); + tf->disco_timeout = 0; + 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_file_canceled( tf->ft, "Couldn't get buddy's features or the server's" ); + else if( !tf->bud->features ) + imcb_file_canceled( tf->ft, "Couldn't get buddy's features" ); + else + imcb_file_canceled( tf->ft, "Couldn't get server's features" ); + + tf->disco_timeout = 0; + 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; - char *server = jd->server; + 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 ); @@ -94,22 +175,27 @@ void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, tf->ft->data = tf; tf->ft->free = jabber_si_free_transfer; tf->ft->finished = jabber_si_finished; + tf->bud = bud; ft->write = jabber_bs_send_write; jd->filetransfers = g_slist_prepend( jd->filetransfers, tf ); - /* query the buddy's features */ - jabber_iq_query_features( ic, who ); + /* query buddy's features and server's streaming proxies if neccessary */ - /* query proxies from the server */ - if( !jd->have_streamhosts ) - jabber_iq_query_server( ic, server, XMLNS_DISCO_ITEMS ); + if( !tf->bud->features ) + jabber_iq_query_features( ic, bud->full_jid ); - /* send the request to our buddy */ - jabber_si_send_request( ic, who, tf ); + if( jd->have_streamhosts!=1 ) { + jd->have_streamhosts = 0; + jabber_iq_query_server( ic, server, XMLNS_DISCO_ITEMS ); + } - /* and start the receive logic */ - imcb_file_recv_start( ft ); + /* 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 ); } /* -- cgit v1.2.3 From cce0450184b4358ef06d91cca985fa3ca389fcd6 Mon Sep 17 00:00:00 2001 From: ulim Date: Mon, 2 Jun 2008 14:49:33 +0200 Subject: Added textual SOCKS5 error messages. --- protocols/jabber/s5bytestream.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c index 996d1f24..41bae40a 100644 --- a/protocols/jabber/s5bytestream.c +++ b/protocols/jabber/s5bytestream.c @@ -71,6 +71,18 @@ struct socks5_message 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 */ @@ -476,12 +488,19 @@ gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition con } if( !( socks5_reply.ver == 5 ) || - !( socks5_reply.cmdrep.rep == 0 ) ) - return jabber_bs_abort( bt, "SOCKS5 CONNECT failed (reply: ver=%d, rep=%d, atyp=%d, addrlen=%d", + !( 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). -- cgit v1.2.3 From 3355a82eae40c89024233ccafc5f0c25dee2dc5c Mon Sep 17 00:00:00 2001 From: ulim Date: Mon, 2 Jun 2008 15:26:38 +0200 Subject: added a #define for AI_NUMERICSERV in s5bytestream.c (missing in ulibc). --- protocols/jabber/s5bytestream.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c index 41bae40a..809fbb22 100644 --- a/protocols/jabber/s5bytestream.c +++ b/protocols/jabber/s5bytestream.c @@ -35,6 +35,10 @@ #endif #endif +#ifndef AI_NUMERICSERV +#define AI_NUMERICSERV 0x0400 /* Don't use name resolution. */ +#endif + struct bs_transfer { struct jabber_transfer *tf; -- cgit v1.2.3 From 9afeeface6363ea81c5f80a7274a691f18550a21 Mon Sep 17 00:00:00 2001 From: ulim Date: Tue, 22 Jul 2008 14:36:56 +0200 Subject: fixes bug in handling file names with spaces. --- dcc.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/dcc.c b/dcc.c index 0ee894b7..d815e9b7 100644 --- a/dcc.c +++ b/dcc.c @@ -627,11 +627,11 @@ void dcc_finish( file_transfer_t *file ) file_transfer_t *dcc_request( struct im_connection *ic, char *line ) { char *pattern = "SEND" - " (([^\"][^ ]*)|\"([^\"]|\\\")*\")" + " (([^\"][^ ]*)|\"(([^\"]|\\\")*)\")" " (([0-9]*)|([^ ]*))" " ([0-9]*)" " ([0-9]*)\001"; - regmatch_t pmatch[9]; + regmatch_t pmatch[10]; regex_t re; file_transfer_t *ft; dcc_file_transfer_t *df; @@ -639,7 +639,7 @@ file_transfer_t *dcc_request( struct im_connection *ic, char *line ) int regerrcode, gret; if( ( regerrcode = regcomp( &re, pattern, REG_EXTENDED ) ) || - ( regerrcode = regexec( &re, line, 9, pmatch, 0 ) ) ) { + ( regerrcode = regexec( &re, line, 10, pmatch, 0 ) ) ) { regerror( regerrcode,&re,errbuf,sizeof( errbuf ) ); imcb_log( ic, "DCC: error parsing 'DCC SEND': %s, line: %s", @@ -648,9 +648,9 @@ file_transfer_t *dcc_request( struct im_connection *ic, char *line ) } if( ( pmatch[1].rm_so > 0 ) && - ( pmatch[4].rm_so > 0 ) && - ( pmatch[7].rm_so > 0 ) && - ( pmatch[8].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; @@ -668,24 +668,24 @@ file_transfer_t *dcc_request( struct im_connection *ic, char *line ) filename = input + pmatch[3].rm_so; } - input[pmatch[4].rm_eo] = '\0'; + input[pmatch[5].rm_eo] = '\0'; /* number means ipv4, something else means ipv6 */ - if ( pmatch[5].rm_so > 0 ) + 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[6].rm_so; + host = input + pmatch[7].rm_so; } - input[pmatch[7].rm_eo] = '\0'; input[pmatch[8].rm_eo] = '\0'; + input[pmatch[9].rm_eo] = '\0'; - port = input + pmatch[7].rm_so; - filesize = atoll( input + pmatch[8].rm_so ); + 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 ) ) ) -- cgit v1.2.3 From 8a90001ab2cde971abc64b8d5e4174dc4c4b0ae2 Mon Sep 17 00:00:00 2001 From: ulim Date: Tue, 22 Jul 2008 14:37:49 +0200 Subject: Added an account setting 'proxy'. Note that this is only used for sending. The default ; means let the receiver try a direct connection first and then the proxy discovered from the server (if any). If you know you're firewalled you can remove the . If you want to provide your own proxy try something like ";JID,HOST,PORT". E.g. ";proxy.somewhere.org,123.123.123.123,7777". --- protocols/jabber/jabber.c | 2 + protocols/jabber/s5bytestream.c | 101 ++++++++++++++++++++++++++++++---------- protocols/jabber/si.c | 22 +++++---- 3 files changed, 93 insertions(+), 32 deletions(-) diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c index 04d6f906..8b1d78db 100644 --- a/protocols/jabber/jabber.c +++ b/protocols/jabber/jabber.c @@ -61,6 +61,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", ";", NULL, acc ); } static void jabber_generate_id_hash( struct jabber_data *jd ); diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c index 809fbb22..c492491b 100644 --- a/protocols/jabber/s5bytestream.c +++ b/protocols/jabber/s5bytestream.c @@ -361,7 +361,7 @@ int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, stru tf->streamhandle = bt; tf->ft->free = jabber_bs_free_transfer; - jabber_bs_recv_handshake( bt, 0, 0 ); + jabber_bs_recv_handshake( bt, -1, 0 ); return XT_HANDLED; } @@ -380,7 +380,7 @@ gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition con short revents; int gret; - if ( ( fd != 0 ) && !jabber_bs_poll( bt, fd, &revents ) ) + if ( ( fd != -1 ) && !jabber_bs_poll( bt, fd, &revents ) ) return FALSE; switch( bt->phase ) @@ -553,7 +553,7 @@ gboolean jabber_bs_recv_handshake_abort( struct bs_transfer *bt, char *error ) if( shlist && shlist->next ) { bt->sh = shlist->next->data; - return jabber_bs_recv_handshake( bt, 0, 0 ); + return jabber_bs_recv_handshake( bt, -1, 0 ); } @@ -619,7 +619,7 @@ gboolean jabber_bs_recv_read( gpointer data, gint fd, b_input_condition cond ) struct bs_transfer *bt = data; struct jabber_transfer *tf = bt->tf; - if( fd != 0 ) /* called via event thread */ + if( fd != -1 ) /* called via event thread */ { tf->watch_in = 0; ASSERTSOCKOP( ret = recv( fd, tf->ft->buffer, sizeof( tf->ft->buffer ), 0 ) , "Receiving" ); @@ -665,7 +665,7 @@ gboolean jabber_bs_recv_write_request( file_transfer_t *ft ) return FALSE; } - jabber_bs_recv_read( tf->streamhandle, 0 , 0 ); + jabber_bs_recv_read( tf->streamhandle, -1 , 0 ); return TRUE; } @@ -775,14 +775,16 @@ static xt_status jabber_bs_send_handle_reply(struct im_connection *ic, struct xt { /* using a proxy, abort listen */ - if ( tf->watch_in ) + if( tf->watch_in ) { b_event_remove( tf->watch_in ); tf->watch_in = 0; } - closesocket( tf->fd ); - tf->fd = 0; + if( tf->fd != -1 ) { + closesocket( tf->fd ); + tf->fd = -1; + } if ( bt->connect_timeout ) { @@ -797,7 +799,7 @@ static xt_status jabber_bs_send_handle_reply(struct im_connection *ic, struct xt if( strcmp( sh->jid, jid ) == 0 ) { bt->sh = sh; - jabber_bs_recv_handshake( bt, 0, 0 ); + jabber_bs_recv_handshake( bt, -1, 0 ); return XT_HANDLED; } } @@ -871,20 +873,80 @@ static xt_status jabber_bs_send_handle_activate( struct im_connection *ic, struc 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, "" ) == 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, "" ) == 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 ) { - char host[INET6_ADDRSTRLEN]; struct bs_transfer *bt; sha1_state_t sha; char hash_hex[41]; unsigned char hash[20]; int i,ret; - struct jabber_data *jd = tf->ic->proto_data; - jabber_streamhost_t sh; - GSList *streamhosts = jd->streamhosts; /* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */ sha1_init( &sha ); @@ -904,18 +966,9 @@ gboolean jabber_bs_send_start( struct jabber_transfer *tf ) tf->ft->free = jabber_bs_free_transfer; tf->ft->canceled = jabber_bs_canceled; - if ( !jabber_bs_send_listen( bt, &tf->saddr, sh.host = host, sh.port ) ) - return FALSE; - - bt->tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_send_handshake, bt ); - bt->connect_timeout = b_timeout_add( JABBER_BS_LISTEN_TIMEOUT * 1000, jabber_bs_connect_timeout, bt ); - - sh.jid = tf->ini_jid; + jabber_si_set_proxies( bt ); - /* temporarily add listen address to streamhosts, send the request and remove it */ - streamhosts = g_slist_prepend( streamhosts, &sh ); - ret = jabber_bs_send_request( tf, streamhosts); - streamhosts = g_slist_remove( streamhosts, &sh ); + ret = jabber_bs_send_request( tf, bt->streamhosts); return ret; } diff --git a/protocols/jabber/si.c b/protocols/jabber/si.c index 3afa30ff..e7cb2a17 100644 --- a/protocols/jabber/si.c +++ b/protocols/jabber/si.c @@ -38,10 +38,10 @@ void jabber_si_free_transfer( file_transfer_t *ft) jd->filetransfers = g_slist_remove( jd->filetransfers, tf ); - if( tf->fd ) + if( tf->fd != -1 ) { close( tf->fd ); - tf->fd = 0; + tf->fd = -1; } if( tf->disco_timeout ) @@ -128,8 +128,8 @@ gboolean jabber_si_waitfor_disco( gpointer data, gint fd, b_input_condition cond tf->disco_timeout_fired++; if( tf->bud->features && jd->have_streamhosts==1 ) { - jabber_si_transfer_start( tf ); tf->disco_timeout = 0; + jabber_si_transfer_start( tf ); return FALSE; } @@ -138,13 +138,14 @@ gboolean jabber_si_waitfor_disco( gpointer data, gint fd, b_input_condition cond return TRUE; if( !tf->bud->features && jd->have_streamhosts!=1 ) - imcb_file_canceled( tf->ft, "Couldn't get buddy's features or the server's" ); + imcb_log( tf->ic, "Couldn't get buddy's features nor discover all services of the server" ); else if( !tf->bud->features ) - imcb_file_canceled( tf->ft, "Couldn't get buddy's features" ); + imcb_log( tf->ic, "Couldn't get buddy's features" ); else - imcb_file_canceled( tf->ft, "Couldn't get server's features" ); + imcb_log( tf->ic, "Couldn't discover some of the server's services" ); tf->disco_timeout = 0; + jabber_si_transfer_start( tf ); return FALSE; } @@ -172,6 +173,7 @@ void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, tf->ic = ic; tf->ft = ft; + tf->fd = -1; tf->ft->data = tf; tf->ft->free = jabber_si_free_transfer; tf->ft->finished = jabber_si_finished; @@ -185,10 +187,13 @@ void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, if( !tf->bud->features ) jabber_iq_query_features( ic, bud->full_jid ); - if( jd->have_streamhosts!=1 ) { + /* If is not set don't check for proxies */ + if( ( jd->have_streamhosts!=1 ) && ( jd->streamhosts==NULL ) && + ( strstr( set_getstr( &ic->acc->set, "proxy" ), "" ) != 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. */ @@ -308,6 +313,7 @@ int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, st 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; -- cgit v1.2.3 From 4ac647dbcef152bebde7209f7c9cbbf8a5e0fc37 Mon Sep 17 00:00:00 2001 From: ulim Date: Mon, 4 Aug 2008 16:21:49 +0200 Subject: moved some stuff around in preperation for MSN merge. * both ends (proto&dcc) need to finish a transfer now for it to be finished * moved throughput calc. and some messages to dcc (no need to implement in protocols) --- dcc.c | 39 ++++++++++++++++++++++++++++++++++----- dcc.h | 4 ++++ protocols/ft.h | 2 ++ protocols/jabber/jabber.c | 12 ++++++++++++ protocols/jabber/s5bytestream.c | 14 +++++++------- protocols/jabber/si.c | 15 --------------- 6 files changed, 59 insertions(+), 27 deletions(-) diff --git a/dcc.c b/dcc.c index d815e9b7..909fedad 100644 --- a/dcc.c +++ b/dcc.c @@ -113,6 +113,17 @@ 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 ); @@ -154,6 +165,10 @@ file_transfer_t *dccs_send_start( struct im_connection *ic, char *user_nick, cha 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; } @@ -410,7 +425,8 @@ gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond ) file->bytes_transferred = bytes_received; if( file->bytes_transferred >= file->file_size ) { - dcc_finish( file ); + if( df->proto_finished ) + dcc_finish( file ); return FALSE; } @@ -492,6 +508,9 @@ gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond) 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; @@ -508,13 +527,16 @@ gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond) return dcc_abort( df, "Error sending DCC ACK, sent %d instead of 4 bytes", ackret ); } - if( !ft->write( df->ft, ft->buffer, ret ) ) - return FALSE; + if( df->bytes_sent == ret ) + ft->started = time( NULL ); if( done ) { - closesocket( fd ); - dcc_finish( ft ); + if( df->watch_out ) + b_event_remove( df->watch_out ); + + if( df->proto_finished ) + dcc_finish( ft ); df->watch_in = 0; return FALSE; @@ -571,6 +593,9 @@ gboolean dccs_send_write( file_transfer_t *file, char *data, unsigned int data_l 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 ) @@ -609,11 +634,15 @@ static void dcc_close( file_transfer_t *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 ); } diff --git a/dcc.h b/dcc.h index de24dac0..4be5a338 100644 --- a/dcc.h +++ b/dcc.h @@ -82,6 +82,10 @@ typedef struct dcc_file_transfer { /* 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 ); diff --git a/protocols/ft.h b/protocols/ft.h index 42333c90..1155f06f 100644 --- a/protocols/ft.h +++ b/protocols/ft.h @@ -170,4 +170,6 @@ file_transfer_t *imcb_file_send_start( struct im_connection *ic, char *user_nick 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/jabber.c b/protocols/jabber/jabber.c index 8b1d78db..987ef96e 100644 --- a/protocols/jabber/jabber.c +++ b/protocols/jabber/jabber.c @@ -244,6 +244,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 ); diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c index c492491b..7e4ca2ab 100644 --- a/protocols/jabber/s5bytestream.c +++ b/protocols/jabber/s5bytestream.c @@ -642,11 +642,11 @@ gboolean jabber_bs_recv_read( gpointer data, gint fd, b_input_condition cond ) if( ret == 0 ) return jabber_bs_abort( bt, "Remote end closed connection" ); - if( tf->bytesread == 0 ) - tf->ft->started = time( NULL ); - 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; @@ -700,16 +700,16 @@ gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, unsigned int l /* TODO: catch broken pipe */ ASSERTSOCKOP( ret = send( tf->fd, buffer, len, 0 ), "Sending" ); - if( tf->byteswritten == 0 ) - tf->ft->started = time( NULL ); - tf->byteswritten += ret; /* TODO: this should really not be fatal */ if( ret < len ) return jabber_bs_abort( bt, "send() sent %d instead of %d (send buffer too big!)", ret, len ); - bt->tf->watch_out = b_input_add( tf->fd, GAIM_INPUT_WRITE, jabber_bs_send_can_write, bt ); + 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; } diff --git a/protocols/jabber/si.c b/protocols/jabber/si.c index e7cb2a17..e7aeffc9 100644 --- a/protocols/jabber/si.c +++ b/protocols/jabber/si.c @@ -53,15 +53,6 @@ void jabber_si_free_transfer( file_transfer_t *ft) g_free( tf->sid ); } -/* file_transfer finished() callback */ -void jabber_si_finished( file_transfer_t *ft ) -{ - struct jabber_transfer *tf = ft->data; - time_t diff = time( NULL ) - ft->started ? : 1; - - imcb_log( tf->ic, "File %s transferred successfully at %d kb/s!" , ft->file_name, (int) ( ft->bytes_transferred / 1024 / diff ) ); -} - /* file_transfer canceled() callback */ void jabber_si_canceled( file_transfer_t *ft, char *reason ) { @@ -176,7 +167,6 @@ void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, tf->fd = -1; tf->ft->data = tf; tf->ft->free = jabber_si_free_transfer; - tf->ft->finished = jabber_si_finished; tf->bud = bud; ft->write = jabber_bs_send_write; @@ -301,10 +291,6 @@ int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, st /* Request is fine. */ - imcb_log( ic, "File transfer request from %s for %s (%zd kb). ", xt_find_attr( node, "from" ), name, size/1024 ); - - imcb_log( ic, "Accept the file transfer if you'd like the file. If you don't, issue the 'transfers reject' command."); - tf = g_new0( struct jabber_transfer, 1 ); tf->ini_jid = g_strdup( ini_jid ); @@ -317,7 +303,6 @@ int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, st tf->ft->data = tf; tf->ft->accept = jabber_si_answer_request; tf->ft->free = jabber_si_free_transfer; - tf->ft->finished = jabber_si_finished; tf->ft->canceled = jabber_si_canceled; jd->filetransfers = g_slist_prepend( jd->filetransfers, tf ); -- cgit v1.2.3 From a2b99ec7a1a02c57b2ef44663e56bdfab6063a4f Mon Sep 17 00:00:00 2001 From: ulim Date: Mon, 11 Aug 2008 00:17:58 +0200 Subject: Added MSN file transfer of type MSNFTP. Transfer is direct and the sender can not be firewalled. --- protocols/msn/Makefile | 2 +- protocols/msn/invitation.c | 666 +++++++++++++++++++++++++++++++++++++++++++++ protocols/msn/invitation.h | 82 ++++++ protocols/msn/msn.c | 5 + protocols/msn/msn.h | 4 + protocols/msn/sb.c | 95 +++---- 6 files changed, 797 insertions(+), 57 deletions(-) create mode 100644 protocols/msn/invitation.c create mode 100644 protocols/msn/invitation.h diff --git a/protocols/msn/Makefile b/protocols/msn/Makefile index 6a588613..dd5d46e2 100644 --- a/protocols/msn/Makefile +++ b/protocols/msn/Makefile @@ -9,7 +9,7 @@ -include ../../Makefile.settings # [SH] Program variables -objects = msn.o msn_util.o ns.o passport.o sb.o tables.o +objects = msn.o msn_util.o ns.o passport.o sb.o tables.o invitation.o CFLAGS += -Wall LFLAGS += -r diff --git a/protocols/msn/invitation.c b/protocols/msn/invitation.c new file mode 100644 index 00000000..4e24d54c --- /dev/null +++ b/protocols/msn/invitation.c @@ -0,0 +1,666 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway * +* * +* Copyright 2008 Uli Meis * +* Copyright 2006 Marijn Kruisselbrink and others * +\********************************************************************/ + +/* MSN module - File transfer support */ + +/* + 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 + */ + +#include "bitlbee.h" +#include "invitation.h" +#include "msn.h" + +#ifdef debug +#undef debug +#endif +#define debug(msg...) log_message( LOGLVL_INFO, msg ) + +static void msn_ftp_free( file_transfer_t *file ); +static void msn_ftpr_accept( file_transfer_t *file ); +static void msn_ftp_finished( file_transfer_t *file ); +static void msn_ftp_canceled( file_transfer_t *file, char *reason ); +static gboolean msn_ftpr_write_request( file_transfer_t *file ); + +static gboolean msn_ftp_connected( gpointer data, gint fd, b_input_condition cond ); +static gboolean msn_ftp_read( gpointer data, gint fd, b_input_condition cond ); +gboolean msn_ftps_write( file_transfer_t *file, char *buffer, unsigned int len ); + +/* + * Vararg wrapper for imcb_file_canceled(). + */ +gboolean msn_ftp_abort( file_transfer_t *file, 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 ); + imcb_file_canceled( file, error ); + return FALSE; +} + +/* very useful */ +#define ASSERTSOCKOP(op, msg) \ + if( (op) == -1 ) \ + return msn_ftp_abort( file , msg ": %s", strerror( errno ) ); + +/* + * Creates a listening socket and returns its address in host, port. + */ +gboolean msn_ftp_listen( msn_filetransfer_t *msn_file, char *host, char *port ) +{ + file_transfer_t *file = msn_file->dcc; + int fd,gret; + char hostname[ HOST_NAME_MAX + 1 ]; + struct addrinfo hints, *rp; + struct sockaddr_storage saddrst, *saddr = &saddrst; + 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 msn_ftp_abort( file, "getaddrinfo() failed: %s", gai_strerror( gret ) ); + + memcpy( saddr, rp->ai_addr, rp->ai_addrlen ); + + ASSERTSOCKOP( fd = msn_file->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 msn_ftp_abort( file, "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; +} + +void msn_ftp_invitation_cmd( struct im_connection *ic, char *who, int cookie, char *icmd, + char *trailer ) +{ + struct msn_message *m = g_new0( struct msn_message, 1 ); + + m->text = g_strdup_printf( "%s" + "Invitation-Command: %s\r\n" + "Invitation-Cookie: %u\r\n" + "%s", + MSN_INVITE_HEADERS, + icmd, + cookie, + trailer); + + m->who = g_strdup( who ); + + msn_sb_write_msg( ic, m ); +} + +void msn_ftp_cancel_invite( struct im_connection *ic, char *who, int cookie, char *code ) +{ + char buf[64]; + + g_snprintf( buf, sizeof( buf ), "Cancel-Code: %s\r\n", code ); + msn_ftp_invitation_cmd( ic, who, cookie, "CANCEL", buf ); +} + +void msn_ftp_transfer_request( struct im_connection *ic, file_transfer_t *file, char *who ) +{ + unsigned int cookie = time( NULL ); /* TODO: randomize */ + char buf[2048]; + + msn_filetransfer_t *msn_file = g_new0( msn_filetransfer_t, 1 ); + file->data = msn_file; + file->free = msn_ftp_free; + file->canceled = msn_ftp_canceled; + file->write = msn_ftps_write; + msn_file->md = ic->proto_data; + msn_file->invite_cookie = cookie; + msn_file->handle = g_strdup( who ); + msn_file->dcc = file; + msn_file->md->filetransfers = g_slist_prepend( msn_file->md->filetransfers, msn_file->dcc ); + msn_file->fd = -1; + msn_file->sbufpos = 3; + + g_snprintf( buf, sizeof( buf ), + "Application-Name: File Transfer\r\n" + "Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n" + "Application-File: %s\r\n" + "Application-FileSize: %zd\r\n", + file->file_name, + file->file_size); + + msn_ftp_invitation_cmd( msn_file->md->ic, msn_file->handle, cookie, "INVITE", buf ); + + imcb_file_recv_start( file ); +} + +void msn_invitation_invite( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) +{ + char *itype = msn_findheader( body, "Application-GUID:", blen ); + char *name, *size, *invitecookie, *reject = NULL; + user_t *u; + size_t isize; + file_transfer_t *file; + + if( !itype || strcmp( itype, "{5D3E02AB-6190-11d3-BBBB-00C04F795683}" ) != 0 ) { + /* Don't know what that is - don't care */ + char *iname = msn_findheader( body, "Application-Name:", blen ); + imcb_log( sb->ic, "Received unknown MSN invitation %s (%s) from %s", + itype ? : "with no GUID", iname ? iname : "no application name", handle ); + g_free( iname ); + reject = "REJECT_NOT_INSTALLED"; + } else if ( + !( name = msn_findheader( body, "Application-File:", blen )) || + !( size = msn_findheader( body, "Application-FileSize:", blen )) || + !( invitecookie = msn_findheader( body, "Invitation-Cookie:", blen)) || + !( isize = atoll( size ) ) ) { + imcb_log( sb->ic, "Received corrupted transfer request from %s" + "(name=%s, size=%s, invitecookie=%s)", + handle, name, size, invitecookie ); + reject = "REJECT"; + } else if ( !( u = user_findhandle( sb->ic, handle ) ) ) { + imcb_log( sb->ic, "Error in parsing transfer request, User '%s'" + "is not in contact list", handle ); + reject = "REJECT"; + } else if ( !( file = imcb_file_send_start( sb->ic, handle, name, isize ) ) ) { + imcb_log( sb->ic, "Error initiating transfer for request from %s for %s", + handle, name ); + reject = "REJECT"; + } else { + msn_filetransfer_t *msn_file = g_new0( msn_filetransfer_t, 1 ); + file->data = msn_file; + file->accept = msn_ftpr_accept; + file->free = msn_ftp_free; + file->finished = msn_ftp_finished; + file->canceled = msn_ftp_canceled; + file->write_request = msn_ftpr_write_request; + msn_file->md = sb->ic->proto_data; + msn_file->invite_cookie = cookie; + msn_file->handle = g_strdup( handle ); + msn_file->dcc = file; + msn_file->md->filetransfers = g_slist_prepend( msn_file->md->filetransfers, msn_file->dcc ); + msn_file->fd = -1; + } + + if( reject ) + msn_ftp_cancel_invite( sb->ic, sb->who, cookie, reject ); + + g_free( name ); + g_free( size ); + g_free( invitecookie ); + g_free( itype ); +} + +msn_filetransfer_t* msn_find_filetransfer( struct msn_data *md, unsigned int cookie, char *handle ) +{ + GSList *l; + + for( l = md->filetransfers; l; l = l->next ) { + msn_filetransfer_t *file = ( (file_transfer_t*) l->data )->data; + if( file->invite_cookie == cookie && strcmp( handle, file->handle ) == 0 ) { + return file; + } + } + return NULL; +} + +gboolean msn_ftps_connected( gpointer data, gint fd, b_input_condition cond ) +{ + file_transfer_t *file = data; + msn_filetransfer_t *msn_file = file->data; + struct sockaddr_storage clt_addr; + socklen_t ssize = sizeof( clt_addr ); + + debug( "Connected to MSNFTP client" ); + + ASSERTSOCKOP( msn_file->fd = accept( fd, (struct sockaddr *) &clt_addr, &ssize ), "Accepting connection" ); + + closesocket( fd ); + fd = msn_file->fd; + sock_make_nonblocking( fd ); + + msn_file->r_event_id = b_input_add( fd, GAIM_INPUT_READ, msn_ftp_read, file ); + + return FALSE; +} + +void msn_invitations_accept( msn_filetransfer_t *msn_file, struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) +{ + file_transfer_t *file = msn_file->dcc; + char buf[1024]; + unsigned int acookie = time ( NULL ); + char host[INET6_ADDRSTRLEN]; + char port[6]; + + msn_file->auth_cookie = acookie; + + if( !msn_ftp_listen( msn_file, host, port ) ) + return; + + msn_file->r_event_id = b_input_add( msn_file->fd, GAIM_INPUT_READ, msn_ftps_connected, file ); + + g_snprintf( buf, sizeof( buf ), + "IP-Address: %s\r\n" + "Port: %s\r\n" + "AuthCookie: %d\r\n" + "Launch-Application: FALSE\r\n" + "Request-Data: IP-Address:\r\n\r\n", + host, + port, + msn_file->auth_cookie ); + + msn_ftp_invitation_cmd( msn_file->md->ic, handle, msn_file->invite_cookie, "ACCEPT", buf ); +} + +void msn_invitationr_accept( msn_filetransfer_t *msn_file, struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) { + file_transfer_t *file = msn_file->dcc; + char *authcookie, *ip, *port; + + if( !( authcookie = msn_findheader( body, "AuthCookie:", blen ) ) || + !( ip = msn_findheader( body, "IP-Address:", blen ) ) || + !( port = msn_findheader( body, "Port:", blen ) ) ) { + msn_ftp_abort( file, "Received invalid accept reply" ); + } else if( + ( msn_file->fd = proxy_connect( ip, atoi( port ), msn_ftp_connected, file ) ) + < 0 ) { + msn_ftp_abort( file, "Error connecting to MSN client" ); + } else + msn_file->auth_cookie = strtoul( authcookie, NULL, 10 ); + + g_free( authcookie ); + g_free( ip ); + g_free( port ); +} + +void msn_invitation_accept( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) +{ + msn_filetransfer_t *msn_file = msn_find_filetransfer( sb->ic->proto_data, cookie, handle ); + file_transfer_t *file = msn_file ? msn_file->dcc : NULL; + + if( !msn_file ) + imcb_log( sb->ic, "Received invitation ACCEPT message for unknown invitation (already aborted?)" ); + else if( file->sending ) + msn_invitations_accept( msn_file, sb, handle, cookie, body, blen ); + else + msn_invitationr_accept( msn_file, sb, handle, cookie, body, blen ); +} + +void msn_invitation_cancel( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) +{ + msn_filetransfer_t *msn_file = msn_find_filetransfer( sb->ic->proto_data, cookie, handle ); + + if( !msn_file ) + imcb_log( sb->ic, "Received invitation CANCEL message for unknown invitation (already aborted?)" ); + else + msn_ftp_abort( msn_file->dcc, msn_findheader( body, "Cancel-Code:", blen ) ); +} + +int msn_ftp_write( file_transfer_t *file, char *format, ... ) +{ + msn_filetransfer_t *msn_file = file->data; + va_list params; + int st; + char *s; + + va_start( params, format ); + s = g_strdup_vprintf( format, params ); + va_end( params ); + + st = write( msn_file->fd, s, strlen( s ) ); + if( st != strlen( s ) ) + return msn_ftp_abort( file, "Error sending data over MSNFTP connection: %s", + strerror( errno ) ); + + g_free( s ); + return 1; +} + +gboolean msn_ftp_connected( gpointer data, gint fd, b_input_condition cond ) +{ + file_transfer_t *file = data; + msn_filetransfer_t *msn_file = file->data; + + debug( "Connected to MSNFTP server, starting authentication" ); + if( !msn_ftp_write( file, "VER MSNFTP\r\n" ) ) + return FALSE; + + sock_make_nonblocking( msn_file->fd ); + msn_file->r_event_id = b_input_add( msn_file->fd, GAIM_INPUT_READ, msn_ftp_read, file ); + + return FALSE; +} + +gboolean msn_ftp_handle_command( file_transfer_t *file, char* line ) +{ + msn_filetransfer_t *msn_file = file->data; + char **cmd = msn_linesplit( line ); + int count = 0; + if( cmd[0] ) while( cmd[++count] ); + + if( count < 1 ) + return msn_ftp_abort( file, "Missing command in MSNFTP communication" ); + + if( strcmp( cmd[0], "VER" ) == 0 ) { + if( strcmp( cmd[1], "MSNFTP" ) != 0 ) + return msn_ftp_abort( file, "Unsupported filetransfer protocol: %s", cmd[1] ); + if( file->sending ) + msn_ftp_write( file, "VER MSNFTP\r\n" ); + else + msn_ftp_write( file, "USR %s %u\r\n", msn_file->md->ic->acc->user, msn_file->auth_cookie ); + } else if( strcmp( cmd[0], "FIL" ) == 0 ) { + if( strtoul( cmd[1], NULL, 10 ) != file->file_size ) + return msn_ftp_abort( file, "FIL reply contains a different file size than the size in the invitation" ); + msn_ftp_write( file, "TFR\r\n" ); + msn_file->status |= MSN_TRANSFER_RECEIVING; + } else if( strcmp( cmd[0], "USR" ) == 0 ) { + if( ( strcmp( cmd[1], msn_file->handle ) != 0 ) || + ( strtoul( cmd[2], NULL, 10 ) != msn_file->auth_cookie ) ) + msn_ftp_abort( file, "Authentication failed. " + "Expected handle: %s (got %s), cookie: %u (got %s)", + msn_file->handle, cmd[1], + msn_file->auth_cookie, cmd[2] ); + msn_ftp_write( file, "FIL %zu\r\n", file->file_size); + } else if( strcmp( cmd[0], "TFR" ) == 0 ) { + file->write_request( file ); + } else if( strcmp( cmd[0], "BYE" ) == 0 ) { + unsigned int retcode = count > 1 ? atoi(cmd[1]) : 1; + + if( ( retcode==16777989 ) || ( retcode==16777987 ) ) + imcb_file_finished( file ); + else if( retcode==2147942405 ) + imcb_file_canceled( file, "Failure: receiver is out of disk space" ); + else if( retcode==2164261682 ) + imcb_file_canceled( file, "Failure: receiver cancelled the transfer" ); + else if( retcode==2164261683 ) + imcb_file_canceled( file, "Failure: sender has cancelled the transfer" ); + else if( retcode==2164261694 ) + imcb_file_canceled( file, "Failure: connection is blocked" ); + else { + char buf[128]; + + sprintf( buf, "Failure: unknown BYE code: %d", retcode); + imcb_file_canceled( file, buf ); + } + } else if( strcmp( cmd[0], "CCL" ) == 0 ) { + imcb_file_canceled( file, "Failure: receiver cancelled the transfer" ); + } else { + msn_ftp_abort( file, "Received invalid command %s from msn client", cmd[0] ); + } + return TRUE; +} + +gboolean msn_ftp_send( gpointer data, gint fd, b_input_condition cond ) +{ + file_transfer_t *file = data; + msn_filetransfer_t *msn_file = file->data; + + msn_file->w_event_id = 0; + + file->write_request( file ); + + 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). + * This got a bit complicated because (at least) amsn expects packets of size 2045. + */ +gboolean msn_ftps_write( file_transfer_t *file, char *buffer, unsigned int len ) +{ + msn_filetransfer_t *msn_file = file->data; + int ret, overflow; + + /* what we can't send now */ + overflow = msn_file->sbufpos + len - MSNFTP_PSIZE; + + /* append what we can do the send buffer */ + memcpy( msn_file->sbuf + msn_file->sbufpos, buffer, MIN( len, MSNFTP_PSIZE - msn_file->sbufpos ) ); + msn_file->sbufpos += MIN( len, MSNFTP_PSIZE - msn_file->sbufpos ); + + /* if we don't have enough for a full packet and there's more wait for it */ + if( ( msn_file->sbufpos < MSNFTP_PSIZE ) && + ( msn_file->data_sent + msn_file->sbufpos - 3 < file->file_size ) ) { + if( !msn_file->w_event_id ) + msn_file->w_event_id = b_input_add( msn_file->fd, GAIM_INPUT_WRITE, msn_ftp_send, file ); + return TRUE; + } + + /* Accumulated enough data, lets send something out */ + + msn_file->sbuf[0] = 0; + msn_file->sbuf[1] = ( msn_file->sbufpos - 3 ) & 0xff; + msn_file->sbuf[2] = ( ( msn_file->sbufpos - 3 ) >> 8 ) & 0xff; + + ASSERTSOCKOP( ret = send( msn_file->fd, msn_file->sbuf, msn_file->sbufpos, 0 ), "Sending" ); + + msn_file->data_sent += ret - 3; + + /* TODO: this should really not be fatal */ + if( ret < msn_file->sbufpos ) + return msn_ftp_abort( file, "send() sent %d instead of %d (send buffer full!)", ret, msn_file->sbufpos ); + + msn_file->sbufpos = 3; + + if( overflow > 0 ) { + while( overflow > ( MSNFTP_PSIZE - 3 ) ) { + if( !msn_ftps_write( file, buffer + len - overflow, MSNFTP_PSIZE - 3 ) ) + return FALSE; + overflow -= MSNFTP_PSIZE - 3; + } + return msn_ftps_write( file, buffer + len - overflow, overflow ); + } + + if( msn_file->data_sent == file->file_size ) { + if( msn_file->w_event_id ) { + b_event_remove( msn_file->w_event_id ); + msn_file->w_event_id = 0; + } + } else { + /* we might already be listening if this is data from an overflow */ + if( !msn_file->w_event_id ) + msn_file->w_event_id = b_input_add( msn_file->fd, GAIM_INPUT_WRITE, msn_ftp_send, file ); + } + + return TRUE; +} + +/* Binary part of the file transfer protocol */ +gboolean msn_ftpr_read( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + int st; + unsigned char buf[3]; + + if( msn_file->data_remaining ) { + msn_file->r_event_id = 0; + + ASSERTSOCKOP( st = read( msn_file->fd, file->buffer, MIN( sizeof( file->buffer ), msn_file->data_remaining ) ), "Receiving" ); + + if( st == 0 ) + return msn_ftp_abort( file, "Remote end closed connection"); + + msn_file->data_sent += st; + + msn_file->data_remaining -= st; + + file->write( file, file->buffer, st ); + + if( msn_file->data_sent >= file->file_size ) + imcb_file_finished( file ); + + return FALSE; + } else { + ASSERTSOCKOP( st = read( msn_file->fd, buf, 1 ), "Receiving" ); + if( st == 0 ) { + return msn_ftp_abort( file, "read returned EOF while reading data header from msn client" ); + } else if( buf[0] == '\r' || buf[0] == '\n' ) { + debug( "Discarding extraneous newline" ); + } else if( buf[0] != 0 ) { + msn_ftp_abort( file, "Remote end canceled the transfer"); + /* don't really care about these last 2 (should be 0,0) */ + read( msn_file->fd, buf, 2 ); + return FALSE; + } else { + unsigned int size; + ASSERTSOCKOP( st = read( msn_file->fd, buf, 2 ), "Receiving" ); + if( st < 2 ) + return msn_ftp_abort( file, "read returned EOF while reading data header from msn client" ); + + size = buf[0] + ((unsigned int) buf[1] << 8); + msn_file->data_remaining = size; + } + } + return TRUE; +} + +/* Text mode part of the file transfer protocol */ +gboolean msn_ftp_txtproto( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + int i = msn_file->tbufpos, st; + char *tbuf = msn_file->tbuf; + + ASSERTSOCKOP( st = read( msn_file->fd, + tbuf + msn_file->tbufpos, + sizeof( msn_file->tbuf ) - msn_file->tbufpos ), + "Receiving" ); + + if( st == 0 ) + return msn_ftp_abort( file, "read returned EOF while reading text from msn client" ); + + msn_file->tbufpos += st; + + do { + for( ;i < msn_file->tbufpos; i++ ) { + if( tbuf[i] == '\n' || tbuf[i] == '\r' ) { + tbuf[i] = '\0'; + if( i > 0 ) + msn_ftp_handle_command( file, tbuf ); + else + while( tbuf[i] == '\n' || tbuf[i] == '\r' ) i++; + memmove( tbuf, tbuf + i + 1, msn_file->tbufpos - i - 1 ); + msn_file->tbufpos -= i + 1; + i = 0; + break; + } + } + } while ( i < msn_file->tbufpos ); + + if( msn_file->tbufpos == sizeof( msn_file->tbuf ) ) + return msn_ftp_abort( file, + "Line exceeded %d bytes in text protocol", + sizeof( msn_file->tbuf ) ); + return TRUE; +} + +gboolean msn_ftp_read( gpointer data, gint fd, b_input_condition cond ) +{ + file_transfer_t *file = data; + msn_filetransfer_t *msn_file = file->data; + + if( msn_file->status & MSN_TRANSFER_RECEIVING ) + return msn_ftpr_read( file ); + else + return msn_ftp_txtproto( file ); +} + +void msn_ftp_free( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + + if( msn_file->r_event_id ) + b_event_remove( msn_file->r_event_id ); + + if( msn_file->w_event_id ) + b_event_remove( msn_file->w_event_id ); + + if( msn_file->fd != -1 ) + closesocket( msn_file->fd ); + + msn_file->md->filetransfers = g_slist_remove( msn_file->md->filetransfers, msn_file->dcc ); + + g_free( msn_file->handle ); + + g_free( msn_file ); +} + +void msn_ftpr_accept( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + + msn_ftp_invitation_cmd( msn_file->md->ic, msn_file->handle, msn_file->invite_cookie, "ACCEPT", + "Launch-Application: FALSE\r\n" + "Request-Data: IP-Address:\r\n"); +} + +void msn_ftp_finished( file_transfer_t *file ) +{ + msn_ftp_write( file, "BYE 16777989\r\n" ); +} + +void msn_ftp_canceled( file_transfer_t *file, char *reason ) +{ + msn_filetransfer_t *msn_file = file->data; + + msn_ftp_cancel_invite( msn_file->md->ic, msn_file->handle, + msn_file->invite_cookie, + file->status & FT_STATUS_TRANSFERRING ? + "FTTIMEOUT" : + "FAIL" ); + + imcb_log( msn_file->md->ic, "File transfer aborted: %s", reason ); +} + +gboolean msn_ftpr_write_request( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + if( msn_file->r_event_id != 0 ) { + msn_ftp_abort( file, + "BUG in MSN file transfer:" + "write_request called when" + "already watching for input" ); + return FALSE; + } + + msn_file->r_event_id = + b_input_add( msn_file->fd, GAIM_INPUT_READ, msn_ftp_read, file ); + + return TRUE; +} diff --git a/protocols/msn/invitation.h b/protocols/msn/invitation.h new file mode 100644 index 00000000..289efd7b --- /dev/null +++ b/protocols/msn/invitation.h @@ -0,0 +1,82 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway * +* * +* Copyright 2006 Marijn Kruisselbrink and others * +\********************************************************************/ + +/* MSN module - File transfer support */ + +/* + 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 _MSN_INVITATION_H +#define _MSN_INVITATION_H + +#include "msn.h" + +#define MSN_INVITE_HEADERS "MIME-Version: 1.0\r\n" \ + "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n" \ + "\r\n" + +#define MSNFTP_PSIZE 2048 + +typedef enum { + MSN_TRANSFER_RECEIVING = 1, + MSN_TRANSFER_SENDING = 2 +} msn_filetransfer_status_t; + +typedef struct msn_filetransfer +{ +/* Generic invitation data */ + /* msn_data instance this invitation was received with. */ + struct msn_data *md; + /* Cookie specifying this invitation. */ + unsigned int invite_cookie; + /* Handle of user that started this invitation. */ + char *handle; + +/* File transfer specific data */ + /* Current status of the file transfer. */ + msn_filetransfer_status_t status; + /* Pointer to the dcc structure for this transfer. */ + file_transfer_t *dcc; + /* Socket the transfer is taking place over. */ + int fd; + /* Cookie received in the original invitation, this must be sent as soon as + a connection has been established. */ + unsigned int auth_cookie; + /* Data remaining to be received in the current packet. */ + unsigned int data_remaining; + /* Buffer containing received, but unprocessed text. */ + char tbuf[256]; + unsigned int tbufpos; + + unsigned int data_sent; + + gint r_event_id; + gint w_event_id; + + unsigned char sbuf[2048]; + int sbufpos; + +} msn_filetransfer_t; + +void msn_invitation_invite( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ); +void msn_invitation_accept( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ); +void msn_invitation_cancel( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ); + +#endif diff --git a/protocols/msn/msn.c b/protocols/msn/msn.c index 046b2772..590a1382 100644 --- a/protocols/msn/msn.c +++ b/protocols/msn/msn.c @@ -76,6 +76,10 @@ static void msn_logout( struct im_connection *ic ) if( md ) { + while( md->filetransfers ) { + imcb_file_canceled( md->filetransfers->data, "Closing msn connection" ); + } + if( md->fd >= 0 ) closesocket( md->fd ); @@ -337,6 +341,7 @@ void msn_initmodule() ret->rem_deny = msn_rem_deny; ret->send_typing = msn_send_typing; ret->handle_cmp = g_strcasecmp; + ret->transfer_request = msn_ftp_transfer_request; register_protocol(ret); } diff --git a/protocols/msn/msn.h b/protocols/msn/msn.h index 7c849acf..e2badbf9 100644 --- a/protocols/msn/msn.h +++ b/protocols/msn/msn.h @@ -68,6 +68,7 @@ struct msn_data GSList *switchboards; int sb_failures; time_t first_sb_failure; + GSList *filetransfers; const struct msn_away_state *away_state; int buddycount; @@ -180,4 +181,7 @@ void msn_sb_destroy( struct msn_switchboard *sb ); gboolean msn_sb_connected( gpointer data, gint source, b_input_condition cond ); int msn_sb_write_msg( struct im_connection *ic, struct msn_message *m ); +/* invitation.c */ +void msn_ftp_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who ); + #endif //_MSN_H diff --git a/protocols/msn/sb.c b/protocols/msn/sb.c index e9526234..b60a9a8b 100644 --- a/protocols/msn/sb.c +++ b/protocols/msn/sb.c @@ -28,6 +28,7 @@ #include "msn.h" #include "passport.h" #include "md5.h" +#include "invitation.h" static gboolean msn_sb_callback( gpointer data, gint source, b_input_condition cond ); static int msn_sb_command( gpointer data, char **cmd, int num_parts ); @@ -167,7 +168,16 @@ int msn_sb_sendmessage( struct msn_switchboard *sb, char *text ) int i, j; /* Build the message. Convert LF to CR-LF for normal messages. */ - if( strcmp( text, TYPING_NOTIFICATION_MESSAGE ) != 0 ) + if( strcmp( text, TYPING_NOTIFICATION_MESSAGE ) == 0 ) + { + i = strlen( MSN_TYPING_HEADERS ) + strlen( sb->ic->acc->user ); + buf = g_new0( char, i ); + i = g_snprintf( buf, i, MSN_TYPING_HEADERS, sb->ic->acc->user ); + } else if( strncmp( text, MSN_INVITE_HEADERS, sizeof( MSN_INVITE_HEADERS ) - 1 ) == 0 ) + { + buf = g_strdup( text ); + i = strlen( buf ); + } else { buf = g_new0( char, sizeof( MSN_MESSAGE_HEADERS ) + strlen( text ) * 2 + 1 ); i = strlen( MSN_MESSAGE_HEADERS ); @@ -181,12 +191,6 @@ int msn_sb_sendmessage( struct msn_switchboard *sb, char *text ) buf[i++] = text[j]; } } - else - { - i = strlen( MSN_TYPING_HEADERS ) + strlen( sb->ic->acc->user ); - buf = g_new0( char, i ); - i = g_snprintf( buf, i, MSN_TYPING_HEADERS, sb->ic->acc->user ); - } /* Build the final packet (MSG command + the message). */ packet = g_strdup_printf( "MSG %d N %d\r\n%s", ++sb->trId, i, buf ); @@ -686,62 +690,41 @@ static int msn_sb_message( gpointer data, char *msg, int msglen, char **cmd, int } else if( g_strncasecmp( ct, "text/x-msmsgsinvite", 19 ) == 0 ) { - char *itype = msn_findheader( body, "Application-GUID:", blen ); - char buf[1024]; + char *command = msn_findheader( body, "Invitation-Command:", blen ); + char *cookie = msn_findheader( body, "Invitation-Cookie:", blen ); + unsigned int icookie; g_free( ct ); - *buf = 0; - - if( !itype ) - return( 1 ); - - /* File transfer. */ - if( strcmp( itype, "{5D3E02AB-6190-11d3-BBBB-00C04F795683}" ) == 0 ) - { - char *name = msn_findheader( body, "Application-File:", blen ); - char *size = msn_findheader( body, "Application-FileSize:", blen ); - - if( name && size ) - { - g_snprintf( buf, sizeof( buf ), "<< \x02""BitlBee\x02"" - Filetransfer: `%s', %s bytes >>\n" - "Filetransfers are not supported by BitlBee for now...", name, size ); - } - else - { - strcpy( buf, "<< \x02""BitlBee\x02"" - Corrupted MSN filetransfer invitation message >>" ); - } - - if( name ) g_free( name ); - if( size ) g_free( size ); - } - else - { - char *iname = msn_findheader( body, "Application-Name:", blen ); - - g_snprintf( buf, sizeof( buf ), "<< \x02""BitlBee\x02"" - Unknown MSN invitation - %s (%s) >>", - itype, iname ? iname : "no name" ); - - if( iname ) g_free( iname ); + /* Every invite should have both a Command and Cookie header */ + if( !command || !cookie ) { + g_free( command ); + g_free( cookie ); + imcb_log( ic, "Warning: No command or cookie from %s", sb->who ); + return 1; } - g_free( itype ); + icookie = strtoul( cookie, NULL, 10 ); + g_free( cookie ); - if( !*buf ) - return( 1 ); - - if( sb->who ) - { - imcb_buddy_msg( ic, cmd[1], buf, 0, 0 ); - } - else if( sb->chat ) - { - imcb_chat_msg( sb->chat, cmd[1], buf, 0, 0 ); - } - else - { - /* PANIC! */ + if( g_strncasecmp( command, "INVITE", 6 ) == 0 ) { + msn_invitation_invite( sb, cmd[1], icookie, body, blen ); + } else if( g_strncasecmp( command, "ACCEPT", 6 ) == 0 ) { + msn_invitation_accept( sb, cmd[1], icookie, body, blen ); + } else if( g_strncasecmp( command, "CANCEL", 6 ) == 0 ) { + msn_invitation_cancel( sb, cmd[1], icookie, body, blen ); + } else { + imcb_log( ic, "Warning: Received invalid invitation with " + "command %s from %s", command, sb->who ); } + + g_free( command ); + } + else if( g_strncasecmp( ct, "application/x-msnmsgrp2p", 24 ) == 0 ) + { + imcb_error( sb->ic, "Cannot receive file from %s: BitlBee does not " + "support msnmsgrp2p yet.", sb->who ); + g_free( ct ); } else if( g_strncasecmp( ct, "text/x-msmsgscontrol", 20 ) == 0 ) { -- cgit v1.2.3 From 92f4ec5d9d480247281c50c163c19b1ea438c1b3 Mon Sep 17 00:00:00 2001 From: ulim Date: Mon, 11 Aug 2008 01:51:48 +0200 Subject: fixes bug on 32bit archs in DCC code. Shouldn't atoi be the same on 64 and 32bits? Strange. --- dcc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dcc.c b/dcc.c index 909fedad..2bb85ad4 100644 --- a/dcc.c +++ b/dcc.c @@ -702,7 +702,7 @@ file_transfer_t *dcc_request( struct im_connection *ic, char *line ) /* 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 ) ) }; + struct in_addr ipaddr = { .s_addr = htonl( strtoul( input + pmatch[5].rm_so, NULL, 10 ) ) }; host = inet_ntoa( ipaddr ); } else { -- cgit v1.2.3 From 66be7849ef6b74c39bc8f1dc1d96bc4788eb50a0 Mon Sep 17 00:00:00 2001 From: ulim Date: Mon, 11 Aug 2008 16:31:03 +0200 Subject: copied ulibc/BSD ifdefs over to invitation.c. I'll have to move these to a generic header file eventually. --- protocols/msn/invitation.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/protocols/msn/invitation.c b/protocols/msn/invitation.c index 4e24d54c..021764d2 100644 --- a/protocols/msn/invitation.c +++ b/protocols/msn/invitation.c @@ -28,6 +28,20 @@ #include "invitation.h" #include "msn.h" +/* Some ifdefs for ulibc and apparently also BSD (Thanks to Whoopie) */ +#ifndef HOST_NAME_MAX +#include +#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 + #ifdef debug #undef debug #endif -- cgit v1.2.3 From a02f34fb047af728f991ced3688c5e804c130878 Mon Sep 17 00:00:00 2001 From: ulim Date: Tue, 12 Aug 2008 01:07:12 +0200 Subject: Added conf entries and lib/ftutil.[ch]. ft_listen = :;: to specify listening addresses for the bitlbee<->client connection and the bitlbee<->IM peer connection, respectively. ft_max_size should be obvious. ft_max_kbps should limit the kilobits per second per transfer (not implemented yet). --- Makefile | 2 +- conf.c | 28 ++++++++++++- conf.h | 4 +- dcc.c | 79 +++++++------------------------------ lib/Makefile | 2 +- protocols/jabber/s5bytestream.c | 87 ++++++++--------------------------------- protocols/msn/invitation.c | 70 +++------------------------------ 7 files changed, 69 insertions(+), 203 deletions(-) diff --git a/Makefile b/Makefile index 0c07b91b..2aae8c83 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ # 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 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 +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 lib/ftutil.h subdirs = lib protocols ifeq ($(TARGET),i586-mingw32msvc) diff --git a/conf.c b/conf.c index 02b1eb98..19fe7020 100644 --- a/conf.c +++ b/conf.c @@ -62,7 +62,9 @@ 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; + conf->ft_max_size = SIZE_MAX; + conf->ft_max_kbps = G_MAXUINT; + conf->ft_listen = NULL; proxytype = 0; i = conf_loadini( conf, global.conf_file ); @@ -306,6 +308,30 @@ static int conf_loadini( conf_t *conf, char *file ) g_free( conf->user ); conf->user = g_strdup( ini->value ); } + else if( g_strcasecmp( ini->key, "ft_max_size" ) == 0 ) + { + size_t ft_max_size; + if( sscanf( ini->value, "%zu", &ft_max_size ) != 1 ) + { + fprintf( stderr, "Invalid %s value: %s\n", ini->key, ini->value ); + return 0; + } + conf->ft_max_size = ft_max_size; + } + else if( g_strcasecmp( ini->key, "ft_max_kbps" ) == 0 ) + { + if( sscanf( ini->value, "%d", &i ) != 1 ) + { + fprintf( stderr, "Invalid %s value: %s\n", ini->key, ini->value ); + return 0; + } + conf->ft_max_kbps = i; + } + else if( g_strcasecmp( ini->key, "ft_listen" ) == 0 ) + { + g_free( conf->ft_listen ); + conf->ft_listen = g_strdup( ini->value ); + } else { fprintf( stderr, "Error: Unknown setting `%s` in configuration file.\n", ini->key ); diff --git a/conf.h b/conf.h index 7fcd0ede..b39845ad 100644 --- a/conf.h +++ b/conf.h @@ -49,7 +49,9 @@ typedef struct conf int ping_interval; int ping_timeout; char *user; - size_t max_filetransfer_size; + size_t ft_max_size; + int ft_max_kbps; + char *ft_listen; } conf_t; G_GNUC_MALLOC conf_t *conf_load( int argc, char *argv[] ); diff --git a/dcc.c b/dcc.c index 2bb85ad4..c54e9169 100644 --- a/dcc.c +++ b/dcc.c @@ -28,20 +28,7 @@ #include #include #include - -/* Some ifdefs for ulibc (Thanks to Whoopie) */ -#ifndef HOST_NAME_MAX -#include -#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 +#include "lib/ftutil.h" /* * Since that might be confusing a note on naming: @@ -79,12 +66,12 @@ 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 ); +gboolean dcc_abort( dcc_file_transfer_t *df, char *reason, ... ); /* 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 ) @@ -142,9 +129,12 @@ file_transfer_t *dccs_send_start( struct im_connection *ic, char *user_nick, cha { file_transfer_t *file; dcc_file_transfer_t *df; - struct sockaddr_storage *saddr; + struct sockaddr_storage saddr; + char *errmsg; + char host[INET6_ADDRSTRLEN]; + char port[6]; - if( file_size > global.conf->max_filetransfer_size ) + if( file_size > global.conf->ft_max_size ) return NULL; df = dcc_alloc_transfer( file_name, file_size, ic ); @@ -152,11 +142,16 @@ file_transfer_t *dccs_send_start( struct im_connection *ic, char *user_nick, cha file->write = dccs_send_write; /* listen and request */ - if( !dcc_listen( df, &saddr ) || - !dccs_send_request( df, user_nick, saddr ) ) + + if( ( df->fd = ft_listen( &saddr, host, port, TRUE, &errmsg ) ) == -1 ) { + dcc_abort( df, "Failed to listen locally, check your ft_listen setting in bitlbee.conf: %s", errmsg ); return NULL; + } - g_free( saddr ); + file->status = FT_STATUS_LISTENING; + + if( !dccs_send_request( df, user_nick, &saddr ) ) + return NULL; /* watch */ df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_send_proto, df ); @@ -261,50 +256,6 @@ int dccs_send_request( struct dcc_file_transfer *df, char *user_nick, struct soc 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 */ diff --git a/lib/Makefile b/lib/Makefile index 03fef1ab..3d128b5a 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -9,7 +9,7 @@ -include ../Makefile.settings # [SH] Program variables -objects = arc.o base64.o $(EVENT_HANDLER) http_client.o ini.o md5.o misc.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o +objects = arc.o base64.o $(EVENT_HANDLER) http_client.o ini.o md5.o misc.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o ftutil.o CFLAGS += -Wall LFLAGS += -r diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c index 7e4ca2ab..3c5ce503 100644 --- a/protocols/jabber/s5bytestream.c +++ b/protocols/jabber/s5bytestream.c @@ -23,22 +23,9 @@ #include "jabber.h" #include "sha1.h" +#include "lib/ftutil.h" #include -/* Some ifdefs for ulibc (Thanks to Whoopie) */ -#ifndef HOST_NAME_MAX -#include -#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; @@ -114,7 +101,6 @@ int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, stru 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 ); @@ -901,7 +887,7 @@ 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 *proxy, *next, *errmsg = NULL; char port[6]; char host[INET6_ADDRSTRLEN]; jabber_streamhost_t *sh, *sh2; @@ -912,15 +898,21 @@ void jabber_si_set_proxies( struct bs_transfer *bt ) if( ( next = strchr( proxy, ';' ) ) ) *next++ = '\0'; - if( ( strcmp( proxy, "" ) == 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 ); + if( strcmp( proxy, "" ) == 0 ) { + if( ( tf->fd = ft_listen( &tf->saddr, host, port, FALSE, &errmsg ) ) != -1 ) { + 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 ); + 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 { + imcb_log( tf->ic, "Transferring file %s: couldn't listen locally(non fatal, check your ft_listen setting in bitlbee.conf): %s", + tf->ft->file_name, + errmsg ); + } } else if( strcmp( proxy, "" ) == 0 ) { while ( streamhosts ) { sh = g_new0( jabber_streamhost_t, 1 ); @@ -1021,53 +1013,6 @@ gboolean jabber_bs_send_handshake_abort(struct bs_transfer *bt, char *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 */ diff --git a/protocols/msn/invitation.c b/protocols/msn/invitation.c index 021764d2..f44155fa 100644 --- a/protocols/msn/invitation.c +++ b/protocols/msn/invitation.c @@ -27,20 +27,7 @@ #include "bitlbee.h" #include "invitation.h" #include "msn.h" - -/* Some ifdefs for ulibc and apparently also BSD (Thanks to Whoopie) */ -#ifndef HOST_NAME_MAX -#include -#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 +#include "lib/ftutil.h" #ifdef debug #undef debug @@ -78,54 +65,6 @@ gboolean msn_ftp_abort( file_transfer_t *file, char *format, ... ) if( (op) == -1 ) \ return msn_ftp_abort( file , msg ": %s", strerror( errno ) ); -/* - * Creates a listening socket and returns its address in host, port. - */ -gboolean msn_ftp_listen( msn_filetransfer_t *msn_file, char *host, char *port ) -{ - file_transfer_t *file = msn_file->dcc; - int fd,gret; - char hostname[ HOST_NAME_MAX + 1 ]; - struct addrinfo hints, *rp; - struct sockaddr_storage saddrst, *saddr = &saddrst; - 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 msn_ftp_abort( file, "getaddrinfo() failed: %s", gai_strerror( gret ) ); - - memcpy( saddr, rp->ai_addr, rp->ai_addrlen ); - - ASSERTSOCKOP( fd = msn_file->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 msn_ftp_abort( file, "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; -} - void msn_ftp_invitation_cmd( struct im_connection *ic, char *who, int cookie, char *icmd, char *trailer ) { @@ -281,11 +220,14 @@ void msn_invitations_accept( msn_filetransfer_t *msn_file, struct msn_switchboar unsigned int acookie = time ( NULL ); char host[INET6_ADDRSTRLEN]; char port[6]; + char *errmsg; msn_file->auth_cookie = acookie; - if( !msn_ftp_listen( msn_file, host, port ) ) - return; + if( ( msn_file->fd = ft_listen( NULL, host, port, FALSE, &errmsg ) ) == -1 ) { + msn_ftp_abort( file, "Failed to listen locally, check your ft_listen setting in bitlbee.conf: %s", errmsg ); + return; + } msn_file->r_event_id = b_input_add( msn_file->fd, GAIM_INPUT_READ, msn_ftps_connected, file ); -- cgit v1.2.3 From 5d550c51a5e9c9f48f26283f0ea3fee2d4945feb Mon Sep 17 00:00:00 2001 From: ulim Date: Tue, 12 Aug 2008 11:24:09 +0200 Subject: Added lib/ftutil.[ch]. yes, that was supposed to happen in the last commit ;) --- lib/ftutil.c | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/ftutil.h | 24 +++++++++++ 2 files changed, 159 insertions(+) create mode 100644 lib/ftutil.c create mode 100644 lib/ftutil.h diff --git a/lib/ftutil.c b/lib/ftutil.c new file mode 100644 index 00000000..cfe06006 --- /dev/null +++ b/lib/ftutil.c @@ -0,0 +1,135 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Utility functions for file transfer * +* * +* Copyright 2008 Uli Meis * +* * +* 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. * +* * +\***************************************************************************/ + +#define BITLBEE_CORE +#include "bitlbee.h" +#include +#include +#include "lib/ftutil.h" + +/* Some ifdefs for ulibc and apparently also BSD (Thanks to Whoopie) */ +#ifndef HOST_NAME_MAX +#include +#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 + +#define ASSERTSOCKOP(op, msg) \ + if( (op) == -1 ) {\ + sprintf( errmsg , msg ": %s", strerror( errno ) ); \ + return -1; } + +/* + * Creates a listening socket and returns it in saddr_ptr. + */ +int ft_listen( struct sockaddr_storage *saddr_ptr, char *hostp, char *port, int for_bitlbee_client, char **errptr ) +{ + int fd,gret,saddrlen; + struct addrinfo hints, *rp; + socklen_t ssize = sizeof( struct sockaddr_storage ); + struct sockaddr_storage saddrs, *saddr = &saddrs; + static char errmsg[1024]; + char host[ HOST_NAME_MAX + 1 ]; + char *ftlisten = global.conf->ft_listen; + + *errptr = errmsg; + + sprintf( port, "0" ); + + /* Format is [:];[:] where + * A is for connections with the bitlbee client (DCC) + * and B is for connections with IM peers. + */ + if( ftlisten ) { + char *scolon = strchr( ftlisten, ';' ); + char *colon; + + if( scolon ) { + if( for_bitlbee_client ) { + *scolon = '\0'; + sprintf( host, ftlisten ); + *scolon = ';'; + } else { + sprintf( host, scolon + 1 ); + } + } else { + sprintf( host, ftlisten ); + } + + if( ( colon = strchr( host, ':' ) ) ) { + *colon = '\0'; + sprintf( port, colon + 1 ); + } + } else { + ASSERTSOCKOP( gethostname( host, HOST_NAME_MAX + 1 ), "gethostname()" ); + } + + memset( &hints, 0, sizeof( struct addrinfo ) ); + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_NUMERICSERV; + + if ( ( gret = getaddrinfo( host, port, &hints, &rp ) ) != 0 ) { + sprintf( errmsg, "getaddrinfo() failed: %s", gai_strerror( gret ) ); + return -1; + } + + saddrlen = rp->ai_addrlen; + + memcpy( saddr, rp->ai_addr, saddrlen ); + + freeaddrinfo( rp ); + + ASSERTSOCKOP( fd = socket( saddr->ss_family, SOCK_STREAM, 0 ), "Opening socket" ); + + ASSERTSOCKOP( bind( fd, ( struct sockaddr *)saddr, saddrlen ), "Binding socket" ); + + 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 ) ) { + sprintf( errmsg, "inet_ntop failed on listening socket" ); + return -1; + } + + 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 ) ); + + if( saddr_ptr ) + memcpy( saddr_ptr, saddr, saddrlen ); + + strcpy( hostp, host ); + + return fd; +} diff --git a/lib/ftutil.h b/lib/ftutil.h new file mode 100644 index 00000000..5f2780ef --- /dev/null +++ b/lib/ftutil.h @@ -0,0 +1,24 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Utility functions for file transfer * +* * +* Copyright 2008 Uli Meis * +* * +* 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. * +* * +\***************************************************************************/ + +int ft_listen( struct sockaddr_storage *saddr_ptr, char *host, char *port, int for_bitlbee_client, char **errptr ); -- cgit v1.2.3 From aac40178a6669e20855b7f5d3cc6a82cba10042e Mon Sep 17 00:00:00 2001 From: ulim Date: Tue, 12 Aug 2008 13:04:37 +0200 Subject: More hints for getaddrinfo(). Hopefully solves a problem on FreeBSD. --- dcc.c | 3 +++ lib/ftutil.c | 4 ---- lib/ftutil.h | 4 ++++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/dcc.c b/dcc.c index c54e9169..b6345a92 100644 --- a/dcc.c +++ b/dcc.c @@ -668,6 +668,9 @@ file_transfer_t *dcc_request( struct im_connection *ic, char *line ) filesize = atoll( input + pmatch[9].rm_so ); memset( &hints, 0, sizeof ( struct addrinfo ) ); + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_NUMERICSERV; + if ( ( gret = getaddrinfo( host, port, &hints, &rp ) ) ) { g_free( input ); diff --git a/lib/ftutil.c b/lib/ftutil.c index cfe06006..169a6480 100644 --- a/lib/ftutil.c +++ b/lib/ftutil.c @@ -37,10 +37,6 @@ #endif #endif -#ifndef AI_NUMERICSERV -#define AI_NUMERICSERV 0x0400 /* Don't use name resolution. */ -#endif - #define ASSERTSOCKOP(op, msg) \ if( (op) == -1 ) {\ sprintf( errmsg , msg ": %s", strerror( errno ) ); \ diff --git a/lib/ftutil.h b/lib/ftutil.h index 5f2780ef..636fcbd0 100644 --- a/lib/ftutil.h +++ b/lib/ftutil.h @@ -21,4 +21,8 @@ * * \***************************************************************************/ +#ifndef AI_NUMERICSERV +#define AI_NUMERICSERV 0x0400 /* Don't use name resolution. */ +#endif + int ft_listen( struct sockaddr_storage *saddr_ptr, char *host, char *port, int for_bitlbee_client, char **errptr ); -- cgit v1.2.3 From 1c3008ac0b2b29f7e14ec9b874af3277c511c7a4 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 13 Dec 2009 14:48:56 +0000 Subject: No functional changes, just some style "fixes". Although I admit I'm somewhat growing out of my own coding style, I do try to keep things consistent at least within files. Comments are now in reviewboard: http://code.bitlbee.org/rb/r/13/ --- dcc.c | 32 ++++++++++++++++++-------------- protocols/jabber/Makefile | 2 +- protocols/jabber/iq.c | 38 +++++++++++++++++++++++--------------- protocols/jabber/jabber.c | 4 ++-- protocols/jabber/jabber.h | 2 +- protocols/msn/Makefile | 2 +- protocols/msn/msn.c | 2 +- protocols/msn/sb.c | 6 ++++-- 8 files changed, 51 insertions(+), 37 deletions(-) diff --git a/dcc.c b/dcc.c index b6345a92..655c98b9 100644 --- a/dcc.c +++ b/dcc.c @@ -114,7 +114,8 @@ void imcb_file_finished( file_transfer_t *file ) 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); + 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++; @@ -143,7 +144,8 @@ file_transfer_t *dccs_send_start( struct im_connection *ic, char *user_nick, cha /* listen and request */ - if( ( df->fd = ft_listen( &saddr, host, port, TRUE, &errmsg ) ) == -1 ) { + if( ( df->fd = ft_listen( &saddr, host, port, TRUE, &errmsg ) ) == -1 ) + { dcc_abort( df, "Failed to listen locally, check your ft_listen setting in bitlbee.conf: %s", errmsg ); return NULL; } @@ -160,9 +162,10 @@ file_transfer_t *dccs_send_start( struct im_connection *ic, char *user_nick, cha 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."); + imcb_log( ic, "File transfer request from %s for %s (%zd kb).\n" + "Accept the file transfer if you'd like the file. If you don't, " + "issue the 'transfers reject' command.", + user_nick, file_name, file_size / 1024 ); return file; } @@ -198,7 +201,7 @@ gboolean dcc_progress( gpointer data, gint fd, b_input_condition cond ) { /* no progress. cancel */ if( df->bytes_sent == 0 ) - return dcc_abort( df, "Couldnt establish transfer within %d seconds", DCC_MAX_STALL ); + return dcc_abort( df, "Couldn't 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 ); @@ -229,7 +232,8 @@ int dccs_send_request( struct dcc_file_transfer *df, char *user_nick, struct soc sprintf( ipaddr, "%d", ntohl( saddr_ipv4->sin_addr.s_addr ) ); port = saddr_ipv4->sin_port; - } else + } + else { struct sockaddr_in6 *saddr_ipv6 = ( struct sockaddr_in6 *) saddr; @@ -249,7 +253,7 @@ int dccs_send_request( struct dcc_file_transfer *df, char *user_nick, struct soc 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 ); + return dcc_abort( df, "Couldn't send `DCC SEND' message to %s.", user_nick ); g_free( cmd ); @@ -312,7 +316,7 @@ gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond ) file_transfer_t *file = df->ft; short revents; - if( !dcc_poll( df, fd, &revents) ) + if( !dcc_poll( df, fd, &revents ) ) return FALSE; if( ( revents & POLLIN ) && @@ -348,18 +352,18 @@ gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond ) int bytes_received; int ret; - ASSERTSOCKOP( ret = recv( fd, &bytes_received, sizeof( bytes_received ), MSG_PEEK ), "Receiving" ); + 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!" ); + imcb_log( df->ic, "WARNING: DCC SEND: receiver sent only %d bytes instead of 4, shouldn't happen too often!", ret ); return TRUE; } - ASSERTSOCKOP( ret = recv( fd, &bytes_received, sizeof( bytes_received ), 0 ), "Receiving" ); + 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 ); @@ -399,7 +403,7 @@ gboolean dccs_recv_start( file_transfer_t *ft ) 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" ); + ASSERTSOCKOP( fd = df->fd = socket( saddr->ss_family, SOCK_STREAM, 0 ), "Opening Socket" ); sock_make_nonblocking( fd ); @@ -428,7 +432,7 @@ gboolean dccs_recv_start( file_transfer_t *ft ) return TRUE; } -gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond) +gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond ) { dcc_file_transfer_t *df = data; file_transfer_t *ft = df->ft; diff --git a/protocols/jabber/Makefile b/protocols/jabber/Makefile index 891df5eb..78a02696 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 si.o s5bytestream.o +objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o s5bytestream.o sasl.o si.o CFLAGS += -Wall LFLAGS += -r diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c index f17a7bb5..e0658a9e 100644 --- a/protocols/jabber/iq.c +++ b/protocols/jabber/iq.c @@ -98,9 +98,9 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ) XMLNS_CHATSTATES, XMLNS_MUC, XMLNS_PING, - XMLNS_SI, - XMLNS_BYTESTREAMS, - XMLNS_FILETRANSFER, + XMLNS_SI, + XMLNS_BYTESTREAMS, + XMLNS_FILETRANSFER, NULL }; const char **f; @@ -126,16 +126,18 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ) } else if( strcmp( type, "set" ) == 0 ) { - if( ( c = xt_find_node( node->children, "si" ) ) && - ( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_SI ) == 0 ) ) + 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" ) ) ) + } + 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 ) + } + 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 @@ -159,11 +161,13 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ) reply = jabber_make_error_packet( node, "not-allowed", "cancel", NULL ); pack = 0; } - } else if( strcmp( s, XMLNS_BYTESTREAMS ) == 0 ) + } + else if( strcmp( s, XMLNS_BYTESTREAMS ) == 0 ) { - /* Bytestream Request (stage 2 of file transfer) */ + /* Bytestream Request (stage 2 of file transfer) */ return jabber_bs_recv_request( ic, node, c ); - } else + } + else { xt_free_node( reply ); reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel", NULL ); @@ -654,9 +658,10 @@ xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *no } c = c->children; - while( ( c = xt_find_node( c, "feature" ) ) ) { + while( ( c = xt_find_node( c, "feature" ) ) ) + { feature = xt_find_attr( c, "var" ); - bud->features = g_slist_append(bud->features, g_strdup(feature) ); + bud->features = g_slist_append( bud->features, g_strdup( feature ) ); c = c->next; } @@ -718,7 +723,8 @@ xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_n c = c->next; } - } else if( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_DISCO_INFO ) == 0 ) + } + else if( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_DISCO_INFO ) == 0 ) { char *category, *type; @@ -736,7 +742,8 @@ xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_n c = c->next; } - } else if( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_BYTESTREAMS ) == 0 ) + } + else if( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_BYTESTREAMS ) == 0 ) { char *host, *jid; int port; @@ -760,5 +767,6 @@ xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_n if( jd->have_streamhosts == 0 ) jd->have_streamhosts++; + return XT_HANDLED; } diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c index 15341c9b..02461101 100644 --- a/protocols/jabber/jabber.c +++ b/protocols/jabber/jabber.c @@ -62,6 +62,8 @@ static void jabber_init( account_t *acc ) s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add( &acc->set, "priority", "0", set_eval_priority, acc ); + + s = set_add( &acc->set, "proxy", ";", NULL, acc ); s = set_add( &acc->set, "resource", "BitlBee", NULL, acc ); s->flags |= ACC_SET_OFFLINE_ONLY; @@ -79,8 +81,6 @@ 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", ";", NULL, acc ); } static void jabber_generate_id_hash( struct jabber_data *jd ); diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h index 9f101f83..898c4978 100644 --- a/protocols/jabber/jabber.h +++ b/protocols/jabber/jabber.h @@ -59,7 +59,7 @@ typedef enum have a real JID. */ } jabber_buddy_flags_t; -/* Stores a streamhost's(a.k.a. proxy) data */ +/* Stores a streamhost's (a.k.a. proxy) data */ typedef struct { char *jid; diff --git a/protocols/msn/Makefile b/protocols/msn/Makefile index dd5d46e2..5d199b9e 100644 --- a/protocols/msn/Makefile +++ b/protocols/msn/Makefile @@ -9,7 +9,7 @@ -include ../../Makefile.settings # [SH] Program variables -objects = msn.o msn_util.o ns.o passport.o sb.o tables.o invitation.o +objects = invitation.o msn.o msn_util.o ns.o passport.o sb.o tables.o CFLAGS += -Wall LFLAGS += -r diff --git a/protocols/msn/msn.c b/protocols/msn/msn.c index 590a1382..b17a0bfa 100644 --- a/protocols/msn/msn.c +++ b/protocols/msn/msn.c @@ -77,7 +77,7 @@ static void msn_logout( struct im_connection *ic ) if( md ) { while( md->filetransfers ) { - imcb_file_canceled( md->filetransfers->data, "Closing msn connection" ); + imcb_file_canceled( md->filetransfers->data, "Closing connection" ); } if( md->fd >= 0 ) diff --git a/protocols/msn/sb.c b/protocols/msn/sb.c index b60a9a8b..c3302e57 100644 --- a/protocols/msn/sb.c +++ b/protocols/msn/sb.c @@ -173,11 +173,13 @@ int msn_sb_sendmessage( struct msn_switchboard *sb, char *text ) i = strlen( MSN_TYPING_HEADERS ) + strlen( sb->ic->acc->user ); buf = g_new0( char, i ); i = g_snprintf( buf, i, MSN_TYPING_HEADERS, sb->ic->acc->user ); - } else if( strncmp( text, MSN_INVITE_HEADERS, sizeof( MSN_INVITE_HEADERS ) - 1 ) == 0 ) + } + else if( strncmp( text, MSN_INVITE_HEADERS, sizeof( MSN_INVITE_HEADERS ) - 1 ) == 0 ) { buf = g_strdup( text ); i = strlen( buf ); - } else + } + else { buf = g_new0( char, sizeof( MSN_MESSAGE_HEADERS ) + strlen( text ) * 2 + 1 ); i = strlen( MSN_MESSAGE_HEADERS ); -- cgit v1.2.3 From 60e4df367e5c3af0eb1aada19f9c39ef7079e8e6 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Wed, 17 Mar 2010 23:23:27 +0000 Subject: Small cleanup. The max_packet_size variable doesn't seem to be read anywhere, and reworked string handling in ft_listen() a little bit. --- dcc.c | 27 +-------------- lib/ftutil.c | 77 +++++++++++++++++++++-------------------- lib/ftutil.h | 12 +++++++ protocols/jabber/s5bytestream.c | 2 +- protocols/msn/invitation.c | 2 +- 5 files changed, 55 insertions(+), 65 deletions(-) diff --git a/dcc.c b/dcc.c index 655c98b9..d28c3b34 100644 --- a/dcc.c +++ b/dcc.c @@ -61,8 +61,6 @@ unsigned int local_transfer_id=1; */ 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 ); @@ -132,7 +130,7 @@ file_transfer_t *dccs_send_start( struct im_connection *ic, char *user_nick, cha dcc_file_transfer_t *df; struct sockaddr_storage saddr; char *errmsg; - char host[INET6_ADDRSTRLEN]; + char host[HOST_NAME_MAX]; char port[6]; if( file_size > global.conf->ft_max_size ) @@ -288,24 +286,6 @@ gboolean dcc_poll( dcc_file_transfer_t *df, int fd, short *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. @@ -334,9 +314,6 @@ gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond ) 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 ); @@ -445,8 +422,6 @@ gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond ) ( 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 ); diff --git a/lib/ftutil.c b/lib/ftutil.c index 169a6480..d59dd4e0 100644 --- a/lib/ftutil.c +++ b/lib/ftutil.c @@ -27,63 +27,63 @@ #include #include "lib/ftutil.h" -/* Some ifdefs for ulibc and apparently also BSD (Thanks to Whoopie) */ -#ifndef HOST_NAME_MAX -#include -#ifdef MAXHOSTNAMELEN -#define HOST_NAME_MAX MAXHOSTNAMELEN -#else -#define HOST_NAME_MAX 255 -#endif -#endif - #define ASSERTSOCKOP(op, msg) \ if( (op) == -1 ) {\ - sprintf( errmsg , msg ": %s", strerror( errno ) ); \ + g_snprintf( errmsg, sizeof( errmsg ), msg ": %s", strerror( errno ) ); \ return -1; } /* * Creates a listening socket and returns it in saddr_ptr. */ -int ft_listen( struct sockaddr_storage *saddr_ptr, char *hostp, char *port, int for_bitlbee_client, char **errptr ) +int ft_listen( struct sockaddr_storage *saddr_ptr, char *host, char *port, int for_bitlbee_client, char **errptr ) { - int fd,gret,saddrlen; + int fd, gret, saddrlen; struct addrinfo hints, *rp; socklen_t ssize = sizeof( struct sockaddr_storage ); struct sockaddr_storage saddrs, *saddr = &saddrs; static char errmsg[1024]; - char host[ HOST_NAME_MAX + 1 ]; char *ftlisten = global.conf->ft_listen; - *errptr = errmsg; + if( errptr ) + *errptr = errmsg; - sprintf( port, "0" ); + strcpy( port, "0" ); /* Format is [:];[:] where * A is for connections with the bitlbee client (DCC) * and B is for connections with IM peers. */ - if( ftlisten ) { + if( ftlisten ) + { char *scolon = strchr( ftlisten, ';' ); char *colon; - if( scolon ) { - if( for_bitlbee_client ) { + if( scolon ) + { + if( for_bitlbee_client ) + { *scolon = '\0'; - sprintf( host, ftlisten ); + strncpy( host, ftlisten, HOST_NAME_MAX ); *scolon = ';'; - } else { - sprintf( host, scolon + 1 ); } - } else { - sprintf( host, ftlisten ); + else + { + strncpy( host, scolon + 1, HOST_NAME_MAX ); + } + } + else + { + strncpy( host, ftlisten, HOST_NAME_MAX ); } - if( ( colon = strchr( host, ':' ) ) ) { + if( ( colon = strchr( host, ':' ) ) ) + { *colon = '\0'; - sprintf( port, colon + 1 ); + strncpy( port, colon + 1, 5 ); } - } else { + } + else + { ASSERTSOCKOP( gethostname( host, HOST_NAME_MAX + 1 ), "gethostname()" ); } @@ -91,7 +91,8 @@ int ft_listen( struct sockaddr_storage *saddr_ptr, char *hostp, char *port, int hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_NUMERICSERV; - if ( ( gret = getaddrinfo( host, port, &hints, &rp ) ) != 0 ) { + if ( ( gret = getaddrinfo( host, port, &hints, &rp ) ) != 0 ) + { sprintf( errmsg, "getaddrinfo() failed: %s", gai_strerror( gret ) ); return -1; } @@ -103,29 +104,31 @@ int ft_listen( struct sockaddr_storage *saddr_ptr, char *hostp, char *port, int freeaddrinfo( rp ); ASSERTSOCKOP( fd = socket( saddr->ss_family, SOCK_STREAM, 0 ), "Opening socket" ); - ASSERTSOCKOP( bind( fd, ( struct sockaddr *)saddr, saddrlen ), "Binding socket" ); - 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 ) ) { - sprintf( errmsg, "inet_ntop failed on listening socket" ); + ( void * )&( ( struct sockaddr_in * ) saddr )->sin_addr.s_addr : + ( void * )&( ( struct sockaddr_in6 * ) saddr )->sin6_addr.s6_addr, + host, HOST_NAME_MAX ) ) + { + strcpy( errmsg, "inet_ntop failed on listening socket" ); return -1; } 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 ) ); + g_snprintf( port, 6, "%d", ntohs( ( (struct sockaddr_in *) saddr )->sin_port ) ); else - sprintf( port, "%d", ntohs( ( ( struct sockaddr_in6 *) saddr )->sin6_port ) ); + g_snprintf( port, 6, "%d", ntohs( ( (struct sockaddr_in6 *) saddr )->sin6_port ) ); if( saddr_ptr ) memcpy( saddr_ptr, saddr, saddrlen ); - strcpy( hostp, host ); - + /* I hate static-length strings.. */ + host[HOST_NAME_MAX] = '\0'; + port[5] = '\0'; + return fd; } diff --git a/lib/ftutil.h b/lib/ftutil.h index 636fcbd0..c4a5b02b 100644 --- a/lib/ftutil.h +++ b/lib/ftutil.h @@ -25,4 +25,16 @@ #define AI_NUMERICSERV 0x0400 /* Don't use name resolution. */ #endif +/* Some ifdefs for ulibc and apparently also BSD (Thanks to Whoopie) */ +#ifndef HOST_NAME_MAX +#include +#ifdef MAXHOSTNAMELEN +#define HOST_NAME_MAX MAXHOSTNAMELEN +#else +#define HOST_NAME_MAX 255 +#endif +#endif + +/* This function should be used with care. host should be AT LEAST a + char[HOST_NAME_MAX+1] and port AT LEAST a char[6]. */ int ft_listen( struct sockaddr_storage *saddr_ptr, char *host, char *port, int for_bitlbee_client, char **errptr ); diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c index 3c5ce503..c292e61e 100644 --- a/protocols/jabber/s5bytestream.c +++ b/protocols/jabber/s5bytestream.c @@ -889,7 +889,7 @@ void jabber_si_set_proxies( struct bs_transfer *bt ) char *proxysetting = g_strdup ( set_getstr( &tf->ic->acc->set, "proxy" ) ); char *proxy, *next, *errmsg = NULL; char port[6]; - char host[INET6_ADDRSTRLEN]; + char host[HOST_NAME_MAX+1]; jabber_streamhost_t *sh, *sh2; GSList *streamhosts = jd->streamhosts; diff --git a/protocols/msn/invitation.c b/protocols/msn/invitation.c index f44155fa..d2b2a5c8 100644 --- a/protocols/msn/invitation.c +++ b/protocols/msn/invitation.c @@ -218,7 +218,7 @@ void msn_invitations_accept( msn_filetransfer_t *msn_file, struct msn_switchboar file_transfer_t *file = msn_file->dcc; char buf[1024]; unsigned int acookie = time ( NULL ); - char host[INET6_ADDRSTRLEN]; + char host[HOST_NAME_MAX+1]; char port[6]; char *errmsg; -- cgit v1.2.3 From c1a3c27575ac6ae77cbffb1e48d02ebee3f83152 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Wed, 17 Mar 2010 23:41:07 +0000 Subject: Avoid some NULL pointer dereferences on malformed XMPP packets. --- protocols/jabber/iq.c | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c index d690d336..22f97b2a 100644 --- a/protocols/jabber/iq.c +++ b/protocols/jabber/iq.c @@ -657,18 +657,20 @@ xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *no { struct xt_node *c; struct jabber_buddy *bud; - char *feature; + char *feature, *xmlns, *from; if( !( c = xt_find_node( node->children, "query" ) ) || - !( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_DISCO_INFO ) == 0 ) ) + !( from = xt_find_attr( c, "from" ) ) || + !( xmlns = xt_find_attr( c, "xmlns" ) ) || + !( strcmp( 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 ) + if( ( bud = jabber_buddy_by_jid( ic, from, 0 ) ) == NULL ) { /* Who cares about the unknown... */ - imcb_log( ic, "Couldn't find buddy: %s", xt_find_attr( node, "from")); + imcb_log( ic, "Couldn't find buddy: %s", from ); return 0; } @@ -676,7 +678,8 @@ xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *no while( ( c = xt_find_node( c, "feature" ) ) ) { feature = xt_find_attr( c, "var" ); - bud->features = g_slist_append( bud->features, g_strdup( feature ) ); + if( feature ) + bud->features = g_slist_append( bud->features, g_strdup( feature ) ); c = c->next; } @@ -712,9 +715,11 @@ xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_n { struct xt_node *c; struct jabber_data *jd = ic->proto_data; + char *xmlns, *from; if( !( c = xt_find_node( node->children, "query" ) ) || - !xt_find_attr( node, "from" ) ) + !( from = xt_find_attr( node, "from" ) ) || + !( xmlns = xt_find_attr( c, "xmlns" ) ) ) { imcb_log( ic, "WARNING: Received incomplete IQ-result packet for discover" ); return XT_HANDLED; @@ -722,24 +727,24 @@ xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_n jd->have_streamhosts++; - if( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_DISCO_ITEMS ) == 0 ) + if( strcmp( xmlns, XMLNS_DISCO_ITEMS ) == 0 ) { - char *item, *itemjid; + char *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 ); + + if( itemjid ) + jabber_iq_query_server( ic, itemjid, XMLNS_DISCO_INFO ); c = c->next; } } - else if( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_DISCO_INFO ) == 0 ) + else if( xmlns, XMLNS_DISCO_INFO ) == 0 ) { char *category, *type; @@ -753,27 +758,29 @@ xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_n if( type && ( strcmp( type, "bytestreams" ) == 0 ) && category && ( strcmp( category, "proxy" ) == 0 ) ) - jabber_iq_query_server( ic, xt_find_attr( node, "from" ), XMLNS_BYTESTREAMS ); + jabber_iq_query_server( ic, from, XMLNS_BYTESTREAMS ); c = c->next; } } - else if( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_BYTESTREAMS ) == 0 ) + else if( xmlns, XMLNS_BYTESTREAMS ) == 0 ) { - char *host, *jid; + char *host, *jid, *port_s; 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" ) ) ) && + ( port_s = xt_find_attr( c, "port" ) ) && + ( sscanf( port_s, "%d", &port ) == 1 ) && ( 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 ); + g_snprintf( sh->port, sizeof( 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 ); -- cgit v1.2.3 From 42fc5b6cfed51ac011df8877cf5e24f00828e8be Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Wed, 17 Mar 2010 23:47:35 +0000 Subject: Ouch, committing stuff without even checking if it compiles is bad style. :-) --- protocols/jabber/iq.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c index 22f97b2a..e3c77d3d 100644 --- a/protocols/jabber/iq.c +++ b/protocols/jabber/iq.c @@ -744,7 +744,7 @@ xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_n c = c->next; } } - else if( xmlns, XMLNS_DISCO_INFO ) == 0 ) + else if( strcmp( xmlns, XMLNS_DISCO_INFO ) == 0 ) { char *category, *type; @@ -763,7 +763,7 @@ xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_n c = c->next; } } - else if( xmlns, XMLNS_BYTESTREAMS ) == 0 ) + else if( strcmp( xmlns, XMLNS_BYTESTREAMS ) == 0 ) { char *host, *jid, *port_s; int port; -- cgit v1.2.3 From 78d254f110d47f9e0c3a8f12259f93a4faa2130d Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Thu, 18 Mar 2010 00:07:16 +0000 Subject: More small fixes. (NULL derefs and s/close/disconnect/) --- protocols/jabber/s5bytestream.c | 5 +++-- protocols/jabber/si.c | 8 +++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c index c292e61e..5fcbef6a 100644 --- a/protocols/jabber/s5bytestream.c +++ b/protocols/jabber/s5bytestream.c @@ -281,11 +281,12 @@ int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, stru shnode = qnode->children; while( ( shnode = xt_find_node( shnode, "streamhost" ) ) ) { - char *jid, *host; + char *jid, *host, *port_s; int port; if( ( jid = xt_find_attr( shnode, "jid" ) ) && ( host = xt_find_attr( shnode, "host" ) ) && - ( ( port = atoi( xt_find_attr( shnode, "port" ) ) ) ) ) + ( port_s = xt_find_attr( shnode, "port" ) ) && + ( sscanf( port_s, "%d", &port ) == 1 ) ) { jabber_streamhost_t *sh = g_new0( jabber_streamhost_t, 1 ); sh->jid = g_strdup(jid); diff --git a/protocols/jabber/si.c b/protocols/jabber/si.c index e7aeffc9..424db8b3 100644 --- a/protocols/jabber/si.c +++ b/protocols/jabber/si.c @@ -40,7 +40,7 @@ void jabber_si_free_transfer( file_transfer_t *ft) if( tf->fd != -1 ) { - close( tf->fd ); + disconnect( tf->fd ); tf->fd = -1; } @@ -51,6 +51,7 @@ void jabber_si_free_transfer( file_transfer_t *ft) g_free( tf->tgt_jid ); g_free( tf->iq_id ); g_free( tf->sid ); + g_free( tf ); } /* file_transfer canceled() callback */ @@ -203,7 +204,7 @@ void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, 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; + char *sid, *ini_jid, *tgt_jid, *iq_id, *s, *ext_jid, *size_s; struct jabber_buddy *bud; int requestok = FALSE; char *name; @@ -229,7 +230,8 @@ int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, st !( 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" ) ) ) || + !( size_s = xt_find_attr( d, "size" ) ) || + !( 1 == sscanf( size_s, "%lld", &size ) ) || !( d = xt_find_node( sinode->children, "feature" ) ) || !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_FEATURE ) == 0 ) || !( d = xt_find_node( d->children, "x" ) ) || -- cgit v1.2.3 From b8a491db597ba2d82cc8eddc727509197747f0d5 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Thu, 18 Mar 2010 00:30:38 +0000 Subject: Some compiler warning fixes and renamed "transfers" command to just "transfer" since all root commands are singular so far. --- protocols/jabber/s5bytestream.c | 2 +- protocols/jabber/si.c | 21 +++++++++++---------- root_commands.c | 13 +++++-------- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c index 5fcbef6a..bb1d9e2e 100644 --- a/protocols/jabber/s5bytestream.c +++ b/protocols/jabber/s5bytestream.c @@ -829,7 +829,7 @@ static xt_status jabber_bs_send_handle_activate( struct im_connection *ic, struc { char *sid; GSList *tflist; - struct jabber_transfer *tf; + struct jabber_transfer *tf = NULL; struct xt_node *query; struct jabber_data *jd = ic->proto_data; diff --git a/protocols/jabber/si.c b/protocols/jabber/si.c index 424db8b3..a87f7b8b 100644 --- a/protocols/jabber/si.c +++ b/protocols/jabber/si.c @@ -40,7 +40,7 @@ void jabber_si_free_transfer( file_transfer_t *ft) if( tf->fd != -1 ) { - disconnect( tf->fd ); + closesocket( tf->fd ); tf->fd = -1; } @@ -155,10 +155,10 @@ void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, if( bud == NULL ) { - imcb_file_canceled( tf->ft, "Couldn't find buddy (BUG?)" ); + imcb_file_canceled( 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 ); @@ -231,7 +231,7 @@ int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, st !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_FILETRANSFER ) == 0 ) || !( name = xt_find_attr( d, "name" ) ) || !( size_s = xt_find_attr( d, "size" ) ) || - !( 1 == sscanf( size_s, "%lld", &size ) ) || + !( 1 == sscanf( size_s, "%zd", &size ) ) || !( d = xt_find_node( sinode->children, "feature" ) ) || !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_FEATURE ) == 0 ) || !( d = xt_find_node( d->children, "x" ) ) || @@ -241,14 +241,15 @@ int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, st !( strcmp( xt_find_attr( d, "var" ), "stream-method" ) == 0 ) ) { imcb_log( ic, "WARNING: Received incomplete Stream Initiation request" ); - } else + } + 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 ) ) + if( ( d = xt_find_node( c->children, "value" ) ) && + ( strcmp( d->text, XMLNS_BYTESTREAMS ) == 0 ) ) { requestok = TRUE; break; @@ -258,10 +259,11 @@ int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, st imcb_log( ic, "WARNING: Unsupported file transfer request from %s", ini_jid); } - if ( requestok ) + if( requestok ) { /* Figure out who the transfer should come frome... */ + ext_jid = ini_jid; if( ( s = strchr( ini_jid, '/' ) ) ) { if( ( bud = jabber_buddy_by_jid( ic, ini_jid, GET_BUDDY_EXACT ) ) ) @@ -281,8 +283,7 @@ int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, st *s = '/'; } - - if ( !requestok ) + else { reply = jabber_make_error_packet( node, "item-not-found", "cancel", NULL ); if (!jabber_write_packet( ic, reply )) diff --git a/root_commands.c b/root_commands.c index b39cb1ba..c60092f0 100644 --- a/root_commands.c +++ b/root_commands.c @@ -1127,7 +1127,7 @@ static void cmd_chat( irc_t *irc, char **cmd ) } } -static void cmd_transfers( irc_t *irc, char **cmd ) +static void cmd_transfer( irc_t *irc, char **cmd ) { GSList *files = irc->file_transfers; enum { LIST, REJECT, CANCEL }; @@ -1140,15 +1140,12 @@ static void cmd_transfers( irc_t *irc, char **cmd ) return; } - if( cmd[1] && - ( strcmp( cmd[1], "reject" ) == 0 ) ) + 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] ) ) ) + else if( cmd[1] && ( strcmp( cmd[1], "cancel" ) == 0 ) && + cmd[2] && ( sscanf( cmd[2], "%d", &fid ) == 1 ) ) { subcmd = CANCEL; } @@ -1213,6 +1210,6 @@ const command_t commands[] = { { "qlist", 0, cmd_qlist, 0 }, { "join_chat", 2, cmd_join_chat, 0 }, { "chat", 1, cmd_chat, 0 }, - { "transfers", 0, cmd_transfers, 0 }, + { "transfer", 0, cmd_transfer, 0 }, { NULL } }; -- cgit v1.2.3 From 699376f7c3b3d6aff18af0601fa1f1ac6c5a2892 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 21 Mar 2010 00:39:45 +0000 Subject: Read the from attribute from the main stanza, not the inside query. Also fixing another potential NULL pointer dereference. --- protocols/jabber/iq.c | 4 ++-- protocols/jabber/si.c | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c index e3c77d3d..dfdc6887 100644 --- a/protocols/jabber/iq.c +++ b/protocols/jabber/iq.c @@ -659,8 +659,8 @@ xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *no struct jabber_buddy *bud; char *feature, *xmlns, *from; - if( !( c = xt_find_node( node->children, "query" ) ) || - !( from = xt_find_attr( c, "from" ) ) || + if( !( from = xt_find_attr( node, "from" ) ) || + !( c = xt_find_node( node->children, "query" ) ) || !( xmlns = xt_find_attr( c, "xmlns" ) ) || !( strcmp( xmlns, XMLNS_DISCO_INFO ) == 0 ) ) { diff --git a/protocols/jabber/si.c b/protocols/jabber/si.c index a87f7b8b..b76fb3e0 100644 --- a/protocols/jabber/si.c +++ b/protocols/jabber/si.c @@ -249,6 +249,7 @@ int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, st c = d->children; while( ( c = xt_find_node( c, "option" ) ) ) if( ( d = xt_find_node( c->children, "value" ) ) && + ( d->text != NULL ) && ( strcmp( d->text, XMLNS_BYTESTREAMS ) == 0 ) ) { requestok = TRUE; -- cgit v1.2.3 From 54a20149778028bff730511c6cc8027f61634124 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 21 Mar 2010 00:46:18 +0000 Subject: xt_status functions should always return an XT_ status, it's not a boolean. --- protocols/jabber/iq.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c index dfdc6887..3e2eb870 100644 --- a/protocols/jabber/iq.c +++ b/protocols/jabber/iq.c @@ -632,7 +632,7 @@ xt_status jabber_iq_query_features( struct im_connection *ic, char *bare_jid ) { /* Who cares about the unknown... */ imcb_log( ic, "Couldn't find buddy: %s", bare_jid); - return 0; + return XT_HANDLED; } if( bud->features ) /* been here already */ @@ -645,12 +645,12 @@ xt_status jabber_iq_query_features( struct im_connection *ic, char *bare_jid ) { imcb_log( ic, "WARNING: Couldn't generate feature query" ); xt_free_node( node ); - return 0; + return XT_HANDLED; } jabber_cache_add( ic, query, jabber_iq_parse_features ); - return jabber_write_packet( ic, query ); + return jabber_write_packet( ic, query ) ? XT_HANDLED : XT_ABORT; } xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) @@ -671,7 +671,7 @@ xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *no { /* Who cares about the unknown... */ imcb_log( ic, "Couldn't find buddy: %s", from ); - return 0; + return XT_HANDLED; } c = c->children; @@ -705,7 +705,7 @@ xt_status jabber_iq_query_server( struct im_connection *ic, char *jid, char *xml jd->have_streamhosts--; jabber_cache_add( ic, query, jabber_iq_parse_server_features ); - return jabber_write_packet( ic, query ); + return jabber_write_packet( ic, query ) ? XT_HANDLED : XT_ABORT; } /* -- cgit v1.2.3 From aed152f005a70e04f7d833dc4fb468e400e54fb3 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 21 Mar 2010 01:02:03 +0000 Subject: Killed the last str*cmp(xml_find_attr) combos. --- protocols/jabber/iq.c | 3 ++- protocols/jabber/si.c | 39 ++++++++++++++++++++++++--------------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c index 3e2eb870..f5fbdc13 100644 --- a/protocols/jabber/iq.c +++ b/protocols/jabber/iq.c @@ -127,7 +127,8 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ) else if( strcmp( type, "set" ) == 0 ) { if( ( c = xt_find_node( node->children, "si" ) ) && - ( strcmp( xt_find_attr( c, "xmlns" ), XMLNS_SI ) == 0 ) ) + ( s = xt_find_attr( c, "xmlns" ) ) && + ( strcmp( s, XMLNS_SI ) == 0 ) ) { return jabber_si_handle_request( ic, node, c ); } diff --git a/protocols/jabber/si.c b/protocols/jabber/si.c index b76fb3e0..eff4aad7 100644 --- a/protocols/jabber/si.c +++ b/protocols/jabber/si.c @@ -207,7 +207,7 @@ int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, st char *sid, *ini_jid, *tgt_jid, *iq_id, *s, *ext_jid, *size_s; struct jabber_buddy *bud; int requestok = FALSE; - char *name; + char *name, *cmp; size_t size; struct jabber_transfer *tf; struct jabber_data *jd = ic->proto_data; @@ -226,19 +226,25 @@ int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, st !( 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 ) || + !( cmp = xt_find_attr( sinode, "profile" ) ) || + !( 0 == strcmp( cmp, XMLNS_FILETRANSFER ) ) || !( d = xt_find_node( sinode->children, "file" ) ) || - !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_FILETRANSFER ) == 0 ) || + !( cmp = xt_find_attr( d, "xmlns" ) ) || + !( 0 == strcmp( cmp, XMLNS_FILETRANSFER ) ) || !( name = xt_find_attr( d, "name" ) ) || !( size_s = xt_find_attr( d, "size" ) ) || !( 1 == sscanf( size_s, "%zd", &size ) ) || !( d = xt_find_node( sinode->children, "feature" ) ) || - !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_FEATURE ) == 0 ) || + !( cmp = xt_find_attr( d, "xmlns" ) ) || + !( 0 == strcmp( cmp, XMLNS_FEATURE ) ) || !( d = xt_find_node( d->children, "x" ) ) || - !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_XDATA ) == 0 ) || - !( strcmp( xt_find_attr( d, "type" ), "form" ) == 0 ) || + !( cmp = xt_find_attr( d, "xmlns" ) ) || + !( 0 == strcmp( cmp, XMLNS_XDATA ) ) || + !( cmp = xt_find_attr( d, "type" ) ) || + !( 0 == strcmp( cmp, "form" ) ) || !( d = xt_find_node( d->children, "field" ) ) || - !( strcmp( xt_find_attr( d, "var" ), "stream-method" ) == 0 ) ) + !( cmp = xt_find_attr( d, "var" ) ) || + !( 0 == strcmp( cmp, "stream-method" ) ) ) { imcb_log( ic, "WARNING: Received incomplete Stream Initiation request" ); } @@ -366,7 +372,7 @@ void jabber_si_answer_request( file_transfer_t *ft ) { 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; + char *ini_jid, *tgt_jid, *iq_id, *cmp; GSList *tflist; struct jabber_transfer *tf=NULL; struct jabber_data *jd = ic->proto_data; @@ -391,16 +397,19 @@ static xt_status jabber_si_handle_response(struct im_connection *ic, struct xt_n !( 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 ) || */ + !( cmp = xt_find_attr( c, "xmlns" ) ) || + !( strcmp( cmp, XMLNS_SI ) == 0 ) || !( d = xt_find_node( c->children, "feature" ) ) || - !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_FEATURE ) == 0 ) || + !( cmp = xt_find_attr( d, "xmlns" ) ) || + !( strcmp( cmp, 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 ) || + !( cmp = xt_find_attr( d, "xmlns" ) ) || + !( strcmp( cmp, XMLNS_XDATA ) == 0 ) || + !( cmp = xt_find_attr( d, "type" ) ) || + !( strcmp( cmp, "submit" ) == 0 ) || !( d = xt_find_node( d->children, "field" ) ) || - !( strcmp( xt_find_attr( d, "var" ), "stream-method" ) == 0 ) || + !( cmp = xt_find_attr( d, "var" ) ) || + !( strcmp( cmp, "stream-method" ) == 0 ) || !( d = xt_find_node( d->children, "value" ) ) ) { imcb_log( ic, "WARNING: Received incomplete Stream Initiation response" ); -- cgit v1.2.3 From 545d7c058d0604dd6acfa37c68e9867e72f25c2e Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 21 Mar 2010 10:18:28 +0000 Subject: Added ignore_auth_requests setting (patch from vmiklos, bug #578). --- doc/user-guide/commands.xml | 11 +++++++++++ protocols/oscar/oscar.c | 17 +++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/doc/user-guide/commands.xml b/doc/user-guide/commands.xml index f8ae4386..df99c1fc 100644 --- a/doc/user-guide/commands.xml +++ b/doc/user-guide/commands.xml @@ -586,6 +586,17 @@ + + true + + + + Only supported by OSCAR so far, you can use this setting to ignore ICQ authorization requests, which are hardly used for legitimate (i.e. non-spam) reasons anymore. + + + + + true diff --git a/protocols/oscar/oscar.c b/protocols/oscar/oscar.c index f0e65f9a..e0c32257 100644 --- a/protocols/oscar/oscar.c +++ b/protocols/oscar/oscar.c @@ -372,11 +372,15 @@ static void oscar_init(account_t *acc) { set_t *s; - s = set_add( &acc->set, "server", AIM_DEFAULT_LOGIN_SERVER, set_eval_account, acc ); + if (isdigit(acc->user[0])) { + set_add(&acc->set, "ignore_auth_requests", "false", set_eval_bool, acc); + } + + s = set_add(&acc->set, "server", AIM_DEFAULT_LOGIN_SERVER, set_eval_account, acc); s->flags |= ACC_SET_NOSAVE | ACC_SET_OFFLINE_ONLY; - if (isdigit(acc->user[0])) { - s = set_add( &acc->set, "web_aware", "false", set_eval_bool, acc ); + if(isdigit(acc->user[0])) { + s = set_add(&acc->set, "web_aware", "false", set_eval_bool, acc); s->flags |= ACC_SET_OFFLINE_ONLY; } @@ -1211,10 +1215,15 @@ static void gaim_icq_authdeny(void *data_) { * For when other people ask you for authorization */ static void gaim_icq_authask(struct im_connection *ic, guint32 uin, char *msg) { - struct icq_auth *data = g_new(struct icq_auth, 1); + struct icq_auth *data; char *reason = NULL; char *dialog_msg; + + if (set_getbool(&ic->acc->set, "ignore_auth_requests")) + return; + data = g_new(struct icq_auth, 1); + if (strlen(msg) > 6) reason = msg + 6; -- cgit v1.2.3 From 2e89256f2265676d5d37b92433352ceccf079a7a Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 21 Mar 2010 13:20:20 +0000 Subject: Remove dcc_poll() and just use the cond variable passed to I/O events. --- dcc.c | 45 ++++----------------------------------------- 1 file changed, 4 insertions(+), 41 deletions(-) diff --git a/dcc.c b/dcc.c index d28c3b34..dcfeb190 100644 --- a/dcc.c +++ b/dcc.c @@ -25,7 +25,6 @@ #include "bitlbee.h" #include "ft.h" #include "dcc.h" -#include #include #include #include "lib/ftutil.h" @@ -258,34 +257,6 @@ int dccs_send_request( struct dcc_file_transfer *df, char *user_nick, struct soc 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; -} - /* * After setup, the transfer itself is handled entirely by this function. * There are basically four things to handle: connect, receive, send, and error. @@ -294,12 +265,8 @@ 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 ) && + if( ( cond & GAIM_INPUT_READ ) && ( file->status & FT_STATUS_LISTENING ) ) { struct sockaddr *clt_addr; @@ -324,7 +291,7 @@ gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond ) return FALSE; } - if( revents & POLLIN ) + if( cond & GAIM_INPUT_READ ) { int bytes_received; int ret; @@ -413,12 +380,8 @@ 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 ) && + if( ( cond & GAIM_INPUT_WRITE ) && ( ft->status & FT_STATUS_CONNECTING ) ) { ft->status = FT_STATUS_TRANSFERRING; @@ -429,7 +392,7 @@ gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond ) return FALSE; } - if( revents & POLLIN ) + if( cond & GAIM_INPUT_READ ) { int ret, done; -- cgit v1.2.3 From 4ed9c8c11adb8d1010ed4bb7b3ff9af962f91237 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 21 Mar 2010 15:10:07 +0000 Subject: Fixed 100% CPU usage bug in dcc.c. --- dcc.c | 33 ++++++++++++++------------------- dcc.h | 6 ++++++ 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/dcc.c b/dcc.c index dcfeb190..558d923a 100644 --- a/dcc.c +++ b/dcc.c @@ -293,35 +293,30 @@ gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond ) if( cond & GAIM_INPUT_READ ) { - int bytes_received; int ret; - ASSERTSOCKOP( ret = recv( fd, &bytes_received, sizeof( bytes_received ), MSG_PEEK ), "Receiving" ); + ASSERTSOCKOP( ret = recv( fd, ( (char*) &df->acked ) + df->acked_len, + sizeof( df->acked ) - df->acked_len, 0 ), "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 %d bytes instead of 4, shouldn't happen too often!", ret ); + + /* How likely is it that a 32-bit integer gets split accross + packet boundaries? Chances are rarely 0 so let's be sure. */ + if( ( df->acked_len = ( df->acked_len + ret ) % 4 ) > 0 ) 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 ); + df->acked = ntohl( df->acked ); /* 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 ( df->acked > df->bytes_sent ) + return dcc_abort( df, "Receiver magically received more bytes than sent ( %d > %d ) (BUG at receiver?)", df->acked, 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 ); + if ( df->acked < file->bytes_transferred ) + return dcc_abort( df, "Receiver lost bytes? ( has %d, had %d ) (BUG at receiver?)", df->acked, file->bytes_transferred ); - file->bytes_transferred = bytes_received; + file->bytes_transferred = df->acked; if( file->bytes_transferred >= file->file_size ) { if( df->proto_finished ) @@ -411,8 +406,8 @@ gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond ) if( ( ( df->bytes_sent - ft->bytes_transferred ) > DCC_PACKET_SIZE ) || done ) { - int ack, ackret; - ack = htonl( ft->bytes_transferred = df->bytes_sent ); + guint32 ack = htonl( ft->bytes_transferred = df->bytes_sent ); + int ackret; ASSERTSOCKOP( ackret = send( fd, &ack, 4, 0 ), "Sending DCC ACK" ); diff --git a/dcc.h b/dcc.h index 4be5a338..cc784478 100644 --- a/dcc.h +++ b/dcc.h @@ -76,6 +76,12 @@ typedef struct dcc_file_transfer { */ size_t bytes_sent; + /* + * Handle the wonderful sadly-not-deprecated ACKs. + */ + guint32 acked; + int acked_len; + /* imc's handle */ file_transfer_t *ft; -- cgit v1.2.3 From a81d679654e36055ce9913cd7541885b41380947 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 21 Mar 2010 15:56:59 +0000 Subject: Fixed jabber_bs_peek() to deal with incomplete packets as well. --- protocols/jabber/s5bytestream.c | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c index bb1d9e2e..58a6c2e4 100644 --- a/protocols/jabber/s5bytestream.c +++ b/protocols/jabber/s5bytestream.c @@ -45,6 +45,9 @@ struct bs_transfer { char *pseudoadr; gint connect_timeout; + + char peek_buf[64]; + int peek_buf_len; }; struct socks5_message @@ -149,20 +152,26 @@ 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( buflen > sizeof( bt->peek_buf ) ) + return jabber_bs_abort( bt, "BUG: %d > sizeof(peek_buf)", buflen ); + + ASSERTSOCKOP( ret = recv( fd, bt->peek_buf + bt->peek_buf_len, + buflen - bt->peek_buf_len, 0 ), "recv() on SOCKS5 connection" ); 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; + bt->peek_buf_len += ret; + memcpy( buffer, bt->peek_buf, bt->peek_buf_len ); + + if( bt->peek_buf_len == buflen ) + { + /* If we have everything the caller wanted, reset the peek buffer. */ + bt->peek_buf_len = 0; + return buflen; + } + else + return bt->peek_buf_len; } @@ -559,6 +568,7 @@ gboolean jabber_bs_recv_handshake_abort( struct bs_transfer *bt, char *error ) imcb_file_canceled( tf->ft, "couldn't connect to any streamhosts" ); bt->tf->watch_in = 0; + /* MUST always return FALSE! */ return FALSE; } @@ -1011,6 +1021,7 @@ gboolean jabber_bs_send_handshake_abort(struct bs_transfer *bt, char *error ) if( jd->streamhosts==NULL ) /* we're done here unless we have a proxy to try */ imcb_file_canceled( tf->ft, error ); + /* MUST always return FALSE! */ return FALSE; } -- cgit v1.2.3