source: dcc.c @ 1c3008a

Last change on this file since 1c3008a was 1c3008a, checked in by Wilmer van der Gaast <wilmer@…>, at 2009-12-13T14:48:56Z

No functional changes, just some style "fixes". Although I admit I'm
somewhat growing out of my own coding style, I do try to keep things
consistent at least within files.

Comments are now in reviewboard:

http://code.bitlbee.org/rb/r/13/

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