source: dcc.c @ 8240840

Last change on this file since 8240840 was 17a6ee9, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-04-11T14:37:06Z

Including DCC stuff again, with a wonderful extra layer of abstraction.
Some hooks are missing so sending files doesn't work yet. Receiving also
still seems to have some issues. On the plus side, at least the MSN/Jabber
modules work again.

  • Property mode set to 100644
File size: 15.6 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++;
80        df->ic = ic;
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
[1c3008a]106        if( ( df->fd = ft_listen( &saddr, host, port, TRUE, &errmsg ) ) == -1 )
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 */
118        df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_send_proto, df );
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, "
126                      "issue the 'transfers 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       
[2e89256]230        if( ( cond & GAIM_INPUT_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 */
250                df->watch_in = b_input_add( fd, GAIM_INPUT_READ, dccs_send_proto, df );
251
252                return FALSE;
253        }
254
[2e89256]255        if( cond & GAIM_INPUT_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 */
327        df->watch_out = b_input_add( df->fd, GAIM_INPUT_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
[2e89256]340        if( ( cond & GAIM_INPUT_WRITE ) &&
[2ff2076]341            ( ft->status & FT_STATUS_CONNECTING ) )
342        {
343                ft->status = FT_STATUS_TRANSFERRING;
344
345                //df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_recv_proto, df );
346
347                df->watch_out = 0;
348                return FALSE;
349        }
350
[2e89256]351        if( cond & GAIM_INPUT_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
408        df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_recv_proto, df );
409
410        return TRUE;
411}
412
413gboolean dccs_send_can_write( gpointer data, gint fd, b_input_condition cond )
414{
415        struct dcc_file_transfer *df = data;
416        df->watch_out = 0;
417
418        df->ft->write_request( df->ft );
419        return FALSE;
[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 )
451                df->watch_out = b_input_add( df->fd, GAIM_INPUT_WRITE, dccs_send_can_write, df );
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 */
506file_transfer_t *dcc_request( struct im_connection *ic, char *line )
507{
[17a6ee9]508        irc_t *irc = (irc_t *) ic->bee->ui_data;
[2ff2076]509        char *pattern = "SEND"
[9afeefa]510                " (([^\"][^ ]*)|\"(([^\"]|\\\")*)\")"
[2ff2076]511                " (([0-9]*)|([^ ]*))"
512                " ([0-9]*)"
513                " ([0-9]*)\001";
[9afeefa]514        regmatch_t pmatch[10];
[2ff2076]515        regex_t re;
516        file_transfer_t *ft;
517        dcc_file_transfer_t *df;
[6cac643]518        char errbuf[256];
519        int regerrcode, gret;
520
521        if( ( regerrcode = regcomp( &re, pattern, REG_EXTENDED ) ) ||
[9afeefa]522            ( regerrcode = regexec( &re, line, 10, pmatch, 0 ) ) ) {
[6cac643]523                regerror( regerrcode,&re,errbuf,sizeof( errbuf ) );
524                imcb_log( ic, 
525                          "DCC: error parsing 'DCC SEND': %s, line: %s", 
526                          errbuf, line );
[2ff2076]527                return NULL;
[6cac643]528        }
[2ff2076]529
530        if( ( pmatch[1].rm_so > 0 ) &&
[9afeefa]531            ( pmatch[5].rm_so > 0 ) &&
532            ( pmatch[8].rm_so > 0 ) &&
533            ( pmatch[9].rm_so > 0 ) )
[2ff2076]534        {
535                char *input = g_strdup( line );
536                char *filename, *host, *port;
537                size_t filesize;
538                struct addrinfo hints, *rp;
539
540                /* "filename" or filename */
541                if ( pmatch[2].rm_so > 0 )
542                {
543                        input[pmatch[2].rm_eo] = '\0';
544                        filename = input + pmatch[2].rm_so;
545                } else
546                {
547                        input[pmatch[3].rm_eo] = '\0';
548                        filename = input + pmatch[3].rm_so;
549                }
550                       
[9afeefa]551                input[pmatch[5].rm_eo] = '\0';
[2ff2076]552
553                /* number means ipv4, something else means ipv6 */
[9afeefa]554                if ( pmatch[6].rm_so > 0 )
[2ff2076]555                {
[92f4ec5]556                        struct in_addr ipaddr = { .s_addr = htonl( strtoul( input + pmatch[5].rm_so, NULL, 10 ) ) };
[2ff2076]557                        host = inet_ntoa( ipaddr );
558                } else
559                {
560                        /* Contains non-numbers, hopefully an IPV6 address */
[9afeefa]561                        host = input + pmatch[7].rm_so;
[2ff2076]562                }
563
564                input[pmatch[8].rm_eo] = '\0';
[9afeefa]565                input[pmatch[9].rm_eo] = '\0';
[2ff2076]566
[9afeefa]567                port = input + pmatch[8].rm_so;
568                filesize = atoll( input + pmatch[9].rm_so );
[2ff2076]569
570                memset( &hints, 0, sizeof ( struct addrinfo ) );
[aac4017]571                hints.ai_socktype = SOCK_STREAM;
572                hints.ai_flags = AI_NUMERICSERV;
573
[6cac643]574                if ( ( gret = getaddrinfo( host, port, &hints, &rp ) ) )
[2ff2076]575                {
576                        g_free( input );
[6cac643]577                        imcb_log( ic, "DCC: getaddrinfo() failed with %s "
578                                  "when parsing incoming 'DCC SEND': "
579                                  "host %s, port %s", 
580                                  gai_strerror( gret ), host, port );
[2ff2076]581                        return NULL;
582                }
583
584                df = dcc_alloc_transfer( filename, filesize, ic );
585                ft = df->ft;
586                ft->sending = TRUE;
587                memcpy( &df->saddr, rp->ai_addr, rp->ai_addrlen );
588
589                freeaddrinfo( rp );
590                g_free( input );
591
[17a6ee9]592                irc->file_transfers = g_slist_prepend( irc->file_transfers, ft );
[2ff2076]593
594                return ft;
595        }
596
[6cac643]597        imcb_log( ic, "DCC: couldnt parse 'DCC SEND' line: %s", line );
598
[2ff2076]599        return NULL;
600}
601
Note: See TracBrowser for help on using the repository browser.