aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorulim <a.sporto+bee@gmail.com>2007-11-28 22:07:30 +0100
committerulim <a.sporto+bee@gmail.com>2007-11-28 22:07:30 +0100
commit2c2df7dd91930345a9b22a8bb61327d1dcc7e3d5 (patch)
treece4aa38f4022e5a14cc8b8a8534f287f274daaeb
parent221a27346f768b9626f2a0281ff774790858a0c2 (diff)
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.
-rw-r--r--Makefile4
-rw-r--r--conf.c1
-rw-r--r--conf.h1
-rw-r--r--dcc.c534
-rw-r--r--dcc.h125
-rw-r--r--irc.h1
-rw-r--r--protocols/ft.h153
-rw-r--r--protocols/jabber/Makefile2
-rw-r--r--protocols/jabber/iq.c28
-rw-r--r--protocols/jabber/jabber.h58
-rw-r--r--protocols/jabber/jabber_util.c6
-rw-r--r--protocols/jabber/si.c246
-rw-r--r--protocols/jabber/stream.c593
-rw-r--r--protocols/nogaim.h1
-rw-r--r--root_commands.c66
15 files changed, 1795 insertions, 24 deletions
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 <a.sporto+bee@gmail.com> *
+\********************************************************************/
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License with
+ the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+ if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#define BITLBEE_CORE
+#include "bitlbee.h"
+#include "ft.h"
+#include "dcc.h"
+#include <poll.h>
+#include <netinet/tcp.h>
+
+/*
+ * 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 <a.sporto+bee@gmail.com> *
+\********************************************************************/
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License with
+ the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+ if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+/*
+ * DCC SEND
+ *
+ * Historically, DCC means send 1024 Bytes and wait for a 4 byte reply
+ * acknowledging all transferred data. This is ridiculous for two reasons. The
+ * first being that TCP is a stream oriented protocol that doesn't care much
+ * about your idea of a packet. The second reason being that TCP is a reliable
+ * transfer protocol with its own sophisticated ACK mechanism, making DCCs ACK
+ * mechanism look like a joke. For these reasons, DCCs requirements have
+ * (hopefully) been relaxed in most implementations and this implementation
+ * depends upon at least the following: The 1024 bytes need not be transferred
+ * at once, i.e. packets can be smaller. A second relaxation has apparently
+ * gotten the name "DCC SEND ahead" which basically means to not give a damn
+ * about those DCC ACKs and just send data as you please. This behaviour is
+ * enabled by default. Note that this also means that packets may be as large
+ * as the maximum segment size.
+ */
+
+#ifndef _DCC_H
+#define _DCC_H
+
+/* 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 <error>/type="error" tag. Including the original packet
is recommended, so let's just do it. */
diff --git a/protocols/jabber/si.c b/protocols/jabber/si.c
new file mode 100644
index 00000000..d16f723a
--- /dev/null
+++ b/protocols/jabber/si.c
@@ -0,0 +1,246 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Jabber module - SI packets *
+* *
+* Copyright 2007 Uli Meis <a.sporto+bee@gmail.com> *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+* *
+* This program is distributed in the hope that it will be useful, *
+* but WITHOUT ANY WARRANTY; without even the implied warranty of *
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+* GNU General Public License for more details. *
+* *
+* You should have received a copy of the GNU General Public License along *
+* with this program; if not, write to the Free Software Foundation, Inc., *
+* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
+* *
+\***************************************************************************/
+
+#include "jabber.h"
+#include "sha1.h"
+
+void jabber_si_answer_request( file_transfer_t *ft );
+
+/* imcb callback */
+void jabber_si_free_transfer( file_transfer_t *ft)
+{
+ struct jabber_transfer *tf = ft->data;
+ struct jabber_data *jd = tf->ic->proto_data;
+
+ if ( tf->watch_in )
+ b_event_remove( tf->watch_in );
+
+ jd->filetransfers = g_slist_remove( jd->filetransfers, tf );
+
+ if( tf->fd )
+ {
+ close( tf->fd );
+ tf->fd = 0;
+ }
+
+ g_free( tf->ini_jid );
+ g_free( tf->tgt_jid );
+ g_free( tf->iq_id );
+ g_free( tf->sid );
+}
+
+/* imcb callback */
+void jabber_si_finished( file_transfer_t *ft )
+{
+ struct jabber_transfer *tf = ft->data;
+
+ imcb_log( tf->ic, "File %s transferred successfully!" , ft->file_name );
+}
+
+/* imcb callback */
+void jabber_si_canceled( file_transfer_t *ft, char *reason )
+{
+ struct jabber_transfer *tf = ft->data;
+ struct xt_node *reply, *iqnode;
+
+ if( tf->accepted )
+ return;
+
+ iqnode = jabber_make_packet( "iq", "error", tf->ini_jid, NULL );
+ xt_add_attr( iqnode, "id", tf->iq_id );
+ reply = jabber_make_error_packet( iqnode, "forbidden", "cancel", "403" );
+ xt_free_node( iqnode );
+
+ if( !jabber_write_packet( tf->ic, reply ) )
+ imcb_log( tf->ic, "WARNING: Error generating reply to file transfer request" );
+ xt_free_node( reply );
+
+}
+
+/*
+ * First function that gets called when a file transfer request comes in.
+ * A lot to parse.
+ *
+ * We choose a stream type from the options given by the initiator.
+ * Then we wait for imcb to call the accept or cancel callbacks.
+ */
+int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, struct xt_node *sinode)
+{
+ struct xt_node *c, *d, *reply;
+ char *sid, *ini_jid, *tgt_jid, *iq_id, *s, *ext_jid;
+ struct jabber_buddy *bud;
+ int requestok = FALSE;
+ char *name;
+ size_t size;
+ struct jabber_transfer *tf;
+ struct jabber_data *jd = ic->proto_data;
+ file_transfer_t *ft;
+
+ /* All this means we expect something like this: ( I think )
+ * <iq from=... to=... id=...>
+ * <si id=id xmlns=si profile=ft>
+ * <file xmlns=ft/>
+ * <feature xmlns=feature>
+ * <x xmlns=xdata type=submit>
+ * <field var=stream-method>
+ *
+ */
+ if( !( ini_jid = xt_find_attr( node, "from" ) ) ||
+ !( tgt_jid = xt_find_attr( node, "to" ) ) ||
+ !( iq_id = xt_find_attr( node, "id" ) ) ||
+ !( sid = xt_find_attr( sinode, "id" ) ) ||
+ !( strcmp( xt_find_attr( sinode, "profile" ), XMLNS_FILETRANSFER ) == 0 ) ||
+ !( d = xt_find_node( sinode->children, "file" ) ) ||
+ !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_FILETRANSFER ) == 0 ) ||
+ !( name = xt_find_attr( d, "name" ) ) ||
+ !( size = (size_t) atoll( xt_find_attr( d, "size" ) ) ) ||
+ !( d = xt_find_node( sinode->children, "feature" ) ) ||
+ !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_FEATURE ) == 0 ) ||
+ !( d = xt_find_node( d->children, "x" ) ) ||
+ !( strcmp( xt_find_attr( d, "xmlns" ), XMLNS_XDATA ) == 0 ) ||
+ !( strcmp( xt_find_attr( d, "type" ), "form" ) == 0 ) ||
+ !( d = xt_find_node( d->children, "field" ) ) ||
+ !( strcmp( xt_find_attr( d, "var" ), "stream-method" ) == 0 ) )
+ {
+ imcb_log( ic, "WARNING: Received incomplete Stream Initiation request" );
+ } else
+ {
+ /* Check if we support one of the options */
+
+ c = d->children;
+ while( ( c = xt_find_node( c, "option" ) ) )
+ if( ( d = xt_find_node( c->children, "value" ) ) &&
+ ( strcmp( d->text, XMLNS_BYTESTREAMS ) == 0 ) )
+ {
+ requestok = TRUE;
+ break;
+ }
+ }
+
+ if ( requestok )
+ {
+ /* Figure out who the transfer should come frome... */
+
+ if( ( s = strchr( ini_jid, '/' ) ) )
+ {
+ if( ( bud = jabber_buddy_by_jid( ic, ini_jid, GET_BUDDY_EXACT ) ) )
+ {
+ bud->last_act = time( NULL );
+ ext_jid = bud->ext_jid ? : bud->bare_jid;
+ }
+ else
+ *s = 0; /* We need to generate a bare JID now. */
+ }
+
+ if( !( ft = imcb_file_send_start( ic, ext_jid, name, size ) ) )
+ {
+ imcb_log( ic, "WARNING: Error handling transfer request from %s", ini_jid);
+ requestok = FALSE;
+ }
+
+ *s = '/';
+ } else
+ imcb_log( ic, "WARNING: Unsupported file transfer request from %s", ini_jid);
+
+ if ( !requestok )
+ {
+ reply = jabber_make_error_packet( node, "item-not-found", "cancel", NULL );
+ if (!jabber_write_packet( ic, reply ))
+ imcb_log( ic, "WARNING: Error generating reply to file transfer request" );
+ xt_free_node( reply );
+ return XT_HANDLED;
+ }
+
+ /* Request is fine. */
+
+ imcb_log( ic, "File transfer request from %s for %s (%zd kb). ", xt_find_attr( node, "from" ), name, size/1024 );
+
+ imcb_log( ic, "Accept the DCC transfer if you'd like the file. If you don't, issue the 'transfers reject' command.");
+
+ tf = g_new0( struct jabber_transfer, 1 );
+
+ tf->ini_jid = g_strdup( ini_jid );
+ tf->tgt_jid = g_strdup( tgt_jid );
+ tf->iq_id = g_strdup( iq_id );
+ tf->sid = g_strdup( sid );
+ tf->ic = ic;
+ tf->ft = ft;
+ tf->ft->data = tf;
+ tf->ft->accept = jabber_si_answer_request;
+ tf->ft->free = jabber_si_free_transfer;
+ tf->ft->finished = jabber_si_finished;
+ tf->ft->canceled = jabber_si_canceled;
+
+ jd->filetransfers = g_slist_prepend( jd->filetransfers, tf );
+
+ return XT_HANDLED;
+}
+
+/*
+ * imcb called the accept callback which probably means that the user accepted this file transfer.
+ * We send our response to the initiator.
+ * In the next step, the initiator will send us a request for the given stream type.
+ * (currently that can only be a SOCKS5 bytestream)
+ */
+void jabber_si_answer_request( file_transfer_t *ft ) {
+ struct jabber_transfer *tf = ft->data;
+ struct xt_node *node, *sinode, *reply;
+
+ /* generate response, start with the SI tag */
+ sinode = xt_new_node( "si", NULL, NULL );
+ xt_add_attr( sinode, "xmlns", XMLNS_SI );
+ xt_add_attr( sinode, "profile", XMLNS_FILETRANSFER );
+ xt_add_attr( sinode, "id", tf->sid );
+
+ /* now the file tag */
+ node = xt_new_node( "file", NULL, NULL );
+ xt_add_attr( node, "xmlns", XMLNS_FILETRANSFER );
+
+ xt_add_child( sinode, node );
+
+ /* and finally the feature tag */
+ node = xt_new_node( "field", NULL, NULL );
+ xt_add_attr( node, "var", "stream-method" );
+ xt_add_attr( node, "type", "list-single" );
+
+ /* Currently all we can do. One could also implement in-band (IBB) */
+ xt_add_child( node, xt_new_node( "value", XMLNS_BYTESTREAMS, NULL ) );
+
+ node = xt_new_node( "x", NULL, node );
+ xt_add_attr( node, "xmlns", XMLNS_XDATA );
+ xt_add_attr( node, "type", "submit" );
+
+ node = xt_new_node( "feature", NULL, node );
+ xt_add_attr( node, "xmlns", XMLNS_FEATURE );
+
+ xt_add_child( sinode, node );
+
+ reply = jabber_make_packet( "iq", "result", tf->ini_jid, sinode );
+ xt_add_attr( reply, "id", tf->iq_id );
+
+ if( !jabber_write_packet( tf->ic, reply ) )
+ imcb_log( tf->ic, "WARNING: Error generating reply to file transfer request" );
+ else
+ tf->accepted = TRUE;
+ xt_free_node( reply );
+}
diff --git a/protocols/jabber/stream.c b/protocols/jabber/stream.c
new file mode 100644
index 00000000..c88a72fd
--- /dev/null
+++ b/protocols/jabber/stream.c
@@ -0,0 +1,593 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Jabber module - stream handling *
+* *
+* Copyright 2007 Uli Meis <a.sporto+bee@gmail.com> *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+* *
+* This program is distributed in the hope that it will be useful, *
+* but WITHOUT ANY WARRANTY; without even the implied warranty of *
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+* GNU General Public License for more details. *
+* *
+* You should have received a copy of the GNU General Public License along *
+* with this program; if not, write to the Free Software Foundation, Inc., *
+* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
+* *
+\***************************************************************************/
+
+#include "jabber.h"
+#include "sha1.h"
+#include <poll.h>
+
+/* Some structs for the SOCKS5 handshake */
+
+struct bs_handshake_data {
+
+ struct jabber_transfer *tf;
+
+ /* <query> element and <streamhost> elements */
+ struct xt_node *qnode, *shnode;
+
+ enum
+ {
+ BS_PHASE_CONNECT,
+ BS_PHASE_CONNECTED,
+ BS_PHASE_REQUEST,
+ BS_PHASE_REPLY,
+ BS_PHASE_REPLY_HAVE_LEN
+ } phase;
+
+ /* SHA1( SID + Initiator JID + Target JID) */
+ char *pseudoadr;
+
+ void (*parentfree) ( file_transfer_t *ft );
+
+ gint connect_timeout;
+};
+
+struct socks5_hdr
+{
+ unsigned char ver;
+ union
+ {
+ unsigned char cmd;
+ unsigned char rep;
+ } cmdrep;
+ unsigned char rsv;
+ unsigned char atyp;
+};
+
+struct socks5_message
+{
+ struct socks5_hdr hdr;
+ unsigned char addrlen;
+ unsigned char address[64];
+};
+
+/* connect() timeout in seconds. */
+#define JABBER_BS_CONTIMEOUT 15
+
+/* shouldn't matter if it's mostly too much, kernel's smart about that
+ * and will only reserve some address space */
+#define JABBER_BS_BUFSIZE 65536
+
+gboolean jabber_bs_handshake( gpointer data, gint fd, b_input_condition cond );
+
+gboolean jabber_bs_handshake_abort( struct bs_handshake_data *bhd, char *format, ... );
+
+void jabber_bs_answer_request( struct bs_handshake_data *bhd );
+
+gboolean jabber_bs_read( gpointer data, gint fd, b_input_condition cond );
+
+void jabber_bs_out_of_data( file_transfer_t *ft );
+
+void jabber_bs_canceled( file_transfer_t *ft , char *reason );
+
+
+void jabber_bs_free_transfer( file_transfer_t *ft) {
+ struct jabber_transfer *tf = ft->data;
+ struct bs_handshake_data *bhd = tf->streamhandle;
+ void (*parentfree) ( file_transfer_t *ft );
+
+ parentfree = bhd->parentfree;
+
+ if ( tf->watch_in )
+ b_event_remove( tf->watch_in );
+
+ if( tf->watch_out )
+ b_event_remove( tf->watch_out );
+
+ g_free( bhd->pseudoadr );
+ xt_free_node( bhd->qnode );
+ g_free( bhd );
+
+ parentfree( ft );
+}
+
+/*
+ * Parses an incoming bytestream request and calls jabber_bs_handshake on success.
+ */
+int jabber_bs_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode)
+{
+ char *sid, *ini_jid, *tgt_jid, *mode, *iq_id;
+ struct jabber_data *jd = ic->proto_data;
+ struct jabber_transfer *tf = NULL;
+ GSList *tflist;
+ struct bs_handshake_data *bhd;
+
+ sha1_state_t sha;
+ char hash_hex[41];
+ unsigned char hash[20];
+ int i;
+
+ if( !(iq_id = xt_find_attr( node, "id" ) ) ||
+ !(ini_jid = xt_find_attr( node, "from" ) ) ||
+ !(tgt_jid = xt_find_attr( node, "to" ) ) ||
+ !(sid = xt_find_attr( qnode, "sid" ) ) )
+ {
+ imcb_log( ic, "WARNING: Received incomplete SI bytestream request");
+ return XT_HANDLED;
+ }
+
+ if( ( mode = xt_find_attr( qnode, "mode" ) ) &&
+ ( strcmp( mode, "tcp" ) != 0 ) )
+ {
+ imcb_log( ic, "WARNING: Received SI Request for unsupported bytestream mode %s", xt_find_attr( qnode, "mode" ) );
+ return XT_HANDLED;
+ }
+
+ /* Let's see if we can find out what this bytestream should be for... */
+
+ for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) )
+ {
+ struct jabber_transfer *tft = tflist->data;
+ if( ( strcmp( tft->sid, sid ) == 0 ) &&
+ ( strcmp( tft->ini_jid, ini_jid ) == 0 ) &&
+ ( strcmp( tft->tgt_jid, tgt_jid ) == 0 ) )
+ {
+ tf = tft;
+ break;
+ }
+ }
+
+ if (!tf)
+ {
+ imcb_log( ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid );
+ return XT_HANDLED;
+ }
+
+ /* iq_id and canceled can be reused since SI is done */
+ g_free( tf->iq_id );
+ tf->iq_id = g_strdup( iq_id );
+
+ tf->ft->canceled = jabber_bs_canceled;
+
+ /* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */
+ sha1_init( &sha );
+ sha1_append( &sha, (unsigned char*) sid, strlen( sid ) );
+ sha1_append( &sha, (unsigned char*) ini_jid, strlen( ini_jid ) );
+ sha1_append( &sha, (unsigned char*) tgt_jid, strlen( tgt_jid ) );
+ sha1_finish( &sha, hash );
+
+ for( i = 0; i < 20; i ++ )
+ sprintf( hash_hex + i * 2, "%02x", hash[i] );
+
+ bhd = g_new0( struct bs_handshake_data, 1 );
+ bhd->tf = tf;
+ bhd->qnode = xt_dup( qnode );
+ bhd->shnode = bhd->qnode->children;
+ bhd->phase = BS_PHASE_CONNECT;
+ bhd->pseudoadr = g_strdup( hash_hex );
+ tf->streamhandle = bhd;
+ bhd->parentfree = tf->ft->free;
+ tf->ft->free = jabber_bs_free_transfer;
+
+ jabber_bs_handshake( bhd, 0, 0 );
+
+ return XT_HANDLED;
+}
+
+/*
+ * This function is scheduled in bs_handshake via b_timeout_add after a (non-blocking) connect().
+ */
+gboolean jabber_bs_connect_timeout( gpointer data, gint fd, b_input_condition cond )
+{
+ struct bs_handshake_data *bhd = data;
+
+ bhd->connect_timeout = 0;
+
+ jabber_bs_handshake_abort( bhd, "no connection after %d seconds", JABBER_BS_CONTIMEOUT );
+
+ return FALSE;
+}
+
+/*
+ * This is what a protocol handshake can look like in cooperative multitasking :)
+ * Might be confusing at first because it's called from different places and is recursing.
+ * (places being the event thread, bs_request, bs_handshake_abort, and itself)
+ *
+ * All in all, it turned out quite nice :)
+ */
+gboolean jabber_bs_handshake( gpointer data, gint fd, b_input_condition cond )
+{
+
+/* very useful */
+#define ASSERTSOCKOP(op, msg) \
+ if( (op) == -1 ) \
+ return jabber_bs_handshake_abort( bhd , msg ": %s", strerror( errno ) );
+
+ struct bs_handshake_data *bhd = data;
+ struct pollfd pfd = { .fd = fd, .events = POLLHUP|POLLERR };
+ short revents;
+
+ if ( bhd->connect_timeout )
+ {
+ b_event_remove( bhd->connect_timeout );
+ bhd->connect_timeout = 0;
+ }
+
+
+ /* we need the real io condition */
+ if ( poll( &pfd, 1, 0 ) == -1 )
+ {
+ imcb_log( bhd->tf->ic, "poll() failed, weird!" );
+ revents = 0;
+ };
+
+ revents = pfd.revents;
+
+ if( revents & POLLERR )
+ {
+ int sockerror;
+ socklen_t errlen = sizeof( sockerror );
+
+ if ( getsockopt( fd, SOL_SOCKET, SO_ERROR, &sockerror, &errlen ) )
+ return jabber_bs_handshake_abort( bhd, "getsockopt() failed, unknown socket error during SOCKS5 handshake (weird!)" );
+
+ if ( bhd->phase == BS_PHASE_CONNECTED )
+ return jabber_bs_handshake_abort( bhd, "connect() failed: %s", strerror( sockerror ) );
+
+ return jabber_bs_handshake_abort( bhd, "Socket error during SOCKS5 handshake(weird!): %s", strerror( sockerror ) );
+ }
+
+ if( revents & POLLHUP )
+ return jabber_bs_handshake_abort( bhd, "Remote end closed connection" );
+
+
+ switch( bhd->phase )
+ {
+ case BS_PHASE_CONNECT:
+ {
+ struct xt_node *c;
+ char *host, *port;
+ struct addrinfo hints, *rp;
+
+ if( ( c = bhd->shnode = xt_find_node( bhd->shnode, "streamhost" ) ) &&
+ ( port = xt_find_attr( c, "port" ) ) &&
+ ( host = xt_find_attr( c, "host" ) ) &&
+ xt_find_attr( c, "jid" ) )
+ {
+ memset( &hints, 0, sizeof( struct addrinfo ) );
+ hints.ai_socktype = SOCK_STREAM;
+
+ if ( getaddrinfo( host, port, &hints, &rp ) != 0 )
+ return jabber_bs_handshake_abort( bhd, "getaddrinfo() failed: %s", strerror( errno ) );
+
+ ASSERTSOCKOP( bhd->tf->fd = fd = socket( rp->ai_family, rp->ai_socktype, 0 ), "Opening socket" );
+
+ sock_make_nonblocking( fd );
+
+ imcb_log( bhd->tf->ic, "Transferring file %s: Connecting to streamhost %s:%s", bhd->tf->ft->file_name, host, port );
+
+ if( ( connect( fd, rp->ai_addr, rp->ai_addrlen ) == -1 ) &&
+ ( errno != EINPROGRESS ) )
+ return jabber_bs_handshake_abort( bhd , "connect() failed: %s", strerror( errno ) );
+
+ freeaddrinfo( rp );
+
+ bhd->phase = BS_PHASE_CONNECTED;
+
+ bhd->tf->watch_out = b_input_add( fd, GAIM_INPUT_WRITE, jabber_bs_handshake, bhd );
+
+ /* since it takes forever(3mins?) till connect() fails on itself we schedule a timeout */
+ bhd->connect_timeout = b_timeout_add( JABBER_BS_CONTIMEOUT * 1000, jabber_bs_connect_timeout, bhd );
+
+ bhd->tf->watch_in = 0;
+ return FALSE;
+ } else
+ return jabber_bs_handshake_abort( bhd, c ? "incomplete streamhost entry: host=%s port=%s jid=%s" : NULL,
+ host, port, xt_find_attr( c, "jid" ) );
+ }
+ case BS_PHASE_CONNECTED:
+ {
+ struct {
+ unsigned char ver;
+ unsigned char nmethods;
+ unsigned char method;
+ } socks5_hello = {
+ .ver = 5,
+ .nmethods = 1,
+ .method = 0x00 /* no auth */
+ /* one could also implement username/password. If you know
+ * a jabber client or proxy that actually does it, tell me.
+ */
+ };
+
+ ASSERTSOCKOP( send( fd, &socks5_hello, sizeof( socks5_hello ) , 0 ), "Sending auth request" );
+
+ bhd->phase = BS_PHASE_REQUEST;
+
+ bhd->tf->watch_in = b_input_add( fd, GAIM_INPUT_READ, jabber_bs_handshake, bhd );
+
+ bhd->tf->watch_out = 0;
+ return FALSE;
+ }
+ case BS_PHASE_REQUEST:
+ {
+ struct socks5_message socks5_connect =
+ {
+ .hdr =
+ {
+ .ver = 5,
+ .cmdrep.cmd = 0x01,
+ .rsv = 0,
+ .atyp = 0x03
+ },
+ .addrlen = strlen( bhd->pseudoadr )
+ };
+ int ret;
+ char buf[2];
+
+ /* If someone's trying to be funny and sends only one byte at a time we'll fail :) */
+ ASSERTSOCKOP( ret = recv( fd, buf, 2, 0 ) , "Receiving auth reply" );
+
+ if( !( ret == 2 ) ||
+ !( buf[0] == 5 ) ||
+ !( buf[1] == 0 ) )
+ return jabber_bs_handshake_abort( bhd, "Auth not accepted by streamhost (reply: len=%d, ver=%d, status=%d)",
+ ret, buf[0], buf[1] );
+
+ /* copy hash into connect message */
+ memcpy( socks5_connect.address, bhd->pseudoadr, socks5_connect.addrlen );
+
+ /* after the address comes the port, which is always 0 */
+ memset( socks5_connect.address + socks5_connect.addrlen, 0, sizeof( in_port_t ) );
+
+ ASSERTSOCKOP( send( fd, &socks5_connect, sizeof( struct socks5_hdr ) + 1 + socks5_connect.addrlen + sizeof( in_port_t ), 0 ) , "Sending SOCKS5 Connect" );
+
+ bhd->phase = BS_PHASE_REPLY;
+
+ return TRUE;
+ }
+ case BS_PHASE_REPLY:
+ case BS_PHASE_REPLY_HAVE_LEN:
+ {
+ /* we have to wait till we have the address length, then we know how much data is left
+ * (not that we'd actually care about that data, but we need to eat it all up anyway)
+ */
+ struct socks5_message socks5_reply;
+ int ret;
+ int expectedbytes =
+ sizeof( struct socks5_hdr ) + 1 +
+ ( bhd->phase == BS_PHASE_REPLY_HAVE_LEN ? socks5_reply.addrlen + sizeof( in_port_t ) : 0 );
+
+ /* notice the peek, we're doing this till enough is there */
+ ASSERTSOCKOP( ret = recv( fd, &socks5_reply, expectedbytes, MSG_PEEK ) , "Peeking for SOCKS5 CONNECT reply" );
+
+ if ( ret == 0 )
+ return jabber_bs_handshake_abort( bhd , "peer has shutdown connection" );
+
+ /* come again */
+ if ( ret < expectedbytes )
+ return TRUE;
+
+ if ( bhd->phase == BS_PHASE_REPLY )
+ {
+ if( !( socks5_reply.hdr.ver == 5 ) ||
+ !( socks5_reply.hdr.cmdrep.rep == 0 ) ||
+ !( socks5_reply.hdr.atyp == 3 ) ||
+ !( socks5_reply.addrlen <= 62 ) ) /* should also be 40, but who cares as long as all fits in the buffer... */
+ return jabber_bs_handshake_abort( bhd, "SOCKS5 CONNECT failed (reply: ver=%d, rep=%d, atyp=%d, addrlen=%d",
+ socks5_reply.hdr.ver,
+ socks5_reply.hdr.cmdrep.rep,
+ socks5_reply.hdr.atyp,
+ socks5_reply.addrlen);
+
+ /* and again for the rest */
+ bhd->phase = BS_PHASE_REPLY_HAVE_LEN;
+
+ /* since it's very likely that the rest is there as well,
+ * let's not wait for the event loop to call us again */
+ return jabber_bs_handshake( bhd , fd, 0 );
+ }
+
+ /* got it all, remove it from the queue */
+ ASSERTSOCKOP( ret = recv( fd, &socks5_reply, expectedbytes, 0 ) , "Dequeueing MSG_PEEK'ed data after SOCKS5 CONNECT" );
+
+ /* this shouldn't happen */
+ if ( ret < expectedbytes )
+ return jabber_bs_handshake_abort( bhd, "internal error, couldn't dequeue MSG_PEEK'ed data after SOCKS5 CONNECT" );
+
+ /* we're actually done now... */
+
+ jabber_bs_answer_request( bhd );
+
+ bhd->tf->watch_in = 0;
+ return FALSE;
+ }
+ default:
+ /* BUG */
+ imcb_log( bhd->tf->ic, "BUG in file transfer code: undefined handshake phase" );
+
+ bhd->tf->watch_in = 0;
+ return FALSE;
+ }
+#undef ASSERTSOCKOP
+#undef JABBER_BS_ERR_CONDS
+}
+
+/*
+ * If the handshake failed we can try the next streamhost, if there is one.
+ * An intelligent sender would probably specify himself as the first streamhost and
+ * a proxy as the second (Kopete is an example here). That way, a (potentially)
+ * slow proxy is only used if neccessary.
+ */
+gboolean jabber_bs_handshake_abort( struct bs_handshake_data *bhd, char *format, ... )
+{
+ struct jabber_transfer *tf = bhd->tf;
+ struct xt_node *reply, *iqnode;
+
+ if( bhd->shnode )
+ {
+ if( format ) {
+ va_list params;
+ va_start( params, format );
+ char error[128];
+
+ if( vsnprintf( error, 128, format, params ) < 0 )
+ sprintf( error, "internal error parsing error string (BUG)" );
+ va_end( params );
+
+ imcb_log( tf->ic, "Transferring file %s: connection to streamhost %s:%s failed (%s)",
+ tf->ft->file_name,
+ xt_find_attr( bhd->shnode, "host" ),
+ xt_find_attr( bhd->shnode, "port" ),
+ error );
+ }
+
+ /* Alright, this streamhost failed, let's try the next... */
+ bhd->phase = BS_PHASE_CONNECT;
+ bhd->shnode = bhd->shnode->next;
+
+ /* the if is not neccessary but saves us one recursion */
+ if( bhd->shnode )
+ return jabber_bs_handshake( bhd, 0, 0 );
+ }
+
+ /* out of stream hosts */
+
+ iqnode = jabber_make_packet( "iq", "result", tf->ini_jid, NULL );
+ reply = jabber_make_error_packet( iqnode, "item-not-found", "cancel" , "404" );
+ xt_free_node( iqnode );
+
+ xt_add_attr( reply, "id", tf->iq_id );
+
+ if( !jabber_write_packet( tf->ic, reply ) )
+ imcb_log( tf->ic, "WARNING: Error transmitting bytestream response" );
+ xt_free_node( reply );
+
+ imcb_file_canceled( tf->ft, "couldn't connect to any streamhosts" );
+
+ bhd->tf->watch_in = 0;
+ return FALSE;
+}
+
+/*
+ * After the SOCKS5 handshake succeeds we need to inform the initiator which streamhost we chose.
+ * If he is the streamhost himself, he might already know that. However, if it's a proxy,
+ * the initiator will have to make a connection himself.
+ */
+void jabber_bs_answer_request( struct bs_handshake_data *bhd )
+{
+ struct jabber_transfer *tf = bhd->tf;
+ struct xt_node *reply;
+
+ imcb_log( tf->ic, "Transferring file %s: established SOCKS5 connection to %s:%s",
+ tf->ft->file_name,
+ xt_find_attr( bhd->shnode, "host" ),
+ xt_find_attr( bhd->shnode, "port" ) );
+
+ tf->ft->data = tf;
+ tf->ft->started = time( NULL );
+ tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_read, tf );
+ tf->ft->out_of_data = jabber_bs_out_of_data;
+
+ reply = xt_new_node( "streamhost-used", NULL, NULL );
+ xt_add_attr( reply, "jid", xt_find_attr( bhd->shnode, "jid" ) );
+
+ reply = xt_new_node( "query", NULL, reply );
+ xt_add_attr( reply, "xmlns", XMLNS_BYTESTREAMS );
+
+ reply = jabber_make_packet( "iq", "result", tf->ini_jid, reply );
+
+ xt_add_attr( reply, "id", tf->iq_id );
+
+ if( !jabber_write_packet( tf->ic, reply ) )
+ imcb_file_canceled( tf->ft, "Error transmitting bytestream response" );
+ xt_free_node( reply );
+}
+
+/* Reads till it is unscheduled or the receiver signifies an overflow. */
+gboolean jabber_bs_read( gpointer data, gint fd, b_input_condition cond )
+{
+ int ret;
+ struct jabber_transfer *tf = data;
+ char *buffer = g_malloc( JABBER_BS_BUFSIZE );
+
+ if (tf->receiver_overflow)
+ {
+ if( tf->watch_in )
+ {
+ /* should never happen, BUG */
+ imcb_file_canceled( tf->ft, "Bug in jabber file transfer code: read while overflow is true. Please report" );
+ return FALSE;
+ }
+ }
+
+ ret = recv( fd, buffer, JABBER_BS_BUFSIZE, 0 );
+
+ if( ret == -1 )
+ {
+ /* shouldn't actually happen */
+ if( errno == EAGAIN )
+ return TRUE;
+
+ imcb_file_canceled( tf->ft, "Error reading tcp socket" ); /* , strerror( errnum ) */
+
+ return FALSE;
+ }
+
+ /* that should be all */
+ if( ret == 0 )
+ return FALSE;
+
+ tf->bytesread += ret;
+
+ buffer = g_realloc( buffer, ret );
+
+ if ( ( tf->receiver_overflow = imcb_file_write( tf->ft, buffer, ret ) ) )
+ {
+ /* wait for imcb to run out of data */
+ tf->watch_in = 0;
+ return FALSE;
+ }
+
+
+ return TRUE;
+}
+
+/* imcb callback that is invoked when it runs out of data.
+ * We reschedule jabber_bs_read here if neccessary. */
+void jabber_bs_out_of_data( file_transfer_t *ft )
+{
+ struct jabber_transfer *tf = ft->data;
+
+ tf->receiver_overflow = FALSE;
+
+ if ( !tf->watch_in )
+ tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_read, tf );
+}
+
+/* Bad luck */
+void jabber_bs_canceled( file_transfer_t *ft , char *reason )
+{
+ struct jabber_transfer *tf = ft->data;
+
+ imcb_log( tf->ic, "File transfer aborted: %s", reason );
+}
diff --git a/protocols/nogaim.h b/protocols/nogaim.h
index 0e890464..8651754a 100644
--- a/protocols/nogaim.h
+++ b/protocols/nogaim.h
@@ -42,6 +42,7 @@
#include "account.h"
#include "proxy.h"
#include "md5.h"
+#include "ft.h"
#define BUF_LEN MSG_LEN
#define BUF_LONG ( BUF_LEN * 2 )
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 }
};