source: dcc.c @ 5d9257cf

Last change on this file since 5d9257cf was 9767d03, checked in by dequis <dx@…>, at 2018-07-31T04:41:55Z

Modify server-time implementation to not change API

The new functions with the different paramters are flagged as internal,
so it doesn't even add new symbols.

abi-compliance checker says it's 100% compatible, yay.

  • Property mode set to 100644
File size: 14.9 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;
[6f10697]20  if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
21  Fifth Floor, Boston, MA  02110-1301  USA
[2c2df7d]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
[5ebff60]32/*
[2c2df7d]33 * Since that might be confusing a note on naming:
34 *
[5ebff60]35 * Generic dcc functions start with
[2c2df7d]36 *
[5ebff60]37 *      dcc_
[2c2df7d]38 *
39 * ,methods specific to DCC SEND start with
40 *
[5ebff60]41 *      dccs_
[2c2df7d]42 *
43 * . Since we can be on both ends of a DCC SEND,
44 * functions specific to one end are called
45 *
[5ebff60]46 *      dccs_send and dccs_recv
[2c2df7d]47 *
48 * ,respectively.
49 */
50
51
[5ebff60]52/*
[2c2df7d]53 * used to generate a unique local transfer id the user
54 * can use to reject/cancel transfers
55 */
[5ebff60]56unsigned int local_transfer_id = 1;
[2c2df7d]57
[5ebff60]58/*
[2c2df7d]59 * just for debugging the nr. of chunks we received from im-protocols and the total data
60 */
[5ebff60]61unsigned int receivedchunks = 0, receiveddata = 0;
62
63void dcc_finish(file_transfer_t *file);
64void dcc_close(file_transfer_t *file);
65gboolean dccs_send_proto(gpointer data, gint fd, b_input_condition cond);
66int dccs_send_request(struct dcc_file_transfer *df, irc_user_t *iu, struct sockaddr_storage *saddr);
67gboolean dccs_recv_proto(gpointer data, gint fd, b_input_condition cond);
68gboolean dccs_recv_write_request(file_transfer_t *ft);
69gboolean dcc_progress(gpointer data, gint fd, b_input_condition cond);
70gboolean dcc_abort(dcc_file_transfer_t *df, char *reason, ...);
71
72dcc_file_transfer_t *dcc_alloc_transfer(const char *file_name, size_t file_size, struct im_connection *ic)
[2ff2076]73{
[5ebff60]74        file_transfer_t *file = g_new0(file_transfer_t, 1);
75        dcc_file_transfer_t *df = file->priv = g_new0(dcc_file_transfer_t, 1);
76
[2ff2076]77        file->file_size = file_size;
[5ebff60]78        file->file_name = g_strdup(file_name);
[2ff2076]79        file->local_id = local_transfer_id++;
[9d4352c]80        file->ic = df->ic = ic;
[2ff2076]81        df->ft = file;
[5ebff60]82
[2ff2076]83        return df;
84}
85
[2c2df7d]86/* This is where the sending magic starts... */
[5ebff60]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;
[2e8523b]94        char host[NI_MAXHOST];
[a02f34f]95        char port[6];
[2c2df7d]96
[5ebff60]97        if (file_size > global.conf->ft_max_size) {
[2c2df7d]98                return NULL;
[5ebff60]99        }
100
101        df = dcc_alloc_transfer(file_name, file_size, ic);
[2ff2076]102        file = df->ft;
[dce3903]103        file->write = dccs_send_write;
[2ff2076]104
[2c2df7d]105        /* listen and request */
[a02f34f]106
[5ebff60]107        if ((df->fd = ft_listen(&saddr, host, port, irc->fd, TRUE, &errmsg)) == -1) {
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
[5ebff60]114        if (!dccs_send_request(df, iu, &saddr)) {
[a02f34f]115                return NULL;
[5ebff60]116        }
[2c2df7d]117
118        /* watch */
[5ebff60]119        df->watch_in = b_input_add(df->fd, B_EV_IO_READ, dccs_send_proto, df);
[2c2df7d]120
[5ebff60]121        irc->file_transfers = g_slist_prepend(irc->file_transfers, file);
[2c2df7d]122
[5ebff60]123        df->progress_timeout = b_timeout_add(DCC_MAX_STALL * 1000, dcc_progress, df);
[d56ee38]124
[5ebff60]125        imcb_log(ic, "File transfer request from %s for %s (%zd kb).\n"
126                 "Accept the file transfer if you'd like the file. If you don't, "
127                 "issue the 'transfer reject' command.",
128                 iu->nick, file_name, file_size / 1024);
[4ac647d]129
[2c2df7d]130        return file;
131}
132
133/* Used pretty much everywhere in the code to abort a transfer */
[5ebff60]134gboolean dcc_abort(dcc_file_transfer_t *df, char *reason, ...)
[2c2df7d]135{
136        file_transfer_t *file = df->ft;
137        va_list params;
[5ebff60]138
139        va_start(params, reason);
140        char *msg = g_strdup_vprintf(reason, params);
141        va_end(params);
142
[2c2df7d]143        file->status |= FT_STATUS_CANCELED;
[d56ee38]144
[5ebff60]145        if (file->canceled) {
146                file->canceled(file, msg);
147        }
148
149        imcb_log(df->ic, "File %s: DCC transfer aborted: %s", file->file_name, msg);
[2c2df7d]150
[5ebff60]151        g_free(msg);
[2c2df7d]152
[5ebff60]153        dcc_close(df->ft);
[2c2df7d]154
155        return FALSE;
156}
157
[5ebff60]158gboolean dcc_progress(gpointer data, gint fd, b_input_condition cond)
[d56ee38]159{
160        struct dcc_file_transfer *df = data;
161
[5ebff60]162        if (df->bytes_sent == df->progress_bytes_last) {
[d56ee38]163                /* no progress. cancel */
[5ebff60]164                if (df->bytes_sent == 0) {
165                        return dcc_abort(df, "Couldn't establish transfer within %d seconds", DCC_MAX_STALL);
166                } else {
167                        return dcc_abort(df, "Transfer stalled for %d seconds at %d kb", DCC_MAX_STALL,
168                                         df->bytes_sent / 1024);
169                }
[d56ee38]170
171        }
172
[b043ad5]173        df->progress_bytes_last = df->bytes_sent;
[d56ee38]174
175        return TRUE;
176}
177
[2c2df7d]178/* used extensively for socket operations */
179#define ASSERTSOCKOP(op, msg) \
[5ebff60]180        if ((op) == -1) { \
181                return dcc_abort(df, msg ": %s", strerror(errno)); }
[2c2df7d]182
183/* Creates the "DCC SEND" line and sends it to the server */
[5ebff60]184int dccs_send_request(struct dcc_file_transfer *df, irc_user_t *iu, struct sockaddr_storage *saddr)
[2c2df7d]185{
[5ebff60]186        char ipaddr[INET6_ADDRSTRLEN];
[2c2df7d]187        const void *netaddr;
188        int port;
189        char *cmd;
190
[5ebff60]191        if (saddr->ss_family == AF_INET) {
[2c2df7d]192                struct sockaddr_in *saddr_ipv4 = ( struct sockaddr_in *) saddr;
193
[5ebff60]194                sprintf(ipaddr, "%d",
195                        ntohl(saddr_ipv4->sin_addr.s_addr));
[2c2df7d]196                port = saddr_ipv4->sin_port;
[5ebff60]197        } else {
[2c2df7d]198                struct sockaddr_in6 *saddr_ipv6 = ( struct sockaddr_in6 *) saddr;
199
200                netaddr = &saddr_ipv6->sin6_addr.s6_addr;
201                port = saddr_ipv6->sin6_port;
202
[5ebff60]203                /*
[2c2df7d]204                 * Didn't find docs about this, but it seems that's the way irssi does it
205                 */
[5ebff60]206                if (!inet_ntop(saddr->ss_family, netaddr, ipaddr, sizeof(ipaddr))) {
207                        return dcc_abort(df, "inet_ntop failed: %s", strerror(errno));
208                }
[2c2df7d]209        }
210
[5ebff60]211        port = ntohs(port);
212
213        cmd = g_strdup_printf("\001DCC SEND %s %s %u %zu\001",
214                              df->ft->file_name, ipaddr, port, df->ft->file_size);
[2c2df7d]215
[9767d03]216        irc_send_msg_raw(iu, "PRIVMSG", iu->irc->user->nick, cmd);
[2c2df7d]217
[5ebff60]218        g_free(cmd);
[2c2df7d]219
220        return TRUE;
221}
222
[2ff2076]223/*
224 * After setup, the transfer itself is handled entirely by this function.
225 * There are basically four things to handle: connect, receive, send, and error.
226 */
[5ebff60]227gboolean dccs_send_proto(gpointer data, gint fd, b_input_condition cond)
[2ff2076]228{
229        dcc_file_transfer_t *df = data;
230        file_transfer_t *file = df->ft;
[5ebff60]231
232        if ((cond & B_EV_IO_READ) &&
233            (file->status & FT_STATUS_LISTENING)) {
[2c2df7d]234                struct sockaddr *clt_addr;
[5ebff60]235                socklen_t ssize = sizeof(clt_addr);
[2c2df7d]236
237                /* Connect */
238
[5ebff60]239                ASSERTSOCKOP(df->fd = accept(fd, (struct sockaddr *) &clt_addr, &ssize), "Accepting connection");
[2c2df7d]240
[5ebff60]241                closesocket(fd);
[2c2df7d]242                fd = df->fd;
[2ff2076]243                file->status = FT_STATUS_TRANSFERRING;
[5ebff60]244                sock_make_nonblocking(fd);
[2c2df7d]245
246                /* IM protocol callback */
[5ebff60]247                if (file->accept) {
248                        file->accept(file);
249                }
[dce3903]250
[2c2df7d]251                /* reschedule for reading on new fd */
[5ebff60]252                df->watch_in = b_input_add(fd, B_EV_IO_READ, dccs_send_proto, df);
[2c2df7d]253
254                return FALSE;
255        }
256
[5ebff60]257        if (cond & B_EV_IO_READ) {
[2c2df7d]258                int ret;
259
[5ebff60]260                ASSERTSOCKOP(ret = recv(fd, ((char *) &df->acked) + df->acked_len,
261                                        sizeof(df->acked) - df->acked_len, 0), "Receiving");
262
263                if (ret == 0) {
264                        return dcc_abort(df, "Remote end closed connection");
265                }
266
[e88fe7da]267                /* How likely is it that a 32-bit integer gets split across
[4ed9c8c]268                   packet boundaries? Chances are rarely 0 so let's be sure. */
[5ebff60]269                if ((df->acked_len = (df->acked_len + ret) % 4) > 0) {
[2c2df7d]270                        return TRUE;
[5ebff60]271                }
[2c2df7d]272
[5ebff60]273                df->acked = ntohl(df->acked);
[2c2df7d]274
275                /* If any of this is actually happening, the receiver should buy a new IRC client */
276
[5ebff60]277                if (df->acked > df->bytes_sent) {
278                        return dcc_abort(df,
279                                         "Receiver magically received more bytes than sent ( %d > %d ) (BUG at receiver?)", df->acked,
280                                         df->bytes_sent);
281                }
282
283                if (df->acked < file->bytes_transferred) {
284                        return dcc_abort(df, "Receiver lost bytes? ( has %d, had %d ) (BUG at receiver?)", df->acked,
285                                         file->bytes_transferred);
286                }
[2c2df7d]287
[4ed9c8c]288                file->bytes_transferred = df->acked;
[5ebff60]289
290                if (file->bytes_transferred >= file->file_size) {
291                        if (df->proto_finished) {
292                                dcc_finish(file);
293                        }
[2c2df7d]294                        return FALSE;
295                }
[5ebff60]296
[2c2df7d]297                return TRUE;
298        }
299
300        return TRUE;
301}
302
[5ebff60]303gboolean dccs_recv_start(file_transfer_t *ft)
[2ff2076]304{
305        dcc_file_transfer_t *df = ft->priv;
306        struct sockaddr_storage *saddr = &df->saddr;
307        int fd;
[5ebff60]308        char ipaddr[INET6_ADDRSTRLEN];
309        socklen_t sa_len = saddr->ss_family == AF_INET ?
310                           sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
311
312        if (!ft->write) {
313                return dcc_abort(df, "BUG: protocol didn't register write()");
314        }
315
316        ASSERTSOCKOP(fd = df->fd = socket(saddr->ss_family, SOCK_STREAM, 0), "Opening Socket");
317
318        sock_make_nonblocking(fd);
319
320        if ((connect(fd, (struct sockaddr *) saddr, sa_len) == -1) &&
321            (errno != EINPROGRESS)) {
322                return dcc_abort(df, "Connecting to %s:%d : %s",
323                                 inet_ntop(saddr->ss_family,
324                                           saddr->ss_family == AF_INET ?
325                                           ( void * ) &(( struct sockaddr_in *) saddr)->sin_addr.s_addr :
326                                           ( void * ) &(( struct sockaddr_in6 *) saddr)->sin6_addr.s6_addr,
327                                           ipaddr,
328                                           sizeof(ipaddr)),
329                                 ntohs(saddr->ss_family == AF_INET ?
330                                       (( struct sockaddr_in *) saddr)->sin_port :
331                                       (( struct sockaddr_in6 *) saddr)->sin6_port),
332                                 strerror(errno));
333        }
[2ff2076]334
335        ft->status = FT_STATUS_CONNECTING;
336
337        /* watch */
[5ebff60]338        df->watch_out = b_input_add(df->fd, B_EV_IO_WRITE, dccs_recv_proto, df);
[dce3903]339        ft->write_request = dccs_recv_write_request;
[2ff2076]340
[5ebff60]341        df->progress_timeout = b_timeout_add(DCC_MAX_STALL * 1000, dcc_progress, df);
[d56ee38]342
[2ff2076]343        return TRUE;
344}
345
[5ebff60]346gboolean dccs_recv_proto(gpointer data, gint fd, b_input_condition cond)
[2ff2076]347{
348        dcc_file_transfer_t *df = data;
349        file_transfer_t *ft = df->ft;
350
[5ebff60]351        if ((cond & B_EV_IO_WRITE) &&
352            (ft->status & FT_STATUS_CONNECTING)) {
[2ff2076]353                ft->status = FT_STATUS_TRANSFERRING;
354
[0cb71a6]355                //df->watch_in = b_input_add( df->fd, B_EV_IO_READ, dccs_recv_proto, df );
[2ff2076]356
357                df->watch_out = 0;
358                return FALSE;
359        }
360
[5ebff60]361        if (cond & B_EV_IO_READ) {
[2ff2076]362                int ret, done;
363
[5ebff60]364                ASSERTSOCKOP(ret = recv(fd, ft->buffer, sizeof(ft->buffer), 0), "Receiving");
[2ff2076]365
[5ebff60]366                if (ret == 0) {
367                        return dcc_abort(df, "Remote end closed connection");
368                }
[2ff2076]369
[5ebff60]370                if (!ft->write(df->ft, ft->buffer, ret)) {
[4ac647d]371                        return FALSE;
[5ebff60]372                }
[4ac647d]373
[2ff2076]374                df->bytes_sent += ret;
375
376                done = df->bytes_sent >= ft->file_size;
377
[5ebff60]378                if (((df->bytes_sent - ft->bytes_transferred) > DCC_PACKET_SIZE) ||
379                    done) {
380                        guint32 ack = htonl(ft->bytes_transferred = df->bytes_sent);
[4ed9c8c]381                        int ackret;
[2ff2076]382
[5ebff60]383                        ASSERTSOCKOP(ackret = send(fd, &ack, 4, 0), "Sending DCC ACK");
384
385                        if (ackret != 4) {
386                                return dcc_abort(df, "Error sending DCC ACK, sent %d instead of 4 bytes", ackret);
387                        }
388                }
389
390                if (df->bytes_sent == ret) {
391                        ft->started = time(NULL);
[2ff2076]392                }
393
[5ebff60]394                if (done) {
395                        if (df->watch_out) {
396                                b_event_remove(df->watch_out);
397                        }
[4ac647d]398
[b6bd99c]399                        df->watch_in = 0;
400
[5ebff60]401                        if (df->proto_finished) {
402                                dcc_finish(ft);
403                        }
[2ff2076]404
405                        return FALSE;
406                }
407
[dce3903]408                df->watch_in = 0;
409                return FALSE;
[2ff2076]410        }
411
412        return TRUE;
413}
414
[5ebff60]415gboolean dccs_recv_write_request(file_transfer_t *ft)
[2ff2076]416{
417        dcc_file_transfer_t *df = ft->priv;
418
[5ebff60]419        if (df->watch_in) {
420                return dcc_abort(df, "BUG: write_request() called while watching");
421        }
[dce3903]422
[5ebff60]423        df->watch_in = b_input_add(df->fd, B_EV_IO_READ, dccs_recv_proto, df);
[dce3903]424
425        return TRUE;
426}
427
[5ebff60]428gboolean dccs_send_can_write(gpointer data, gint fd, b_input_condition cond)
[dce3903]429{
430        struct dcc_file_transfer *df = data;
[5ebff60]431
[dce3903]432        df->watch_out = 0;
433
[5ebff60]434        df->ft->write_request(df->ft);
[dce3903]435        return FALSE;
[2ff2076]436}
437
[5ebff60]438/*
[dce3903]439 * Incoming data.
[5ebff60]440 *
[dce3903]441 */
[5ebff60]442gboolean dccs_send_write(file_transfer_t *file, char *data, unsigned int data_len)
[2c2df7d]443{
444        dcc_file_transfer_t *df = file->priv;
[dce3903]445        int ret;
446
447        receivedchunks++; receiveddata += data_len;
[2c2df7d]448
[5ebff60]449        if (df->watch_out) {
450                return dcc_abort(df, "BUG: write() called while watching");
451        }
[2c2df7d]452
[5ebff60]453        ASSERTSOCKOP(ret = send(df->fd, data, data_len, 0), "Sending data");
[2c2df7d]454
[5ebff60]455        if (ret == 0) {
456                return dcc_abort(df, "Remote end closed connection");
457        }
[2c2df7d]458
[dce3903]459        /* TODO: this should really not be fatal */
[5ebff60]460        if (ret < data_len) {
461                return dcc_abort(df, "send() sent %d instead of %d", ret, data_len);
462        }
[2c2df7d]463
[5ebff60]464        if (df->bytes_sent == 0) {
465                file->started = time(NULL);
466        }
[4ac647d]467
[dce3903]468        df->bytes_sent += ret;
469
[5ebff60]470        if (df->bytes_sent < df->ft->file_size) {
471                df->watch_out = b_input_add(df->fd, B_EV_IO_WRITE, dccs_send_can_write, df);
472        }
[dce3903]473
474        return TRUE;
[2c2df7d]475}
476
477/*
478 * Cleans up after a transfer.
479 */
[5ebff60]480void dcc_close(file_transfer_t *file)
[2c2df7d]481{
482        dcc_file_transfer_t *df = file->priv;
[17a6ee9]483        irc_t *irc = (irc_t *) df->ic->bee->ui_data;
[2c2df7d]484
[5ebff60]485        if (file->free) {
486                file->free(file);
487        }
488
489        closesocket(df->fd);
490
491        if (df->watch_in) {
492                b_event_remove(df->watch_in);
493        }
494
495        if (df->watch_out) {
496                b_event_remove(df->watch_out);
497        }
498
499        if (df->progress_timeout) {
500                b_event_remove(df->progress_timeout);
501        }
502
503        irc->file_transfers = g_slist_remove(irc->file_transfers, file);
504
505        g_free(df);
506        g_free(file->file_name);
507        g_free(file);
[2c2df7d]508}
509
[5ebff60]510void dcc_finish(file_transfer_t *file)
[2c2df7d]511{
[4ac647d]512        dcc_file_transfer_t *df = file->priv;
[5ebff60]513        time_t diff = time(NULL) - file->started ? : 1;
[4ac647d]514
[2c2df7d]515        file->status |= FT_STATUS_FINISHED;
516
[5ebff60]517        if (file->finished) {
518                file->finished(file);
519        }
520
521        imcb_log(df->ic, "File %s transferred successfully at %d kb/s!", file->file_name,
522                 (int) (file->bytes_transferred / 1024 / diff));
523        dcc_close(file);
[2c2df7d]524}
[2ff2076]525
[5ebff60]526/*
[2ff2076]527 * DCC SEND <filename> <IP> <port> <filesize>
528 *
529 * filename can be in "" or not. If it is, " can probably be escaped...
530 * IP can be an unsigned int (IPV4) or something else (IPV6)
[5ebff60]531 *
[2ff2076]532 */
[5ebff60]533file_transfer_t *dcc_request(struct im_connection *ic, char* const* ctcp)
[2ff2076]534{
[17a6ee9]535        irc_t *irc = (irc_t *) ic->bee->ui_data;
[2ff2076]536        file_transfer_t *ft;
537        dcc_file_transfer_t *df;
[89c11e7]538        int gret;
539        size_t filesize;
[5ebff60]540
541        if (ctcp[5] != NULL &&
542            sscanf(ctcp[4], "%zd", &filesize) == 1 &&   /* Just int. validation. */
543            sscanf(ctcp[5], "%zd", &filesize) == 1) {
[2ff2076]544                char *filename, *host, *port;
545                struct addrinfo hints, *rp;
[5ebff60]546
[89c11e7]547                filename = ctcp[2];
[5ebff60]548
[89c11e7]549                host = ctcp[3];
[5ebff60]550                while (*host && g_ascii_isdigit(*host)) {
551                        host++;                                    /* Just digits? */
552                }
553                if (*host == '\0') {
554                        struct in_addr ipaddr = { .s_addr = htonl(atoll(ctcp[3])) };
555                        host = inet_ntoa(ipaddr);
556                } else {
[2ff2076]557                        /* Contains non-numbers, hopefully an IPV6 address */
[89c11e7]558                        host = ctcp[3];
[2ff2076]559                }
560
[89c11e7]561                port = ctcp[4];
[5ebff60]562                filesize = atoll(ctcp[5]);
[2ff2076]563
[5ebff60]564                memset(&hints, 0, sizeof(struct addrinfo));
[aac4017]565                hints.ai_socktype = SOCK_STREAM;
566                hints.ai_flags = AI_NUMERICSERV;
567
[5ebff60]568                if ((gret = getaddrinfo(host, port, &hints, &rp))) {
569                        imcb_log(ic, "DCC: getaddrinfo() failed with %s "
570                                 "when parsing incoming 'DCC SEND': "
571                                 "host %s, port %s",
572                                 gai_strerror(gret), host, port);
[2ff2076]573                        return NULL;
574                }
575
[5ebff60]576                df = dcc_alloc_transfer(filename, filesize, ic);
[2ff2076]577                ft = df->ft;
578                ft->sending = TRUE;
[5ebff60]579                memcpy(&df->saddr, rp->ai_addr, rp->ai_addrlen);
[2ff2076]580
[5ebff60]581                freeaddrinfo(rp);
[2ff2076]582
[5ebff60]583                irc->file_transfers = g_slist_prepend(irc->file_transfers, ft);
[2ff2076]584
585                return ft;
[5ebff60]586        } else {
587                imcb_log(ic, "DCC: couldnt parse `DCC SEND' line");
[2ff2076]588        }
[6cac643]589
[2ff2076]590        return NULL;
591}
Note: See TracBrowser for help on using the repository browser.