source: dcc.c @ 9b0ad7e

Last change on this file since 9b0ad7e was f1f7b5e, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-07-24T22:50:23Z

Take the local address from the IM/IRC connection when setting up a listening
socket for file transfers.

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