source: protocols/jabber/stream.c @ 2c2df7d

Last change on this file since 2c2df7d was 2c2df7d, checked in by ulim <a.sporto+bee@…>, at 2007-11-28T21:07:30Z

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.

  • Property mode set to 100644
File size: 17.6 KB
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Jabber module - stream handling                                          *
5*                                                                           *
6*  Copyright 2007 Uli Meis <a.sporto+bee@gmail.com>                         *
7*                                                                           *
8*  This program is free software; you can redistribute it and/or modify     *
9*  it under the terms of the GNU General Public License as published by     *
10*  the Free Software Foundation; either version 2 of the License, or        *
11*  (at your option) any later version.                                      *
12*                                                                           *
13*  This program is distributed in the hope that it will be useful,          *
14*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
15*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
16*  GNU General Public License for more details.                             *
17*                                                                           *
18*  You should have received a copy of the GNU General Public License along  *
19*  with this program; if not, write to the Free Software Foundation, Inc.,  *
20*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
21*                                                                           *
22\***************************************************************************/
23
24#include "jabber.h"
25#include "sha1.h"
26#include <poll.h>
27
28/* Some structs for the SOCKS5 handshake */
29
30struct bs_handshake_data {
31
32        struct jabber_transfer *tf;
33
34        /* <query> element and <streamhost> elements */
35        struct xt_node *qnode, *shnode;
36
37        enum 
38        { 
39                BS_PHASE_CONNECT, 
40                BS_PHASE_CONNECTED, 
41                BS_PHASE_REQUEST, 
42                BS_PHASE_REPLY, 
43                BS_PHASE_REPLY_HAVE_LEN
44        } phase;
45
46        /* SHA1( SID + Initiator JID + Target JID) */
47        char *pseudoadr;
48
49        void (*parentfree) ( file_transfer_t *ft );
50
51        gint connect_timeout;
52};
53
54struct socks5_hdr
55{
56        unsigned char ver;
57        union
58        {
59                unsigned char cmd;
60                unsigned char rep;
61        } cmdrep;
62        unsigned char rsv;
63        unsigned char atyp;
64};
65
66struct socks5_message
67{
68        struct socks5_hdr hdr;
69        unsigned char addrlen;
70        unsigned char address[64];
71}; 
72
73/* connect() timeout in seconds. */
74#define JABBER_BS_CONTIMEOUT 15
75
76/* shouldn't matter if it's mostly too much, kernel's smart about that
77 * and will only reserve some address space */
78#define JABBER_BS_BUFSIZE 65536
79
80gboolean jabber_bs_handshake( gpointer data, gint fd, b_input_condition cond );
81
82gboolean jabber_bs_handshake_abort( struct bs_handshake_data *bhd, char *format, ... );
83
84void jabber_bs_answer_request( struct bs_handshake_data *bhd );
85
86gboolean jabber_bs_read( gpointer data, gint fd, b_input_condition cond );
87
88void jabber_bs_out_of_data( file_transfer_t *ft );
89
90void jabber_bs_canceled( file_transfer_t *ft , char *reason );
91
92
93void jabber_bs_free_transfer( file_transfer_t *ft) {
94        struct jabber_transfer *tf = ft->data;
95        struct bs_handshake_data *bhd = tf->streamhandle;
96        void (*parentfree) ( file_transfer_t *ft );
97
98        parentfree = bhd->parentfree;
99
100        if ( tf->watch_in )
101                b_event_remove( tf->watch_in );
102       
103        if( tf->watch_out )
104                b_event_remove( tf->watch_out );
105       
106        g_free( bhd->pseudoadr );
107        xt_free_node( bhd->qnode );
108        g_free( bhd );
109
110        parentfree( ft );
111}
112
113/*
114 * Parses an incoming bytestream request and calls jabber_bs_handshake on success.
115 */
116int jabber_bs_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode)
117{
118        char *sid, *ini_jid, *tgt_jid, *mode, *iq_id;
119        struct jabber_data *jd = ic->proto_data;
120        struct jabber_transfer *tf = NULL;
121        GSList *tflist;
122        struct bs_handshake_data *bhd;
123
124        sha1_state_t sha;
125        char hash_hex[41];
126        unsigned char hash[20];
127        int i;
128       
129        if( !(iq_id   = xt_find_attr( node, "id" ) ) ||
130            !(ini_jid = xt_find_attr( node, "from" ) ) ||
131            !(tgt_jid = xt_find_attr( node, "to" ) ) ||
132            !(sid     = xt_find_attr( qnode, "sid" ) ) )
133        {
134                imcb_log( ic, "WARNING: Received incomplete SI bytestream request");
135                return XT_HANDLED;
136        }
137
138        if( ( mode = xt_find_attr( qnode, "mode" ) ) &&
139              ( strcmp( mode, "tcp" ) != 0 ) ) 
140        {
141                imcb_log( ic, "WARNING: Received SI Request for unsupported bytestream mode %s", xt_find_attr( qnode, "mode" ) );
142                return XT_HANDLED;
143        }
144
145        /* Let's see if we can find out what this bytestream should be for... */
146
147        for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) )
148        {
149                struct jabber_transfer *tft = tflist->data;
150                if( ( strcmp( tft->sid, sid ) == 0 ) &&
151                    ( strcmp( tft->ini_jid, ini_jid ) == 0 ) &&
152                    ( strcmp( tft->tgt_jid, tgt_jid ) == 0 ) )
153                {
154                        tf = tft;
155                        break;
156                }
157        }
158
159        if (!tf) 
160        {
161                imcb_log( ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid );
162                return XT_HANDLED;
163        }
164
165        /* iq_id and canceled can be reused since SI is done */
166        g_free( tf->iq_id );
167        tf->iq_id = g_strdup( iq_id );
168
169        tf->ft->canceled = jabber_bs_canceled;
170
171        /* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */
172        sha1_init( &sha );
173        sha1_append( &sha, (unsigned char*) sid, strlen( sid ) );
174        sha1_append( &sha, (unsigned char*) ini_jid, strlen( ini_jid ) );
175        sha1_append( &sha, (unsigned char*) tgt_jid, strlen( tgt_jid ) );
176        sha1_finish( &sha, hash );
177       
178        for( i = 0; i < 20; i ++ )
179                sprintf( hash_hex + i * 2, "%02x", hash[i] );
180               
181        bhd = g_new0( struct bs_handshake_data, 1 );
182        bhd->tf = tf;
183        bhd->qnode = xt_dup( qnode );
184        bhd->shnode = bhd->qnode->children;
185        bhd->phase = BS_PHASE_CONNECT;
186        bhd->pseudoadr = g_strdup( hash_hex );
187        tf->streamhandle = bhd;
188        bhd->parentfree = tf->ft->free;
189        tf->ft->free = jabber_bs_free_transfer;
190
191        jabber_bs_handshake( bhd, 0, 0 ); 
192
193        return XT_HANDLED;
194}
195
196/*
197 * This function is scheduled in bs_handshake via b_timeout_add after a (non-blocking) connect().
198 */
199gboolean jabber_bs_connect_timeout( gpointer data, gint fd, b_input_condition cond )
200{
201        struct bs_handshake_data *bhd = data;
202
203        bhd->connect_timeout = 0;
204
205        jabber_bs_handshake_abort( bhd, "no connection after %d seconds", JABBER_BS_CONTIMEOUT );
206
207        return FALSE;
208}
209
210/*
211 * This is what a protocol handshake can look like in cooperative multitasking :)
212 * Might be confusing at first because it's called from different places and is recursing.
213 * (places being the event thread, bs_request, bs_handshake_abort, and itself)
214 *
215 * All in all, it turned out quite nice :)
216 */
217gboolean jabber_bs_handshake( gpointer data, gint fd, b_input_condition cond )
218{
219
220/* very useful */
221#define ASSERTSOCKOP(op, msg) \
222        if( (op) == -1 ) \
223                return jabber_bs_handshake_abort( bhd , msg ": %s", strerror( errno ) );
224
225        struct bs_handshake_data *bhd = data;
226        struct pollfd pfd = { .fd = fd, .events = POLLHUP|POLLERR };
227        short revents;
228       
229        if ( bhd->connect_timeout )
230        {
231                b_event_remove( bhd->connect_timeout );
232                bhd->connect_timeout = 0;
233        }
234
235       
236        /* we need the real io condition */
237        if ( poll( &pfd, 1, 0 ) == -1 )
238        {
239                imcb_log( bhd->tf->ic, "poll() failed, weird!" );
240                revents = 0;
241        };
242
243        revents = pfd.revents;
244
245        if( revents & POLLERR )
246        {
247                int sockerror;
248                socklen_t errlen = sizeof( sockerror );
249
250                if ( getsockopt( fd, SOL_SOCKET, SO_ERROR, &sockerror, &errlen ) )
251                        return jabber_bs_handshake_abort( bhd, "getsockopt() failed, unknown socket error during SOCKS5 handshake (weird!)" );
252
253                if ( bhd->phase == BS_PHASE_CONNECTED )
254                        return jabber_bs_handshake_abort( bhd, "connect() failed: %s", strerror( sockerror ) );
255
256                return jabber_bs_handshake_abort( bhd, "Socket error during SOCKS5 handshake(weird!): %s", strerror( sockerror ) );
257        }
258
259        if( revents & POLLHUP )
260                return jabber_bs_handshake_abort( bhd, "Remote end closed connection" );
261       
262
263        switch( bhd->phase ) 
264        {
265        case BS_PHASE_CONNECT:
266                {
267                        struct xt_node *c;
268                        char *host, *port;
269                        struct addrinfo hints, *rp;
270
271                        if( ( c = bhd->shnode = xt_find_node( bhd->shnode, "streamhost" ) ) &&
272                            ( port = xt_find_attr( c, "port" ) ) &&
273                            ( host = xt_find_attr( c, "host" ) ) &&
274                            xt_find_attr( c, "jid" ) )
275                        {
276                                memset( &hints, 0, sizeof( struct addrinfo ) );
277                                hints.ai_socktype = SOCK_STREAM;
278
279                                if ( getaddrinfo( host, port, &hints, &rp ) != 0 )
280                                        return jabber_bs_handshake_abort( bhd, "getaddrinfo() failed: %s", strerror( errno ) );
281
282                                ASSERTSOCKOP( bhd->tf->fd = fd = socket( rp->ai_family, rp->ai_socktype, 0 ), "Opening socket" );
283
284                                sock_make_nonblocking( fd );
285
286                                imcb_log( bhd->tf->ic, "Transferring file %s: Connecting to streamhost %s:%s", bhd->tf->ft->file_name, host, port );
287
288                                if( ( connect( fd, rp->ai_addr, rp->ai_addrlen ) == -1 ) &&
289                                    ( errno != EINPROGRESS ) )
290                                        return jabber_bs_handshake_abort( bhd , "connect() failed: %s", strerror( errno ) );
291
292                                freeaddrinfo( rp );
293
294                                bhd->phase = BS_PHASE_CONNECTED;
295                               
296                                bhd->tf->watch_out = b_input_add( fd, GAIM_INPUT_WRITE, jabber_bs_handshake, bhd );
297
298                                /* since it takes forever(3mins?) till connect() fails on itself we schedule a timeout */
299                                bhd->connect_timeout = b_timeout_add( JABBER_BS_CONTIMEOUT * 1000, jabber_bs_connect_timeout, bhd );
300
301                                bhd->tf->watch_in = 0;
302                                return FALSE;
303                        } else
304                                return jabber_bs_handshake_abort( bhd, c ? "incomplete streamhost entry: host=%s port=%s jid=%s" : NULL,
305                                                                  host, port, xt_find_attr( c, "jid" ) );
306                }
307        case BS_PHASE_CONNECTED:
308                {
309                        struct {
310                                unsigned char ver;
311                                unsigned char nmethods;
312                                unsigned char method;
313                        } socks5_hello = {
314                                .ver = 5,
315                                .nmethods = 1,
316                                .method = 0x00 /* no auth */
317                                /* one could also implement username/password. If you know
318                                 * a jabber client or proxy that actually does it, tell me.
319                                 */
320                        };
321                       
322                        ASSERTSOCKOP( send( fd, &socks5_hello, sizeof( socks5_hello ) , 0 ), "Sending auth request" );
323
324                        bhd->phase = BS_PHASE_REQUEST;
325
326                        bhd->tf->watch_in = b_input_add( fd, GAIM_INPUT_READ, jabber_bs_handshake, bhd );
327
328                        bhd->tf->watch_out = 0;
329                        return FALSE;
330                }
331        case BS_PHASE_REQUEST:
332                {
333                        struct socks5_message socks5_connect = 
334                        {
335                                .hdr =
336                                {
337                                        .ver = 5,
338                                        .cmdrep.cmd = 0x01,
339                                        .rsv = 0,
340                                        .atyp = 0x03
341                                },
342                                .addrlen = strlen( bhd->pseudoadr )
343                        };
344                        int ret;
345                        char buf[2];
346
347                        /* If someone's trying to be funny and sends only one byte at a time we'll fail :) */
348                        ASSERTSOCKOP( ret = recv( fd, buf, 2, 0 ) , "Receiving auth reply" );
349
350                        if( !( ret == 2 ) ||
351                            !( buf[0] == 5 ) ||
352                            !( buf[1] == 0 ) )
353                                return jabber_bs_handshake_abort( bhd, "Auth not accepted by streamhost (reply: len=%d, ver=%d, status=%d)",
354                                                                        ret, buf[0], buf[1] );
355
356                        /* copy hash into connect message */
357                        memcpy( socks5_connect.address, bhd->pseudoadr, socks5_connect.addrlen );
358
359                        /* after the address comes the port, which is always 0 */
360                        memset( socks5_connect.address + socks5_connect.addrlen, 0, sizeof( in_port_t ) );
361
362                        ASSERTSOCKOP( send( fd, &socks5_connect, sizeof( struct socks5_hdr ) + 1 + socks5_connect.addrlen + sizeof( in_port_t ), 0 ) , "Sending SOCKS5 Connect" );
363
364                        bhd->phase = BS_PHASE_REPLY;
365
366                        return TRUE;
367                }
368        case BS_PHASE_REPLY:
369        case BS_PHASE_REPLY_HAVE_LEN:
370                {
371                        /* we have to wait till we have the address length, then we know how much data is left
372                         * (not that we'd actually care about that data, but we need to eat it all up anyway)
373                         */
374                        struct socks5_message socks5_reply;
375                        int ret;
376                        int expectedbytes = 
377                                sizeof( struct socks5_hdr ) + 1 + 
378                                ( bhd->phase == BS_PHASE_REPLY_HAVE_LEN ? socks5_reply.addrlen + sizeof( in_port_t ) : 0 );
379
380                        /* notice the peek, we're doing this till enough is there */
381                        ASSERTSOCKOP( ret = recv( fd, &socks5_reply, expectedbytes, MSG_PEEK ) , "Peeking for SOCKS5 CONNECT reply" );
382
383                        if ( ret == 0 )
384                                return jabber_bs_handshake_abort( bhd , "peer has shutdown connection" );
385
386                        /* come again */
387                        if ( ret < expectedbytes )
388                                return TRUE;
389
390                        if ( bhd->phase == BS_PHASE_REPLY )
391                        {
392                                if( !( socks5_reply.hdr.ver == 5 ) ||
393                                    !( socks5_reply.hdr.cmdrep.rep == 0 ) ||
394                                    !( socks5_reply.hdr.atyp == 3 ) ||
395                                    !( socks5_reply.addrlen <= 62 ) ) /* should also be 40, but who cares as long as all fits in the buffer... */
396                                        return jabber_bs_handshake_abort( bhd, "SOCKS5 CONNECT failed (reply: ver=%d, rep=%d, atyp=%d, addrlen=%d", 
397                                                socks5_reply.hdr.ver,
398                                                socks5_reply.hdr.cmdrep.rep,
399                                                socks5_reply.hdr.atyp,
400                                                socks5_reply.addrlen);
401
402                                /* and again for the rest */
403                                bhd->phase = BS_PHASE_REPLY_HAVE_LEN;
404                               
405                                /* since it's very likely that the rest is there as well,
406                                 * let's not wait for the event loop to call us again */
407                                return jabber_bs_handshake( bhd , fd, 0 );
408                        }
409
410                        /* got it all, remove it from the queue */
411                        ASSERTSOCKOP( ret = recv( fd, &socks5_reply, expectedbytes, 0 ) , "Dequeueing MSG_PEEK'ed data after SOCKS5 CONNECT" );
412
413                        /* this shouldn't happen */
414                        if ( ret < expectedbytes )
415                                return jabber_bs_handshake_abort( bhd, "internal error, couldn't dequeue MSG_PEEK'ed data after SOCKS5 CONNECT" );
416
417                        /* we're actually done now... */
418
419                        jabber_bs_answer_request( bhd );
420
421                        bhd->tf->watch_in = 0;
422                        return FALSE;
423                }
424        default:
425                /* BUG */
426                imcb_log( bhd->tf->ic, "BUG in file transfer code: undefined handshake phase" );
427
428                bhd->tf->watch_in = 0;
429                return FALSE;
430        }
431#undef ASSERTSOCKOP
432#undef JABBER_BS_ERR_CONDS
433}
434
435/*
436 * If the handshake failed we can try the next streamhost, if there is one.
437 * An intelligent sender would probably specify himself as the first streamhost and
438 * a proxy as the second (Kopete is an example here). That way, a (potentially)
439 * slow proxy is only used if neccessary.
440 */
441gboolean jabber_bs_handshake_abort( struct bs_handshake_data *bhd, char *format, ... )
442{
443        struct jabber_transfer *tf = bhd->tf;
444        struct xt_node *reply, *iqnode;
445
446        if( bhd->shnode ) 
447        {
448                if( format ) {
449                        va_list params;
450                        va_start( params, format );
451                        char error[128];
452
453                        if( vsnprintf( error, 128, format, params ) < 0 )
454                                sprintf( error, "internal error parsing error string (BUG)" );
455                        va_end( params );
456
457                        imcb_log( tf->ic, "Transferring file %s: connection to streamhost %s:%s failed (%s)", 
458                                  tf->ft->file_name, 
459                                  xt_find_attr( bhd->shnode, "host" ),
460                                  xt_find_attr( bhd->shnode, "port" ),
461                                  error );
462                }
463
464                /* Alright, this streamhost failed, let's try the next... */
465                bhd->phase = BS_PHASE_CONNECT;
466                bhd->shnode = bhd->shnode->next;
467               
468                /* the if is not neccessary but saves us one recursion */
469                if( bhd->shnode )
470                        return jabber_bs_handshake( bhd, 0, 0 );
471        }
472
473        /* out of stream hosts */
474
475        iqnode = jabber_make_packet( "iq", "result", tf->ini_jid, NULL );
476        reply = jabber_make_error_packet( iqnode, "item-not-found", "cancel" , "404" );
477        xt_free_node( iqnode );
478
479        xt_add_attr( reply, "id", tf->iq_id );
480               
481        if( !jabber_write_packet( tf->ic, reply ) )
482                imcb_log( tf->ic, "WARNING: Error transmitting bytestream response" );
483        xt_free_node( reply );
484
485        imcb_file_canceled( tf->ft, "couldn't connect to any streamhosts" );
486
487        bhd->tf->watch_in = 0;
488        return FALSE;
489}
490
491/*
492 * After the SOCKS5 handshake succeeds we need to inform the initiator which streamhost we chose.
493 * If he is the streamhost himself, he might already know that. However, if it's a proxy,
494 * the initiator will have to make a connection himself.
495 */
496void jabber_bs_answer_request( struct bs_handshake_data *bhd )
497{
498        struct jabber_transfer *tf = bhd->tf;
499        struct xt_node *reply;
500
501        imcb_log( tf->ic, "Transferring file %s: established SOCKS5 connection to %s:%s", 
502                  tf->ft->file_name, 
503                  xt_find_attr( bhd->shnode, "host" ),
504                  xt_find_attr( bhd->shnode, "port" ) );
505
506        tf->ft->data = tf;
507        tf->ft->started = time( NULL );
508        tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_read, tf );
509        tf->ft->out_of_data = jabber_bs_out_of_data;
510
511        reply = xt_new_node( "streamhost-used", NULL, NULL );
512        xt_add_attr( reply, "jid", xt_find_attr( bhd->shnode, "jid" ) );
513
514        reply = xt_new_node( "query", NULL, reply );
515        xt_add_attr( reply, "xmlns", XMLNS_BYTESTREAMS );
516
517        reply = jabber_make_packet( "iq", "result", tf->ini_jid, reply );
518
519        xt_add_attr( reply, "id", tf->iq_id );
520               
521        if( !jabber_write_packet( tf->ic, reply ) )
522                imcb_file_canceled( tf->ft, "Error transmitting bytestream response" );
523        xt_free_node( reply );
524}
525
526/* Reads till it is unscheduled or the receiver signifies an overflow. */
527gboolean jabber_bs_read( gpointer data, gint fd, b_input_condition cond )
528{
529        int ret;
530        struct jabber_transfer *tf = data;
531        char *buffer = g_malloc( JABBER_BS_BUFSIZE );
532
533        if (tf->receiver_overflow)
534        {
535                if( tf->watch_in )
536                {
537                        /* should never happen, BUG */
538                        imcb_file_canceled( tf->ft, "Bug in jabber file transfer code: read while overflow is true. Please report" );
539                        return FALSE;
540                }
541        }
542
543        ret = recv( fd, buffer, JABBER_BS_BUFSIZE, 0 );
544
545        if( ret == -1 )
546        {
547                /* shouldn't actually happen */
548                if( errno == EAGAIN )
549                        return TRUE;
550
551                imcb_file_canceled( tf->ft, "Error reading tcp socket" ); /* , strerror( errnum ) */
552
553                return FALSE;
554        }
555
556        /* that should be all */
557        if( ret == 0 )
558                return FALSE;
559       
560        tf->bytesread += ret;
561
562        buffer = g_realloc( buffer, ret );
563
564        if ( ( tf->receiver_overflow = imcb_file_write( tf->ft, buffer, ret ) ) )
565        {
566                /* wait for imcb to run out of data */
567                tf->watch_in = 0;
568                return FALSE;
569        }
570               
571
572        return TRUE;
573}
574
575/* imcb callback that is invoked when it runs out of data.
576 * We reschedule jabber_bs_read here if neccessary. */
577void jabber_bs_out_of_data( file_transfer_t *ft )
578{
579        struct jabber_transfer *tf = ft->data;
580
581        tf->receiver_overflow = FALSE;
582
583        if ( !tf->watch_in )
584                tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_read, tf );
585}
586
587/* Bad luck */
588void jabber_bs_canceled( file_transfer_t *ft , char *reason )
589{
590        struct jabber_transfer *tf = ft->data;
591
592        imcb_log( tf->ic, "File transfer aborted: %s", reason );
593}
Note: See TracBrowser for help on using the repository browser.