source: dcc.c @ 2370ec2

Last change on this file since 2370ec2 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
RevLine 
[2c2df7d]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>
[2ff2076]29#include <regex.h>
[a02f34f]30#include "lib/ftutil.h"
[4358b10]31
[2c2df7d]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
[17a6ee9]63void dcc_finish( file_transfer_t *file );
64void dcc_close( file_transfer_t *file );
[2c2df7d]65gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond );
[17a6ee9]66int dccs_send_request( struct dcc_file_transfer *df, irc_user_t *iu, struct sockaddr_storage *saddr );
[2ff2076]67gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond);
[dce3903]68gboolean dccs_recv_write_request( file_transfer_t *ft );
[d56ee38]69gboolean dcc_progress( gpointer data, gint fd, b_input_condition cond );
[a02f34f]70gboolean dcc_abort( dcc_file_transfer_t *df, char *reason, ... );
[2c2df7d]71
[17a6ee9]72dcc_file_transfer_t *dcc_alloc_transfer( const char *file_name, size_t file_size, struct im_connection *ic )
[2ff2076]73{
74        file_transfer_t *file = g_new0( file_transfer_t, 1 );
[1c3008a]75        dcc_file_transfer_t *df = file->priv = g_new0( dcc_file_transfer_t, 1 );
76       
[2ff2076]77        file->file_size = file_size;
78        file->file_name = g_strdup( file_name );
79        file->local_id = local_transfer_id++;
[9d4352c]80        file->ic = df->ic = ic;
[2ff2076]81        df->ft = file;
82       
83        return df;
84}
85
[2c2df7d]86/* This is where the sending magic starts... */
[17a6ee9]87file_transfer_t *dccs_send_start( struct im_connection *ic, irc_user_t *iu, const char *file_name, size_t file_size )
[2c2df7d]88{
89        file_transfer_t *file;
90        dcc_file_transfer_t *df;
[17a6ee9]91        irc_t *irc = (irc_t *) ic->bee->ui_data;
[a02f34f]92        struct sockaddr_storage saddr;
93        char *errmsg;
[60e4df3]94        char host[HOST_NAME_MAX];
[a02f34f]95        char port[6];
[2c2df7d]96
[a02f34f]97        if( file_size > global.conf->ft_max_size )
[2c2df7d]98                return NULL;
99       
[2ff2076]100        df = dcc_alloc_transfer( file_name, file_size, ic );
101        file = df->ft;
[dce3903]102        file->write = dccs_send_write;
[2ff2076]103
[2c2df7d]104        /* listen and request */
[a02f34f]105
[f1f7b5e]106        if( ( df->fd = ft_listen( &saddr, host, port, irc->fd, TRUE, &errmsg ) ) == -1 )
[1c3008a]107        {
[a02f34f]108                dcc_abort( df, "Failed to listen locally, check your ft_listen setting in bitlbee.conf: %s", errmsg );
[2c2df7d]109                return NULL;
[a02f34f]110        }
[2c2df7d]111
[a02f34f]112        file->status = FT_STATUS_LISTENING;
113
[17a6ee9]114        if( !dccs_send_request( df, iu, &saddr ) )
[a02f34f]115                return NULL;
[2c2df7d]116
117        /* watch */
[0cb71a6]118        df->watch_in = b_input_add( df->fd, B_EV_IO_READ, dccs_send_proto, df );
[2c2df7d]119
[17a6ee9]120        irc->file_transfers = g_slist_prepend( irc->file_transfers, file );
[2c2df7d]121
[d56ee38]122        df->progress_timeout = b_timeout_add( DCC_MAX_STALL * 1000, dcc_progress, df );
123
[1c3008a]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, "
[2bfe976]126                      "issue the 'transfer reject' command.",
[17a6ee9]127                      iu->nick, file_name, file_size / 1024 );
[4ac647d]128
[2c2df7d]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 );
[d56ee38]145
146        imcb_log( df->ic, "File %s: DCC transfer aborted: %s", file->file_name, msg );
[2c2df7d]147
148        g_free( msg );
149
150        dcc_close( df->ft );
151
152        return FALSE;
153}
154
[d56ee38]155gboolean dcc_progress( gpointer data, gint fd, b_input_condition cond )
156{
157        struct dcc_file_transfer *df = data;
158
[b043ad5]159        if( df->bytes_sent == df->progress_bytes_last )
[d56ee38]160        {
161                /* no progress. cancel */
162                if( df->bytes_sent == 0 )
[1c3008a]163                        return dcc_abort( df, "Couldn't establish transfer within %d seconds", DCC_MAX_STALL );
[d56ee38]164                else 
[b043ad5]165                        return dcc_abort( df, "Transfer stalled for %d seconds at %d kb", DCC_MAX_STALL, df->bytes_sent / 1024 );
[d56ee38]166
167        }
168
[b043ad5]169        df->progress_bytes_last = df->bytes_sent;
[d56ee38]170
171        return TRUE;
172}
173
[2c2df7d]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 */
[17a6ee9]180int dccs_send_request( struct dcc_file_transfer *df, irc_user_t *iu, struct sockaddr_storage *saddr )
[2c2df7d]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", 
[dce3903]192                         ntohl( saddr_ipv4->sin_addr.s_addr ) );
[2c2df7d]193                port = saddr_ipv4->sin_port;
[1c3008a]194        }
195        else 
[2c2df7d]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       
[17a6ee9]214        irc_send_msg_raw( iu, "PRIVMSG", iu->irc->user->nick, cmd );
[2c2df7d]215
216        g_free( cmd );
217
218        return TRUE;
219}
220
[2ff2076]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       
[0cb71a6]230        if( ( cond & B_EV_IO_READ ) &&
[2c2df7d]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;
[2ff2076]242                file->status = FT_STATUS_TRANSFERRING;
[2c2df7d]243                sock_make_nonblocking( fd );
244
245                /* IM protocol callback */
246                if( file->accept )
247                        file->accept( file );
[dce3903]248
[2c2df7d]249                /* reschedule for reading on new fd */
[0cb71a6]250                df->watch_in = b_input_add( fd, B_EV_IO_READ, dccs_send_proto, df );
[2c2df7d]251
252                return FALSE;
253        }
254
[0cb71a6]255        if( cond & B_EV_IO_READ ) 
[2c2df7d]256        {
257                int ret;
258               
[4ed9c8c]259                ASSERTSOCKOP( ret = recv( fd, ( (char*) &df->acked ) + df->acked_len,
260                        sizeof( df->acked ) - df->acked_len, 0 ), "Receiving" );
[2c2df7d]261
262                if( ret == 0 )
263                        return dcc_abort( df, "Remote end closed connection" );
[4ed9c8c]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 )
[2c2df7d]268                        return TRUE;
269
[4ed9c8c]270                df->acked = ntohl( df->acked );
[2c2df7d]271
272                /* If any of this is actually happening, the receiver should buy a new IRC client */
273
[4ed9c8c]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 );
[2c2df7d]276
[4ed9c8c]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 );
[2c2df7d]279               
[4ed9c8c]280                file->bytes_transferred = df->acked;
[2c2df7d]281       
282                if( file->bytes_transferred >= file->file_size ) {
[4ac647d]283                        if( df->proto_finished )
284                                dcc_finish( file );
[2c2df7d]285                        return FALSE;
286                }
287       
288                return TRUE;
289        }
290
291        return TRUE;
292}
293
[2ff2076]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;
[0cab388]299        char ipaddr[INET6_ADDRSTRLEN]; 
[2ff2076]300        socklen_t sa_len = saddr->ss_family == AF_INET ? 
301                sizeof( struct sockaddr_in ) : sizeof( struct sockaddr_in6 );
302       
303        if( !ft->write )
[0cab388]304                return dcc_abort( df, "BUG: protocol didn't register write()" );
[2ff2076]305       
[1c3008a]306        ASSERTSOCKOP( fd = df->fd = socket( saddr->ss_family, SOCK_STREAM, 0 ), "Opening Socket" );
[2ff2076]307
308        sock_make_nonblocking( fd );
309
310        if( ( connect( fd, (struct sockaddr *)saddr, sa_len ) == -1 ) &&
311            ( errno != EINPROGRESS ) )
[0cab388]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 ) );
[2ff2076]323
324        ft->status = FT_STATUS_CONNECTING;
325
326        /* watch */
[0cb71a6]327        df->watch_out = b_input_add( df->fd, B_EV_IO_WRITE, dccs_recv_proto, df );
[dce3903]328        ft->write_request = dccs_recv_write_request;
[2ff2076]329
[d56ee38]330        df->progress_timeout = b_timeout_add( DCC_MAX_STALL * 1000, dcc_progress, df );
331
[2ff2076]332        return TRUE;
333}
334
[1c3008a]335gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond )
[2ff2076]336{
337        dcc_file_transfer_t *df = data;
338        file_transfer_t *ft = df->ft;
339
[0cb71a6]340        if( ( cond & B_EV_IO_WRITE ) &&
[2ff2076]341            ( ft->status & FT_STATUS_CONNECTING ) )
342        {
343                ft->status = FT_STATUS_TRANSFERRING;
344
[0cb71a6]345                //df->watch_in = b_input_add( df->fd, B_EV_IO_READ, dccs_recv_proto, df );
[2ff2076]346
347                df->watch_out = 0;
348                return FALSE;
349        }
350
[0cb71a6]351        if( cond & B_EV_IO_READ )
[2ff2076]352        {
353                int ret, done;
354
[dce3903]355                ASSERTSOCKOP( ret = recv( fd, ft->buffer, sizeof( ft->buffer ), 0 ), "Receiving" );
[2ff2076]356
357                if( ret == 0 )
358                        return dcc_abort( df, "Remote end closed connection" );
359
[4ac647d]360                if( !ft->write( df->ft, ft->buffer, ret ) )
361                        return FALSE;
362
[2ff2076]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                {
[4ed9c8c]370                        guint32 ack = htonl( ft->bytes_transferred = df->bytes_sent );
371                        int ackret;
[2ff2076]372
373                        ASSERTSOCKOP( ackret = send( fd, &ack, 4, 0 ), "Sending DCC ACK" );
374                       
375                        if ( ackret != 4 )
[dce3903]376                                return dcc_abort( df, "Error sending DCC ACK, sent %d instead of 4 bytes", ackret );
[2ff2076]377                }
378               
[4ac647d]379                if( df->bytes_sent == ret )
380                        ft->started = time( NULL );
[2ff2076]381
382                if( done )
383                {
[4ac647d]384                        if( df->watch_out )
385                                b_event_remove( df->watch_out );
386
387                        if( df->proto_finished )
388                                dcc_finish( ft );
[2ff2076]389
390                        df->watch_in = 0;
391                        return FALSE;
392                }
393
[dce3903]394                df->watch_in = 0;
395                return FALSE;
[2ff2076]396        }
397
398        return TRUE;
399}
400
[dce3903]401gboolean dccs_recv_write_request( file_transfer_t *ft )
[2ff2076]402{
403        dcc_file_transfer_t *df = ft->priv;
404
[dce3903]405        if( df->watch_in )
406                return dcc_abort( df, "BUG: write_request() called while watching" );
407
[0cb71a6]408        df->watch_in = b_input_add( df->fd, B_EV_IO_READ, dccs_recv_proto, df );
[dce3903]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;
[2ff2076]420}
421
[2c2df7d]422/*
[dce3903]423 * Incoming data.
[2c2df7d]424 *
[dce3903]425 */
426gboolean dccs_send_write( file_transfer_t *file, char *data, unsigned int data_len )
[2c2df7d]427{
428        dcc_file_transfer_t *df = file->priv;
[dce3903]429        int ret;
430
431        receivedchunks++; receiveddata += data_len;
[2c2df7d]432
[dce3903]433        if( df->watch_out )
434                return dcc_abort( df, "BUG: write() called while watching" );
[2c2df7d]435
[dce3903]436        ASSERTSOCKOP( ret = send( df->fd, data, data_len, 0 ), "Sending data" );
[2c2df7d]437
[dce3903]438        if( ret == 0 )
439                return dcc_abort( df, "Remote end closed connection" );
[2c2df7d]440
[dce3903]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 );
[2c2df7d]444
[4ac647d]445        if( df->bytes_sent == 0 )
446                file->started = time( NULL );
447
[dce3903]448        df->bytes_sent += ret;
449
450        if( df->bytes_sent < df->ft->file_size )
[0cb71a6]451                df->watch_out = b_input_add( df->fd, B_EV_IO_WRITE, dccs_send_can_write, df );
[dce3903]452
453        return TRUE;
[2c2df7d]454}
455
456/*
457 * Cleans up after a transfer.
458 */
[17a6ee9]459void dcc_close( file_transfer_t *file )
[2c2df7d]460{
461        dcc_file_transfer_t *df = file->priv;
[17a6ee9]462        irc_t *irc = (irc_t *) df->ic->bee->ui_data;
[2c2df7d]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       
[d56ee38]475        if( df->progress_timeout )
476                b_event_remove( df->progress_timeout );
477       
[17a6ee9]478        irc->file_transfers = g_slist_remove( irc->file_transfers, file );
[2c2df7d]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{
[4ac647d]487        dcc_file_transfer_t *df = file->priv;
488        time_t diff = time( NULL ) - file->started ? : 1;
489
[2c2df7d]490        file->status |= FT_STATUS_FINISHED;
491       
492        if( file->finished )
493                file->finished( file );
494
[4ac647d]495        imcb_log( df->ic, "File %s transferred successfully at %d kb/s!" , file->file_name, (int) ( file->bytes_transferred / 1024 / diff ) );
[2c2df7d]496        dcc_close( file );
497}
[2ff2076]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 */
[89c11e7]506file_transfer_t *dcc_request( struct im_connection *ic, char* const* ctcp )
[2ff2076]507{
[17a6ee9]508        irc_t *irc = (irc_t *) ic->bee->ui_data;
[2ff2076]509        file_transfer_t *ft;
510        dcc_file_transfer_t *df;
[89c11e7]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 )
[2ff2076]517        {
518                char *filename, *host, *port;
519                struct addrinfo hints, *rp;
[89c11e7]520               
521                filename = ctcp[2];
522               
523                host = ctcp[3];
524                while( *host && isdigit( *host ) ) host++; /* Just digits? */
525                if( *host == '\0' )
[2ff2076]526                {
[89c11e7]527                        struct in_addr ipaddr = { .s_addr = htonl( atoll( ctcp[3] ) ) };
[2ff2076]528                        host = inet_ntoa( ipaddr );
529                } else
530                {
531                        /* Contains non-numbers, hopefully an IPV6 address */
[89c11e7]532                        host = ctcp[3];
[2ff2076]533                }
534
[89c11e7]535                port = ctcp[4];
536                filesize = atoll( ctcp[5] );
[2ff2076]537
538                memset( &hints, 0, sizeof ( struct addrinfo ) );
[aac4017]539                hints.ai_socktype = SOCK_STREAM;
540                hints.ai_flags = AI_NUMERICSERV;
541
[6cac643]542                if ( ( gret = getaddrinfo( host, port, &hints, &rp ) ) )
[2ff2076]543                {
[6cac643]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 );
[2ff2076]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
[17a6ee9]558                irc->file_transfers = g_slist_prepend( irc->file_transfers, ft );
[2ff2076]559
560                return ft;
561        }
[89c11e7]562        else
563                imcb_log( ic, "DCC: couldnt parse `DCC SEND' line" );
[6cac643]564
[2ff2076]565        return NULL;
566}
Note: See TracBrowser for help on using the repository browser.