source: dcc.c @ aac4017

Last change on this file since aac4017 was aac4017, checked in by ulim <a.sporto+bee@…>, at 2008-08-12T11:04:37Z

More hints for getaddrinfo().

Hopefully solves a problem on FreeBSD.

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