source: dcc.c @ 2e89256

Last change on this file since 2e89256 was 2e89256, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-03-21T13:20:20Z

Remove dcc_poll() and just use the cond variable passed to I/O events.

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