source: dcc.c @ 29c1456

Last change on this file since 29c1456 was 4358b10, checked in by ulim <a.sporto+bee@…>, at 2008-05-04T13:32:15Z

ulibc support, fixes "Invalid SOCKS5 Connect message" problem

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