source: dcc.c @ 0cab388

Last change on this file since 0cab388 was 0cab388, checked in by ulim <a.sporto+bee@…>, at 2008-04-14T15:33:13Z

more verbose error reporting

  • Property mode set to 100644
File size: 17.4 KB
Line 
1/********************************************************************\
2* BitlBee -- An IRC to other IM-networks gateway                     *
3*                                                                    *
4* Copyright 2007 Uli Meis <a.sporto+bee@gmail.com>                   *
5\********************************************************************/
6
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 with
19  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
20  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
21  Suite 330, Boston, MA  02111-1307  USA
22*/
23
24#define BITLBEE_CORE
25#include "bitlbee.h"
26#include "ft.h"
27#include "dcc.h"
28#include <poll.h>
29#include <netinet/tcp.h>
30#include <regex.h>
31
32/*
33 * Since that might be confusing a note on naming:
34 *
35 * Generic dcc functions start with
36 *
37 *      dcc_
38 *
39 * ,methods specific to DCC SEND start with
40 *
41 *      dccs_
42 *
43 * . Since we can be on both ends of a DCC SEND,
44 * functions specific to one end are called
45 *
46 *      dccs_send and dccs_recv
47 *
48 * ,respectively.
49 */
50
51
52/*
53 * used to generate a unique local transfer id the user
54 * can use to reject/cancel transfers
55 */
56unsigned int local_transfer_id=1;
57
58/*
59 * just for debugging the nr. of chunks we received from im-protocols and the total data
60 */
61unsigned int receivedchunks=0, receiveddata=0;
62
63int max_packet_size = 0;
64
65static void dcc_finish( file_transfer_t *file );
66static void dcc_close( file_transfer_t *file );
67gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond );
68gboolean dcc_listen( dcc_file_transfer_t *df, struct sockaddr_storage **saddr_ptr );
69int dccs_send_request( struct dcc_file_transfer *df, char *user_nick, struct sockaddr_storage *saddr );
70gboolean dccs_recv_start( file_transfer_t *ft );
71gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond);
72gboolean dccs_recv_write_request( file_transfer_t *ft );
73
74/* As defined in ft.h */
75file_transfer_t *imcb_file_send_start( struct im_connection *ic, char *handle, char *file_name, size_t file_size )
76{
77        user_t *u = user_findhandle( ic, handle );
78        /* one could handle this more intelligent like imcb_buddy_msg.
79         * can't call it directly though cause it does some wrapping.
80         * Maybe give imcb_buddy_msg a parameter NO_WRAPPING? */
81        if (!u) return NULL;
82
83        return dccs_send_start( ic, u->nick, file_name, file_size );
84};
85
86/* As defined in ft.h */
87void imcb_file_canceled( file_transfer_t *file, char *reason )
88{
89        if( file->canceled )
90                file->canceled( file, reason );
91
92        dcc_close( file );
93}
94
95/* As defined in ft.h */
96gboolean imcb_file_recv_start( file_transfer_t *ft )
97{
98        return dccs_recv_start( ft );
99}
100
101dcc_file_transfer_t *dcc_alloc_transfer( char *file_name, size_t file_size, struct im_connection *ic )
102{
103        file_transfer_t *file = g_new0( file_transfer_t, 1 );
104        dcc_file_transfer_t *df = file->priv = g_new0( dcc_file_transfer_t, 1);
105        file->file_size = file_size;
106        file->file_name = g_strdup( file_name );
107        file->local_id = local_transfer_id++;
108        df->ic = ic;
109        df->ft = file;
110       
111        return df;
112}
113
114/* This is where the sending magic starts... */
115file_transfer_t *dccs_send_start( struct im_connection *ic, char *user_nick, char *file_name, size_t file_size )
116{
117        file_transfer_t *file;
118        dcc_file_transfer_t *df;
119        struct sockaddr_storage *saddr;
120
121        if( file_size > global.conf->max_filetransfer_size )
122                return NULL;
123       
124        df = dcc_alloc_transfer( file_name, file_size, ic );
125        file = df->ft;
126        file->write = dccs_send_write;
127
128        /* listen and request */
129        if( !dcc_listen( df, &saddr ) ||
130            !dccs_send_request( df, user_nick, saddr ) )
131                return NULL;
132
133        g_free( saddr );
134
135        /* watch */
136        df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_send_proto, df );
137
138        df->ic->irc->file_transfers = g_slist_prepend( df->ic->irc->file_transfers, file );
139
140        return file;
141}
142
143/* Used pretty much everywhere in the code to abort a transfer */
144gboolean dcc_abort( dcc_file_transfer_t *df, char *reason, ... )
145{
146        file_transfer_t *file = df->ft;
147        va_list params;
148        va_start( params, reason );
149        char *msg = g_strdup_vprintf( reason, params );
150        va_end( params );
151       
152        file->status |= FT_STATUS_CANCELED;
153       
154        if( file->canceled )
155                file->canceled( file, msg );
156        else 
157                imcb_log( df->ic, "DCC transfer aborted: %s", msg );
158
159        g_free( msg );
160
161        dcc_close( df->ft );
162
163        return FALSE;
164}
165
166/* used extensively for socket operations */
167#define ASSERTSOCKOP(op, msg) \
168        if( (op) == -1 ) \
169                return dcc_abort( df , msg ": %s", strerror( errno ) );
170
171/* Creates the "DCC SEND" line and sends it to the server */
172int dccs_send_request( struct dcc_file_transfer *df, char *user_nick, struct sockaddr_storage *saddr )
173{
174        char ipaddr[INET6_ADDRSTRLEN]; 
175        const void *netaddr;
176        int port;
177        char *cmd;
178
179        if( saddr->ss_family == AF_INET )
180        {
181                struct sockaddr_in *saddr_ipv4 = ( struct sockaddr_in *) saddr;
182
183                sprintf( ipaddr, "%d", 
184                         ntohl( saddr_ipv4->sin_addr.s_addr ) );
185                port = saddr_ipv4->sin_port;
186        } else 
187        {
188                struct sockaddr_in6 *saddr_ipv6 = ( struct sockaddr_in6 *) saddr;
189
190                netaddr = &saddr_ipv6->sin6_addr.s6_addr;
191                port = saddr_ipv6->sin6_port;
192
193                /*
194                 * Didn't find docs about this, but it seems that's the way irssi does it
195                 */
196                if( !inet_ntop( saddr->ss_family, netaddr, ipaddr, sizeof( ipaddr ) ) )
197                        return dcc_abort( df, "inet_ntop failed: %s", strerror( errno ) );
198        }
199
200        port = ntohs( port );
201
202        cmd = g_strdup_printf( "\001DCC SEND %s %s %u %zu\001",
203                                df->ft->file_name, ipaddr, port, df->ft->file_size );
204       
205        if ( !irc_msgfrom( df->ic->irc, user_nick, cmd ) )
206                return dcc_abort( df, "couldn't send 'DCC SEND' message to %s", user_nick );
207
208        g_free( cmd );
209
210        return TRUE;
211}
212
213/*
214 * Creates a listening socket and returns it in saddr_ptr.
215 */
216gboolean dcc_listen( dcc_file_transfer_t *df, struct sockaddr_storage **saddr_ptr )
217{
218        file_transfer_t *file = df->ft;
219        struct sockaddr_storage *saddr;
220        int fd,gret;
221        char hostname[ HOST_NAME_MAX + 1 ];
222        struct addrinfo hints, *rp;
223        socklen_t ssize = sizeof( struct sockaddr_storage );
224
225        /* won't be long till someone asks for this to be configurable :) */
226
227        ASSERTSOCKOP( gethostname( hostname, sizeof( hostname ) ), "gethostname()" );
228
229        memset( &hints, 0, sizeof( struct addrinfo ) );
230        hints.ai_socktype = SOCK_STREAM;
231        hints.ai_flags = AI_NUMERICSERV;
232
233        if ( ( gret = getaddrinfo( hostname, "0", &hints, &rp ) != 0 ) )
234                return dcc_abort( df, "getaddrinfo(): %s", gai_strerror( gret ) );
235
236        saddr = g_new( struct sockaddr_storage, 1 );
237
238        *saddr_ptr = saddr;
239
240        memcpy( saddr, rp->ai_addr, rp->ai_addrlen );
241
242        ASSERTSOCKOP( fd = df->fd = socket( saddr->ss_family, SOCK_STREAM, 0 ), "Opening socket" );
243
244        ASSERTSOCKOP( bind( fd, ( struct sockaddr *)saddr, rp->ai_addrlen ), "Binding socket" );
245       
246        freeaddrinfo( rp );
247
248        ASSERTSOCKOP( getsockname( fd, ( struct sockaddr *)saddr, &ssize ), "Getting socket name" );
249
250        ASSERTSOCKOP( listen( fd, 1 ), "Making socket listen" );
251
252        file->status = FT_STATUS_LISTENING;
253
254        return TRUE;
255}
256
257/*
258 * Checks poll(), same for receiving and sending
259 */
260gboolean dcc_poll( dcc_file_transfer_t *df, int fd, short *revents )
261{
262        struct pollfd pfd = { .fd = fd, .events = POLLHUP|POLLERR|POLLIN|POLLOUT };
263
264        ASSERTSOCKOP( poll( &pfd, 1, 0 ), "poll()" )
265
266        if( pfd.revents & POLLERR )
267        {
268                int sockerror;
269                socklen_t errlen = sizeof( sockerror );
270
271                if ( getsockopt( fd, SOL_SOCKET, SO_ERROR, &sockerror, &errlen ) )
272                        return dcc_abort( df, "getsockopt() failed, unknown socket error (weird!)" );
273
274                return dcc_abort( df, "Socket error: %s", strerror( sockerror ) );
275        }
276       
277        if( pfd.revents & POLLHUP ) 
278                return dcc_abort( df, "Remote end closed connection" );
279       
280        *revents = pfd.revents;
281
282        return TRUE;
283}
284
285/*
286 * fills max_packet_size with twice the TCP maximum segment size
287 */
288gboolean  dcc_check_maxseg( dcc_file_transfer_t *df, int fd )
289{
290        /*
291         * use twice the maximum segment size as a maximum for calls to send().
292         */
293        if( max_packet_size == 0 )
294        {
295                unsigned int mpslen = sizeof( max_packet_size );
296                if( getsockopt( fd, IPPROTO_TCP, TCP_MAXSEG, &max_packet_size, &mpslen ) )
297                        return dcc_abort( df, "getsockopt() failed" );
298                max_packet_size *= 2;
299        }
300        return TRUE;
301}
302
303/*
304 * After setup, the transfer itself is handled entirely by this function.
305 * There are basically four things to handle: connect, receive, send, and error.
306 */
307gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond )
308{
309        dcc_file_transfer_t *df = data;
310        file_transfer_t *file = df->ft;
311        short revents;
312       
313        if( !dcc_poll( df, fd, &revents) )
314                return FALSE;
315
316        if( ( revents & POLLIN ) &&
317            ( file->status & FT_STATUS_LISTENING ) )
318        {       
319                struct sockaddr *clt_addr;
320                socklen_t ssize = sizeof( clt_addr );
321
322                /* Connect */
323
324                ASSERTSOCKOP( df->fd = accept( fd, (struct sockaddr *) &clt_addr, &ssize ), "Accepting connection" );
325
326                closesocket( fd );
327                fd = df->fd;
328                file->status = FT_STATUS_TRANSFERRING;
329                sock_make_nonblocking( fd );
330
331                if ( !dcc_check_maxseg( df, fd ) )
332                        return FALSE;
333
334                /* IM protocol callback */
335                if( file->accept )
336                        file->accept( file );
337
338                /* reschedule for reading on new fd */
339                df->watch_in = b_input_add( fd, GAIM_INPUT_READ, dccs_send_proto, df );
340
341                return FALSE;
342        }
343
344        if( revents & POLLIN ) 
345        {
346                int bytes_received;
347                int ret;
348               
349                ASSERTSOCKOP( ret = recv( fd, &bytes_received, sizeof( bytes_received  ), MSG_PEEK ), "Receiving" );
350
351                if( ret == 0 )
352                        return dcc_abort( df, "Remote end closed connection" );
353                       
354                if( ret < 4 )
355                {
356                        imcb_log( df->ic, "WARNING: DCC SEND: receiver sent only 2 bytes instead of 4, shouldn't happen too often!" );
357                        return TRUE;
358                }
359
360                ASSERTSOCKOP( ret = recv( fd, &bytes_received, sizeof( bytes_received  ), 0 ), "Receiving" );
361                if( ret != 4 )
362                        return dcc_abort( df, "MSG_PEEK'ed 4, but can only dequeue %d bytes", ret );
363
364                bytes_received = ntohl( bytes_received );
365
366                /* If any of this is actually happening, the receiver should buy a new IRC client */
367
368                if ( bytes_received > df->bytes_sent )
369                        return dcc_abort( df, "Receiver magically received more bytes than sent ( %d > %d ) (BUG at receiver?)", bytes_received, df->bytes_sent );
370
371                if ( bytes_received < file->bytes_transferred )
372                        return dcc_abort( df, "Receiver lost bytes? ( has %d, had %d ) (BUG at receiver?)", bytes_received, file->bytes_transferred );
373               
374                file->bytes_transferred = bytes_received;
375       
376                if( file->bytes_transferred >= file->file_size ) {
377                        dcc_finish( file );
378                        return FALSE;
379                }
380       
381                return TRUE;
382        }
383
384        return TRUE;
385}
386
387gboolean dccs_recv_start( file_transfer_t *ft )
388{
389        dcc_file_transfer_t *df = ft->priv;
390        struct sockaddr_storage *saddr = &df->saddr;
391        int fd;
392        char ipaddr[INET6_ADDRSTRLEN]; 
393        socklen_t sa_len = saddr->ss_family == AF_INET ? 
394                sizeof( struct sockaddr_in ) : sizeof( struct sockaddr_in6 );
395       
396        if( !ft->write )
397                return dcc_abort( df, "BUG: protocol didn't register write()" );
398       
399        ASSERTSOCKOP( fd = df->fd = socket( saddr->ss_family, SOCK_STREAM, 0 ) , "Opening Socket" );
400
401        sock_make_nonblocking( fd );
402
403        if( ( connect( fd, (struct sockaddr *)saddr, sa_len ) == -1 ) &&
404            ( errno != EINPROGRESS ) )
405                return dcc_abort( df, "Connecting to %s:%d : %s", 
406                        inet_ntop( saddr->ss_family, 
407                                saddr->ss_family == AF_INET ? 
408                                    ( void* ) &( ( struct sockaddr_in *) saddr )->sin_addr.s_addr :
409                                    ( void* ) &( ( struct sockaddr_in6 *) saddr )->sin6_addr.s6_addr,
410                                ipaddr, 
411                                sizeof( ipaddr ) ),
412                        ntohs( saddr->ss_family == AF_INET ?
413                            ( ( struct sockaddr_in *) saddr )->sin_port :
414                            ( ( struct sockaddr_in6 *) saddr )->sin6_port ),
415                        strerror( errno ) );
416
417        ft->status = FT_STATUS_CONNECTING;
418
419        /* watch */
420        df->watch_out = b_input_add( df->fd, GAIM_INPUT_WRITE, dccs_recv_proto, df );
421        ft->write_request = dccs_recv_write_request;
422
423        return TRUE;
424}
425
426gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond)
427{
428        dcc_file_transfer_t *df = data;
429        file_transfer_t *ft = df->ft;
430        short revents;
431
432        if( !dcc_poll( df, fd, &revents ) )
433                return FALSE;
434       
435        if( ( revents & POLLOUT ) &&
436            ( ft->status & FT_STATUS_CONNECTING ) )
437        {
438                ft->status = FT_STATUS_TRANSFERRING;
439                if ( !dcc_check_maxseg( df, fd ) )
440                        return FALSE;
441
442                //df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_recv_proto, df );
443
444                df->watch_out = 0;
445                return FALSE;
446        }
447
448        if( revents & POLLIN )
449        {
450                int ret, done;
451
452                ASSERTSOCKOP( ret = recv( fd, ft->buffer, sizeof( ft->buffer ), 0 ), "Receiving" );
453
454                if( ret == 0 )
455                        return dcc_abort( df, "Remote end closed connection" );
456
457                df->bytes_sent += ret;
458
459                done = df->bytes_sent >= ft->file_size;
460
461                if( ( ( df->bytes_sent - ft->bytes_transferred ) > DCC_PACKET_SIZE ) ||
462                    done )
463                {
464                        int ack, ackret;
465                        ack = htonl( ft->bytes_transferred = df->bytes_sent );
466
467                        ASSERTSOCKOP( ackret = send( fd, &ack, 4, 0 ), "Sending DCC ACK" );
468                       
469                        if ( ackret != 4 )
470                                return dcc_abort( df, "Error sending DCC ACK, sent %d instead of 4 bytes", ackret );
471                }
472               
473                if( !ft->write( df->ft, ft->buffer, ret ) )
474                        return FALSE;
475
476                if( done )
477                {
478                        closesocket( fd );
479                        dcc_finish( ft );
480
481                        df->watch_in = 0;
482                        return FALSE;
483                }
484
485                df->watch_in = 0;
486                return FALSE;
487        }
488
489        return TRUE;
490}
491
492gboolean dccs_recv_write_request( file_transfer_t *ft )
493{
494        dcc_file_transfer_t *df = ft->priv;
495
496        if( df->watch_in )
497                return dcc_abort( df, "BUG: write_request() called while watching" );
498
499        df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_recv_proto, df );
500
501        return TRUE;
502}
503
504gboolean dccs_send_can_write( gpointer data, gint fd, b_input_condition cond )
505{
506        struct dcc_file_transfer *df = data;
507        df->watch_out = 0;
508
509        df->ft->write_request( df->ft );
510        return FALSE;
511}
512
513/*
514 * Incoming data.
515 *
516 */
517gboolean dccs_send_write( file_transfer_t *file, char *data, unsigned int data_len )
518{
519        dcc_file_transfer_t *df = file->priv;
520        int ret;
521
522        receivedchunks++; receiveddata += data_len;
523
524        if( df->watch_out )
525                return dcc_abort( df, "BUG: write() called while watching" );
526
527        ASSERTSOCKOP( ret = send( df->fd, data, data_len, 0 ), "Sending data" );
528
529        if( ret == 0 )
530                return dcc_abort( df, "Remote end closed connection" );
531
532        /* TODO: this should really not be fatal */
533        if( ret < data_len )
534                return dcc_abort( df, "send() sent %d instead of %d", ret, data_len );
535
536        df->bytes_sent += ret;
537
538        if( df->bytes_sent < df->ft->file_size )
539                df->watch_out = b_input_add( df->fd, GAIM_INPUT_WRITE, dccs_send_can_write, df );
540
541        return TRUE;
542}
543
544/*
545 * Cleans up after a transfer.
546 */
547static void dcc_close( file_transfer_t *file )
548{
549        dcc_file_transfer_t *df = file->priv;
550
551        if( file->free )
552                file->free( file );
553       
554        closesocket( df->fd );
555
556        if( df->watch_in )
557                b_event_remove( df->watch_in );
558
559        if( df->watch_out )
560                b_event_remove( df->watch_out );
561       
562        df->ic->irc->file_transfers = g_slist_remove( df->ic->irc->file_transfers, file );
563       
564        g_free( df );
565        g_free( file->file_name );
566        g_free( file );
567}
568
569void dcc_finish( file_transfer_t *file )
570{
571        file->status |= FT_STATUS_FINISHED;
572       
573        if( file->finished )
574                file->finished( file );
575
576        dcc_close( file );
577}
578
579/*
580 * DCC SEND <filename> <IP> <port> <filesize>
581 *
582 * filename can be in "" or not. If it is, " can probably be escaped...
583 * IP can be an unsigned int (IPV4) or something else (IPV6)
584 *
585 */
586file_transfer_t *dcc_request( struct im_connection *ic, char *line )
587{
588        char *pattern = "SEND"
589                " (([^\"][^ ]*)|\"([^\"]|\\\")*\")"
590                " (([0-9]*)|([^ ]*))"
591                " ([0-9]*)"
592                " ([0-9]*)\001";
593        regmatch_t pmatch[9];
594        regex_t re;
595        file_transfer_t *ft;
596        dcc_file_transfer_t *df;
597        char errbuf[256];
598        int regerrcode, gret;
599
600        if( ( regerrcode = regcomp( &re, pattern, REG_EXTENDED ) ) ||
601            ( regerrcode = regexec( &re, line, 9, pmatch, 0 ) ) ) {
602                regerror( regerrcode,&re,errbuf,sizeof( errbuf ) );
603                imcb_log( ic, 
604                          "DCC: error parsing 'DCC SEND': %s, line: %s", 
605                          errbuf, line );
606                return NULL;
607        }
608
609        if( ( pmatch[1].rm_so > 0 ) &&
610            ( pmatch[4].rm_so > 0 ) &&
611            ( pmatch[7].rm_so > 0 ) &&
612            ( pmatch[8].rm_so > 0 ) )
613        {
614                char *input = g_strdup( line );
615                char *filename, *host, *port;
616                size_t filesize;
617                struct addrinfo hints, *rp;
618
619                /* "filename" or filename */
620                if ( pmatch[2].rm_so > 0 )
621                {
622                        input[pmatch[2].rm_eo] = '\0';
623                        filename = input + pmatch[2].rm_so;
624                } else
625                {
626                        input[pmatch[3].rm_eo] = '\0';
627                        filename = input + pmatch[3].rm_so;
628                }
629                       
630                input[pmatch[4].rm_eo] = '\0';
631
632                /* number means ipv4, something else means ipv6 */
633                if ( pmatch[5].rm_so > 0 )
634                {
635                        struct in_addr ipaddr = { .s_addr = htonl( atoi( input + pmatch[5].rm_so ) ) };
636                        host = inet_ntoa( ipaddr );
637                } else
638                {
639                        /* Contains non-numbers, hopefully an IPV6 address */
640                        host = input + pmatch[6].rm_so;
641                }
642
643                input[pmatch[7].rm_eo] = '\0';
644                input[pmatch[8].rm_eo] = '\0';
645
646                port = input + pmatch[7].rm_so;
647                filesize = atoll( input + pmatch[8].rm_so );
648
649                memset( &hints, 0, sizeof ( struct addrinfo ) );
650                if ( ( gret = getaddrinfo( host, port, &hints, &rp ) ) )
651                {
652                        g_free( input );
653                        imcb_log( ic, "DCC: getaddrinfo() failed with %s "
654                                  "when parsing incoming 'DCC SEND': "
655                                  "host %s, port %s", 
656                                  gai_strerror( gret ), host, port );
657                        return NULL;
658                }
659
660                df = dcc_alloc_transfer( filename, filesize, ic );
661                ft = df->ft;
662                ft->sending = TRUE;
663                memcpy( &df->saddr, rp->ai_addr, rp->ai_addrlen );
664
665                freeaddrinfo( rp );
666                g_free( input );
667
668                df->ic->irc->file_transfers = g_slist_prepend( df->ic->irc->file_transfers, ft );
669
670                return ft;
671        }
672
673        imcb_log( ic, "DCC: couldnt parse 'DCC SEND' line: %s", line );
674
675        return NULL;
676}
677
Note: See TracBrowser for help on using the repository browser.