/***************************************************************************\ * * * 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 ); int jabber_si_send_request(struct im_connection *ic, char *who, struct jabber_transfer *tf ); /* file_transfer free() callback */ void jabber_si_free_transfer( file_transfer_t *ft) { struct jabber_transfer *tf = ft->data; struct jabber_data *jd = tf->ic->proto_data; if ( tf->watch_in ) b_event_remove( tf->watch_in ); jd->filetransfers = g_slist_remove( jd->filetransfers, tf ); if( tf->fd != -1 ) { closesocket( tf->fd ); tf->fd = -1; } if( tf->disco_timeout ) b_event_remove( tf->disco_timeout ); g_free( tf->ini_jid ); g_free( tf->tgt_jid ); g_free( tf->iq_id ); g_free( tf->sid ); g_free( tf ); } /* file_transfer canceled() callback */ void jabber_si_canceled( file_transfer_t *ft, char *reason ) { struct jabber_transfer *tf = ft->data; struct xt_node *reply, *iqnode; if( tf->accepted ) return; iqnode = jabber_make_packet( "iq", "error", tf->ini_jid, NULL ); xt_add_attr( iqnode, "id", tf->iq_id ); reply = jabber_make_error_packet( iqnode, "forbidden", "cancel", "403" ); xt_free_node( iqnode ); if( !jabber_write_packet( tf->ic, reply ) ) imcb_log( tf->ic, "WARNING: Error generating reply to file transfer request" ); xt_free_node( reply ); } int jabber_si_check_features( struct jabber_transfer *tf, GSList *features ) { int foundft = FALSE, foundbt = FALSE, foundsi = FALSE; while ( features ) { if( !strcmp( features->data, XMLNS_FILETRANSFER ) ) foundft = TRUE; if( !strcmp( features->data, XMLNS_BYTESTREAMS ) ) foundbt = TRUE; if( !strcmp( features->data, XMLNS_SI ) ) foundsi = TRUE; features = g_slist_next(features); } if( !foundft ) imcb_file_canceled( tf->ic, tf->ft, "Buddy's client doesn't feature file transfers" ); else if( !foundbt ) imcb_file_canceled( tf->ic, tf->ft, "Buddy's client doesn't feature byte streams (required)" ); else if( !foundsi ) imcb_file_canceled( tf->ic, 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->ic, tf->ft ); } gboolean jabber_si_waitfor_disco( gpointer data, gint fd, b_input_condition cond ) { struct jabber_transfer *tf = data; struct jabber_data *jd = tf->ic->proto_data; tf->disco_timeout_fired++; if( tf->bud->features && jd->have_streamhosts==1 ) { tf->disco_timeout = 0; jabber_si_transfer_start( tf ); return FALSE; } /* 8 seconds should be enough for server and buddy to respond */ if ( tf->disco_timeout_fired < 16 ) return TRUE; if( !tf->bud->features && jd->have_streamhosts!=1 ) imcb_log( tf->ic, "Couldn't get buddy's features nor discover all services of the server" ); else if( !tf->bud->features ) imcb_log( tf->ic, "Couldn't get buddy's features" ); else imcb_log( tf->ic, "Couldn't discover some of the server's services" ); tf->disco_timeout = 0; jabber_si_transfer_start( tf ); return FALSE; } void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who ) { struct jabber_transfer *tf; struct jabber_data *jd = ic->proto_data; struct jabber_buddy *bud; char *server = jd->server, *s; if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) ) bud = jabber_buddy_by_ext_jid( ic, who, 0 ); else bud = jabber_buddy_by_jid( ic, who, 0 ); if( bud == NULL ) { imcb_file_canceled( ic, ft, "Couldn't find buddy (BUG?)" ); return; } imcb_log( ic, "Trying to send %s(%zd bytes) to %s", ft->file_name, ft->file_size, who ); tf = g_new0( struct jabber_transfer, 1 ); tf->ic = ic; tf->ft = ft; tf->fd = -1; tf->ft->data = tf; tf->ft->free = jabber_si_free_transfer; tf->bud = bud; ft->write = jabber_bs_send_write; jd->filetransfers = g_slist_prepend( jd->filetransfers, tf ); /* query buddy's features and server's streaming proxies if neccessary */ if( !tf->bud->features ) jabber_iq_query_features( ic, bud->full_jid ); /* If 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. */ if( !tf->bud->features || jd->have_streamhosts!=1 ) tf->disco_timeout = b_timeout_add( 500, jabber_si_waitfor_disco, tf ); else jabber_si_transfer_start( tf ); } /* * First function that gets called when a file transfer request comes in. * A lot to parse. * * We choose a stream type from the options given by the initiator. * Then we wait for imcb to call the accept or cancel callbacks. */ int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, struct xt_node *sinode) { struct xt_node *c, *d, *reply; char *sid, *ini_jid, *tgt_jid, *iq_id, *s, *ext_jid, *size_s; struct jabber_buddy *bud; int requestok = FALSE; char *name, *cmp; 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" ) ) || !( cmp = xt_find_attr( sinode, "profile" ) ) || !( 0 == strcmp( cmp, XMLNS_FILETRANSFER ) ) || !( d = xt_find_node( sinode->children, "file" ) ) || !( 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" ) ) || !( cmp = xt_find_attr( d, "xmlns" ) ) || !( 0 == strcmp( cmp, XMLNS_FEATURE ) ) || !( d = xt_find_node( d->children, "x" ) ) || !( 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" ) ) || !( cmp = xt_find_attr( d, "var" ) ) || !( 0 == strcmp( cmp, "stream-method" ) ) ) { 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" ) ) && ( d->text != NULL ) && ( strcmp( d->text, XMLNS_BYTESTREAMS ) == 0 ) ) { requestok = TRUE; break; } else { c = c->next; } if ( !requestok ) imcb_log( ic, "WARNING: Unsupported file transfer request from %s", ini_jid); } 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 ) ) ) { bud->last_msg = 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; } if( s ) *s = '/'; } if( !requestok ) { reply = jabber_make_error_packet( node, "item-not-found", "cancel", NULL ); if (!jabber_write_packet( ic, reply )) imcb_log( ic, "WARNING: Error generating reply to file transfer request" ); xt_free_node( reply ); return XT_HANDLED; } /* Request is fine. */ tf = g_new0( struct jabber_transfer, 1 ); tf->ini_jid = g_strdup( ini_jid ); tf->tgt_jid = g_strdup( tgt_jid ); tf->iq_id = g_strdup( iq_id ); tf->sid = g_strdup( sid ); tf->ic = ic; tf->ft = ft; tf->fd = -1; tf->ft->data = tf; tf->ft->accept = jabber_si_answer_request; tf->ft->free = jabber_si_free_transfer; tf->ft->canceled = jabber_si_canceled; jd->filetransfers = g_slist_prepend( jd->filetransfers, tf ); return XT_HANDLED; } /* * imc called the accept callback which probably means that the user accepted this file transfer. * We send our response to the initiator. * In the next step, the initiator will send us a request for the given stream type. * (currently that can only be a SOCKS5 bytestream) */ void jabber_si_answer_request( file_transfer_t *ft ) { struct jabber_transfer *tf = ft->data; struct xt_node *node, *sinode, *reply; /* generate response, start with the SI tag */ sinode = xt_new_node( "si", NULL, NULL ); xt_add_attr( sinode, "xmlns", XMLNS_SI ); xt_add_attr( sinode, "profile", XMLNS_FILETRANSFER ); xt_add_attr( sinode, "id", tf->sid ); /* now the file tag */ node = xt_new_node( "file", NULL, NULL ); xt_add_attr( node, "xmlns", XMLNS_FILETRANSFER ); xt_add_child( sinode, node ); /* and finally the feature tag */ node = xt_new_node( "field", NULL, NULL ); xt_add_attr( node, "var", "stream-method" ); xt_add_attr( node, "type", "list-single" ); /* Currently all we can do. One could also implement in-band (IBB) */ xt_add_child( node, xt_new_node( "value", XMLNS_BYTESTREAMS, NULL ) ); node = xt_new_node( "x", NULL, node ); xt_add_attr( node, "xmlns", XMLNS_XDATA ); xt_add_attr( node, "type", "submit" ); node = xt_new_node( "feature", NULL, node ); xt_add_attr( node, "xmlns", XMLNS_FEATURE ); xt_add_child( sinode, node ); reply = jabber_make_packet( "iq", "result", tf->ini_jid, sinode ); xt_add_attr( reply, "id", tf->iq_id ); if( !jabber_write_packet( tf->ic, reply ) ) imcb_log( tf->ic, "WARNING: Error generating reply to file transfer request" ); else tf->accepted = TRUE; xt_free_node( reply ); } static xt_status jabber_si_handle_response(struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) { struct xt_node *c, *d; char *ini_jid = NULL, *tgt_jid, *iq_id, *cmp; GSList *tflist; struct jabber_transfer *tf=NULL; struct jabber_data *jd = ic->proto_data; if( !( tgt_jid = xt_find_attr( node, "from" ) ) || !( ini_jid = xt_find_attr( node, "to" ) ) ) { imcb_log( ic, "Invalid SI response from=%s to=%s", tgt_jid, ini_jid ); return XT_HANDLED; } /* All this means we expect something like this: ( I think ) * * * [ ] <-- not neccessary * * * * */ 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" ) ) || !( cmp = xt_find_attr( c, "xmlns" ) ) || !( strcmp( cmp, XMLNS_SI ) == 0 ) || !( d = xt_find_node( c->children, "feature" ) ) || !( cmp = xt_find_attr( d, "xmlns" ) ) || !( strcmp( cmp, XMLNS_FEATURE ) == 0 ) || !( d = xt_find_node( d->children, "x" ) ) || !( 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" ) ) || !( 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" ); return XT_HANDLED; } if( !( strcmp( d->text, XMLNS_BYTESTREAMS ) == 0 ) ) { /* since we should only have advertised what we can do and the peer should * only have chosen what we offered, this should never happen */ imcb_log( ic, "WARNING: Received invalid Stream Initiation response, method %s", d->text ); return XT_HANDLED; } /* Let's see if we can find out what this bytestream should be for... */ for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) { struct jabber_transfer *tft = tflist->data; if( ( strcmp( tft->iq_id, iq_id ) == 0 ) ) { tf = tft; break; } } if (!tf) { imcb_log( ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid ); return XT_HANDLED; } tf->ini_jid = g_strdup( ini_jid ); tf->tgt_jid = g_strdup( tgt_jid ); imcb_log( ic, "File %s: %s accepted the transfer!", tf->ft->file_name, tgt_jid ); jabber_bs_send_start( tf ); return XT_HANDLED; } int jabber_si_send_request(struct im_connection *ic, char *who, struct jabber_transfer *tf ) { struct xt_node *node, *sinode; struct jabber_buddy *bud; /* who knows how many bits the future holds :) */ char filesizestr[ 1 + ( int ) ( 0.301029995663981198f * sizeof( size_t ) * 8 ) ]; const char *methods[] = { XMLNS_BYTESTREAMS, //XMLNS_IBB, NULL }; const char **m; char *s; /* Maybe we should hash this? */ tf->sid = g_strdup_printf( "BitlBeeJabberSID%d", tf->ft->local_id ); if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) ) bud = jabber_buddy_by_ext_jid( ic, who, 0 ); else bud = jabber_buddy_by_jid( ic, who, 0 ); /* start with the SI tag */ sinode = xt_new_node( "si", NULL, NULL ); xt_add_attr( sinode, "xmlns", XMLNS_SI ); xt_add_attr( sinode, "profile", XMLNS_FILETRANSFER ); xt_add_attr( sinode, "id", tf->sid ); /* if( mimetype ) xt_add_attr( node, "mime-type", mimetype ); */ /* now the file tag */ /* if( desc ) node = xt_new_node( "desc", descr, NULL ); */ node = xt_new_node( "range", NULL, NULL ); sprintf( filesizestr, "%zd", tf->ft->file_size ); node = xt_new_node( "file", NULL, node ); xt_add_attr( node, "xmlns", XMLNS_FILETRANSFER ); xt_add_attr( node, "name", tf->ft->file_name ); xt_add_attr( node, "size", filesizestr ); /* if (hash) xt_add_attr( node, "hash", hash ); if (date) xt_add_attr( node, "date", date ); */ xt_add_child( sinode, node ); /* and finally the feature tag */ node = xt_new_node( "field", NULL, NULL ); xt_add_attr( node, "var", "stream-method" ); xt_add_attr( node, "type", "list-single" ); for ( m = methods ; *m ; m ++ ) xt_add_child( node, xt_new_node( "option", NULL, xt_new_node( "value", (char *)*m, NULL ) ) ); node = xt_new_node( "x", NULL, node ); xt_add_attr( node, "xmlns", XMLNS_XDATA ); xt_add_attr( node, "type", "form" ); node = xt_new_node( "feature", NULL, node ); xt_add_attr( node, "xmlns", XMLNS_FEATURE ); xt_add_child( sinode, node ); /* and we are there... */ node = jabber_make_packet( "iq", "set", bud ? bud->full_jid : who, sinode ); jabber_cache_add( ic, node, jabber_si_handle_response ); tf->iq_id = g_strdup( xt_find_attr( node, "id" ) ); return jabber_write_packet( ic, node ); }