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. --- 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 + 8 files changed, 1065 insertions(+), 22 deletions(-) create mode 100644 protocols/ft.h create mode 100644 protocols/jabber/si.c create mode 100644 protocols/jabber/stream.c (limited to 'protocols') 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 ) -- 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 --- 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 + 10 files changed, 1127 insertions(+), 606 deletions(-) create mode 100644 protocols/jabber/s5bytestream.c delete mode 100644 protocols/jabber/stream.c (limited to 'protocols') 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. --- protocols/ft.h | 31 +++++---- protocols/jabber/jabber.h | 3 +- protocols/jabber/s5bytestream.c | 137 ++++++++++++++++++++++------------------ protocols/jabber/si.c | 23 +++---- 4 files changed, 108 insertions(+), 86 deletions(-) (limited to 'protocols') 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(-) (limited to 'protocols') 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 --- protocols/jabber/iq.c | 162 +++++++++++++++++++++- protocols/jabber/jabber.h | 16 ++- protocols/jabber/s5bytestream.c | 298 ++++++++++++++++++++++++++++++---------- protocols/jabber/si.c | 10 ++ 4 files changed, 410 insertions(+), 76 deletions(-) (limited to 'protocols') 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) --- 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 +- 9 files changed, 59 insertions(+), 30 deletions(-) (limited to 'protocols') 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; -- 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(-) (limited to 'protocols') 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(-) (limited to 'protocols') 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 --- protocols/jabber/s5bytestream.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'protocols') 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 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 --- protocols/jabber/iq.c | 5 +++-- protocols/jabber/s5bytestream.c | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) (limited to 'protocols') 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(-) (limited to 'protocols') 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). --- protocols/jabber/si.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'protocols') 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 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'protocols') 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 ) ); } -- 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(-) (limited to 'protocols') 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(-) (limited to 'protocols') 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(+) (limited to 'protocols') 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 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(-) (limited to 'protocols') 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) --- protocols/ft.h | 2 ++ protocols/jabber/jabber.c | 12 ++++++++++++ protocols/jabber/s5bytestream.c | 14 +++++++------- protocols/jabber/si.c | 15 --------------- 4 files changed, 21 insertions(+), 22 deletions(-) (limited to 'protocols') 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 (limited to 'protocols') 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 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(+) (limited to 'protocols') 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). --- protocols/jabber/s5bytestream.c | 87 ++++++++--------------------------------- protocols/msn/invitation.c | 70 +++------------------------------ 2 files changed, 22 insertions(+), 135 deletions(-) (limited to 'protocols') 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 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/ --- 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 ++++-- 7 files changed, 33 insertions(+), 23 deletions(-) (limited to 'protocols') 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. --- protocols/jabber/s5bytestream.c | 2 +- protocols/msn/invitation.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'protocols') 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(-) (limited to 'protocols') 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(-) (limited to 'protocols') 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(-) (limited to 'protocols') 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 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) (limited to 'protocols') 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 )) -- 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(-) (limited to 'protocols') 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(-) (limited to 'protocols') 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(-) (limited to 'protocols') 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 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(-) (limited to 'protocols') 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