diff options
author | berkeviktor@aol.com <berkeviktor@aol.com> | 2011-02-24 04:14:30 +0100 |
---|---|---|
committer | berkeviktor@aol.com <berkeviktor@aol.com> | 2011-02-24 04:14:30 +0100 |
commit | 4a6ceffb98a0b785494f680d3776c4bfc4052f9e (patch) | |
tree | 850703c1c841ccd99f58d0b06084615aaebe782c /src/common/dcc.c | |
parent | f16af8be941b596dedac3bf4e371ee2d21f4b598 (diff) |
add xchat r1489
Diffstat (limited to 'src/common/dcc.c')
-rw-r--r-- | src/common/dcc.c | 2587 |
1 files changed, 2587 insertions, 0 deletions
diff --git a/src/common/dcc.c b/src/common/dcc.c new file mode 100644 index 00000000..8f289342 --- /dev/null +++ b/src/common/dcc.c @@ -0,0 +1,2587 @@ +/* X-Chat + * Copyright (C) 1998-2006 Peter Zelezny. + * Copyright (C) 2006 Damjan Jovanovic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + * Wayne Conrad, 3 Apr 1999: Color-coded DCC file transfer status windows + * Bernhard Valenti <bernhard.valenti@gmx.net> 2000-11-21: Fixed DCC send behind nat + * + * 2001-03-08 Added support for getting "dcc_ip" config parameter. + * Jim Seymour (jseymour@LinxNet.com) + */ + +/* we only use 32 bits, but without this define, you get only 31! */ +#define _FILE_OFFSET_BITS 64 +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <errno.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> + +#define WANTSOCKET +#define WANTARPA +#define WANTDNS +#include "inet.h" + +#ifdef WIN32 +#include <windows.h> +#endif + +#include "xchat.h" +#include "util.h" +#include "fe.h" +#include "outbound.h" +#include "inbound.h" +#include "network.h" +#include "plugin.h" +#include "server.h" +#include "text.h" +#include "url.h" +#include "xchatc.h" + +#ifdef USE_DCC64 +#define BIG_STR_TO_INT(x) strtoull(x,NULL,10) +#else +#define BIG_STR_TO_INT(x) strtoul(x,NULL,10) +#endif + +static char *dcctypes[] = { "SEND", "RECV", "CHAT", "CHAT" }; + +struct dccstat_info dccstat[] = { + {N_("Waiting"), 1 /*black */ }, + {N_("Active"), 12 /*cyan */ }, + {N_("Failed"), 4 /*red */ }, + {N_("Done"), 3 /*green */ }, + {N_("Connect"), 1 /*black */ }, + {N_("Aborted"), 4 /*red */ }, +}; + +static int dcc_global_throttle; /* 0x1 = sends, 0x2 = gets */ +/*static*/ int dcc_sendcpssum, dcc_getcpssum; + +static struct DCC *new_dcc (void); +static void dcc_close (struct DCC *dcc, int dccstat, int destroy); +static gboolean dcc_send_data (GIOChannel *, GIOCondition, struct DCC *); +static gboolean dcc_read (GIOChannel *, GIOCondition, struct DCC *); +static gboolean dcc_read_ack (GIOChannel *source, GIOCondition condition, struct DCC *dcc); + +static int new_id() +{ + static int id = 0; + if (id == 0) + { + /* start the first ID at a random number for pseudo security */ + /* 1 - 255 */ + id = RAND_INT(255) + 1; + /* ignore overflows, since it can go to 2 billion */ + } + return id++; +} + +static double +timeval_diff (GTimeVal *greater, + GTimeVal *less) +{ + long usecdiff; + double result; + + result = greater->tv_sec - less->tv_sec; + usecdiff = (long) greater->tv_usec - less->tv_usec; + result += (double) usecdiff / 1000000; + + return result; +} + +static void +dcc_unthrottle (struct DCC *dcc) +{ + /* don't unthrottle here, but delegate to funcs */ + if (dcc->type == TYPE_RECV) + dcc_read (NULL, 0, dcc); + else + dcc_send_data (NULL, 0, dcc); +} + +static void +dcc_calc_cps (struct DCC *dcc) +{ + GTimeVal now; + int oldcps; + double timediff, startdiff; + int glob_throttle_bit, wasthrottled; + int *cpssum, glob_limit; + DCC_SIZE pos, posdiff; + + g_get_current_time (&now); + + /* the pos we use for sends is an average + between pos and ack */ + if (dcc->type == TYPE_SEND) + { + /* carefull to avoid 32bit overflow */ + pos = dcc->pos - ((dcc->pos - dcc->ack) / 2); + glob_throttle_bit = 0x1; + cpssum = &dcc_sendcpssum; + glob_limit = prefs.dcc_global_max_send_cps; + } + else + { + pos = dcc->pos; + glob_throttle_bit = 0x2; + cpssum = &dcc_getcpssum; + glob_limit = prefs.dcc_global_max_get_cps; + } + + if (!dcc->firstcpstv.tv_sec && !dcc->firstcpstv.tv_usec) + dcc->firstcpstv = now; + else + { + startdiff = timeval_diff (&now, &dcc->firstcpstv); + if (startdiff < 1) + startdiff = 1; + else if (startdiff > CPS_AVG_WINDOW) + startdiff = CPS_AVG_WINDOW; + + timediff = timeval_diff (&now, &dcc->lastcpstv); + if (timediff > startdiff) + timediff = startdiff = 1; + + posdiff = pos - dcc->lastcpspos; + oldcps = dcc->cps; + dcc->cps = ((double) posdiff / timediff) * (timediff / startdiff) + + (double) dcc->cps * (1.0 - (timediff / startdiff)); + + *cpssum += dcc->cps - oldcps; + } + + dcc->lastcpspos = pos; + dcc->lastcpstv = now; + + /* now check cps against set limits... */ + wasthrottled = dcc->throttled; + + /* check global limits first */ + dcc->throttled &= ~0x2; + if (glob_limit > 0 && *cpssum >= glob_limit) + { + dcc_global_throttle |= glob_throttle_bit; + if (dcc->maxcps >= 0) + dcc->throttled |= 0x2; + } + else + dcc_global_throttle &= ~glob_throttle_bit; + + /* now check per-connection limit */ + if (dcc->maxcps > 0 && dcc->cps > dcc->maxcps) + dcc->throttled |= 0x1; + else + dcc->throttled &= ~0x1; + + /* take action */ + if (wasthrottled && !dcc->throttled) + dcc_unthrottle (dcc); +} + +static void +dcc_remove_from_sum (struct DCC *dcc) +{ + if (dcc->dccstat != STAT_ACTIVE) + return; + if (dcc->type == TYPE_SEND) + dcc_sendcpssum -= dcc->cps; + else if (dcc->type == TYPE_RECV) + dcc_getcpssum -= dcc->cps; +} + +gboolean +is_dcc (struct DCC *dcc) +{ + GSList *list = dcc_list; + while (list) + { + if (list->data == dcc) + return TRUE; + list = list->next; + } + return FALSE; +} + +/* this is called from xchat.c:xchat_misc_checks() every 1 second. */ + +void +dcc_check_timeouts (void) +{ + struct DCC *dcc; + time_t tim = time (0); + GSList *next, *list = dcc_list; + + while (list) + { + dcc = (struct DCC *) list->data; + next = list->next; + + switch (dcc->dccstat) + { + case STAT_ACTIVE: + dcc_calc_cps (dcc); + fe_dcc_update (dcc); + + if (dcc->type == TYPE_SEND || dcc->type == TYPE_RECV) + { + if (prefs.dccstalltimeout > 0) + { + if (!dcc->throttled + && tim - dcc->lasttime > prefs.dccstalltimeout) + { + EMIT_SIGNAL (XP_TE_DCCSTALL, dcc->serv->front_session, + dcctypes[dcc->type], + file_part (dcc->file), dcc->nick, NULL, 0); + dcc_close (dcc, STAT_ABORTED, FALSE); + } + } + } + break; + case STAT_QUEUED: + if (dcc->type == TYPE_SEND || dcc->type == TYPE_CHATSEND) + { + if (tim - dcc->offertime > prefs.dcctimeout) + { + if (prefs.dcctimeout > 0) + { + EMIT_SIGNAL (XP_TE_DCCTOUT, dcc->serv->front_session, + dcctypes[dcc->type], + file_part (dcc->file), dcc->nick, NULL, 0); + dcc_close (dcc, STAT_ABORTED, FALSE); + } + } + } + break; + case STAT_DONE: + case STAT_FAILED: + case STAT_ABORTED: + if (prefs.dcc_remove) + dcc_close (dcc, 0, TRUE); + break; + } + list = next; + } +} + +static int +dcc_lookup_proxy (char *host, struct sockaddr_in *addr) +{ + struct hostent *h; + static char *cache_host = NULL; + static guint32 cache_addr; + + /* too lazy to thread this, so we cache results */ + if (cache_host) + { + if (strcmp (host, cache_host) == 0) + { + memcpy (&addr->sin_addr, &cache_addr, 4); + return TRUE; + } + free (cache_host); + cache_host = NULL; + } + + h = gethostbyname (host); + if (h != NULL && h->h_length == 4 && h->h_addr_list[0] != NULL) + { + memcpy (&addr->sin_addr, h->h_addr, 4); + memcpy (&cache_addr, h->h_addr, 4); + cache_host = strdup (host); + return TRUE; + } + + return FALSE; +} + +#define DCC_USE_PROXY() (prefs.proxy_host[0] && prefs.proxy_type>0 && prefs.proxy_type<5 && prefs.proxy_use!=1) + +static int +dcc_connect_sok (struct DCC *dcc) +{ + int sok; + struct sockaddr_in addr; + + sok = socket (AF_INET, SOCK_STREAM, 0); + if (sok == -1) + return -1; + + memset (&addr, 0, sizeof (addr)); + addr.sin_family = AF_INET; + if (DCC_USE_PROXY ()) + { + if (!dcc_lookup_proxy (prefs.proxy_host, &addr)) + { + closesocket (sok); + return -1; + } + addr.sin_port = htons (prefs.proxy_port); + } + else + { + addr.sin_port = htons (dcc->port); + addr.sin_addr.s_addr = htonl (dcc->addr); + } + + set_nonblocking (sok); + connect (sok, (struct sockaddr *) &addr, sizeof (addr)); + + return sok; +} + +static void +dcc_close (struct DCC *dcc, int dccstat, int destroy) +{ + if (dcc->wiotag) + { + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + } + + if (dcc->iotag) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + } + + if (dcc->sok != -1) + { + closesocket (dcc->sok); + dcc->sok = -1; + } + + dcc_remove_from_sum (dcc); + + if (dcc->fp != -1) + { + close (dcc->fp); + dcc->fp = -1; + + if(dccstat == STAT_DONE) + { + /* if we just completed a dcc recieve, move the */ + /* completed file to the completed directory */ + if(dcc->type == TYPE_RECV) + { + /* mgl: change this to use destfile_fs for correctness and to */ + /* handle the case where dccwithnick is set */ + move_file_utf8 (prefs.dccdir, prefs.dcc_completed_dir, + file_part (dcc->destfile), prefs.dccpermissions); + } + + } + } + + dcc->dccstat = dccstat; + if (dcc->dccchat) + { + free (dcc->dccchat); + dcc->dccchat = NULL; + } + + if (destroy) + { + dcc_list = g_slist_remove (dcc_list, dcc); + fe_dcc_remove (dcc); + if (dcc->proxy) + free (dcc->proxy); + if (dcc->file) + free (dcc->file); + if (dcc->destfile) + g_free (dcc->destfile); + if (dcc->destfile_fs) + g_free (dcc->destfile_fs); + free (dcc->nick); + free (dcc); + return; + } + + fe_dcc_update (dcc); +} + +void +dcc_abort (session *sess, struct DCC *dcc) +{ + if (dcc) + { + switch (dcc->dccstat) + { + case STAT_QUEUED: + case STAT_CONNECTING: + case STAT_ACTIVE: + dcc_close (dcc, STAT_ABORTED, FALSE); + switch (dcc->type) + { + case TYPE_CHATSEND: + case TYPE_CHATRECV: + EMIT_SIGNAL (XP_TE_DCCCHATABORT, sess, dcc->nick, NULL, NULL, + NULL, 0); + break; + case TYPE_SEND: + EMIT_SIGNAL (XP_TE_DCCSENDABORT, sess, dcc->nick, + file_part (dcc->file), NULL, NULL, 0); + break; + case TYPE_RECV: + EMIT_SIGNAL (XP_TE_DCCRECVABORT, sess, dcc->nick, + dcc->file, NULL, NULL, 0); + } + break; + default: + dcc_close (dcc, 0, TRUE); + } + } +} + +void +dcc_notify_kill (struct server *serv) +{ + struct server *replaceserv = 0; + struct DCC *dcc; + GSList *list = dcc_list; + if (serv_list) + replaceserv = (struct server *) serv_list->data; + while (list) + { + dcc = (struct DCC *) list->data; + if (dcc->serv == serv) + dcc->serv = replaceserv; + list = list->next; + } +} + +struct DCC * +dcc_write_chat (char *nick, char *text) +{ + struct DCC *dcc; + int len; + + dcc = find_dcc (nick, "", TYPE_CHATRECV); + if (!dcc) + dcc = find_dcc (nick, "", TYPE_CHATSEND); + if (dcc && dcc->dccstat == STAT_ACTIVE) + { + len = strlen (text); + tcp_send_real (NULL, dcc->sok, dcc->serv->encoding, dcc->serv->using_irc, + text, len); + send (dcc->sok, "\n", 1, 0); + dcc->size += len; + fe_dcc_update (dcc); + return dcc; + } + return 0; +} + +/* returns: 0 - ok + 1 - the dcc is closed! */ + +static int +dcc_chat_line (struct DCC *dcc, char *line) +{ + session *sess; + char *word[PDIWORDS]; + char *po; + char *utf; + char *conv; + int ret, i; + int len; + gsize utf_len; + char portbuf[32]; + + len = strlen (line); + if (dcc->serv->using_cp1255) + len++; /* include the NUL terminator */ + + if (dcc->serv->using_irc) /* using "IRC" encoding (CP1252/UTF-8 hybrid) */ + utf = NULL; + else if (dcc->serv->encoding == NULL) /* system */ + utf = g_locale_to_utf8 (line, len, NULL, &utf_len, NULL); + else + utf = g_convert (line, len, "UTF-8", dcc->serv->encoding, 0, &utf_len, 0); + + if (utf) + { + line = utf; + len = utf_len; + } + + if (dcc->serv->using_cp1255 && len > 0) + len--; + + /* we really need valid UTF-8 now */ + conv = text_validate (&line, &len); + + sess = find_dialog (dcc->serv, dcc->nick); + if (!sess) + sess = dcc->serv->front_session; + + sprintf (portbuf, "%d", dcc->port); + + word[0] = "DCC Chat Text"; + word[1] = net_ip (dcc->addr); + word[2] = portbuf; + word[3] = dcc->nick; + word[4] = line; + for (i = 5; i < PDIWORDS; i++) + word[i] = "\000"; + + ret = plugin_emit_print (sess, word); + + /* did the plugin close it? */ + if (!g_slist_find (dcc_list, dcc)) + { + if (utf) + g_free (utf); + if (conv) + g_free (conv); + return 1; + } + + /* did the plugin eat the event? */ + if (ret) + { + if (utf) + g_free (utf); + if (conv) + g_free (conv); + return 0; + } + + url_check_line (line, len); + + if (line[0] == 1 && !strncasecmp (line + 1, "ACTION", 6)) + { + po = strchr (line + 8, '\001'); + if (po) + po[0] = 0; + inbound_action (sess, dcc->serv->nick, dcc->nick, "", line + 8, FALSE, FALSE); + } else + { + inbound_privmsg (dcc->serv, dcc->nick, "", line, FALSE); + } + if (utf) + g_free (utf); + if (conv) + g_free (conv); + return 0; +} + +static gboolean +dcc_read_chat (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + int i, len, dead; + char portbuf[32]; + char lbuf[2050]; + + while (1) + { + if (dcc->throttled) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + return FALSE; + } + + if (!dcc->iotag) + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read_chat, dcc); + + len = recv (dcc->sok, lbuf, sizeof (lbuf) - 2, 0); + if (len < 1) + { + if (len < 0) + { + if (would_block ()) + return TRUE; + } + sprintf (portbuf, "%d", dcc->port); + EMIT_SIGNAL (XP_TE_DCCCHATF, dcc->serv->front_session, dcc->nick, + net_ip (dcc->addr), portbuf, + errorstring ((len < 0) ? sock_error () : 0), 0); + dcc_close (dcc, STAT_FAILED, FALSE); + return TRUE; + } + i = 0; + lbuf[len] = 0; + while (i < len) + { + switch (lbuf[i]) + { + case '\r': + break; + case '\n': + dcc->dccchat->linebuf[dcc->dccchat->pos] = 0; + dead = dcc_chat_line (dcc, dcc->dccchat->linebuf); + + if (dead || !dcc->dccchat) /* the dcc has been closed, don't use (DCC *)! */ + return TRUE; + + dcc->pos += dcc->dccchat->pos; + dcc->dccchat->pos = 0; + fe_dcc_update (dcc); + break; + default: + dcc->dccchat->linebuf[dcc->dccchat->pos] = lbuf[i]; + if (dcc->dccchat->pos < (sizeof (dcc->dccchat->linebuf) - 1)) + dcc->dccchat->pos++; + } + i++; + } + } +} + +static void +dcc_calc_average_cps (struct DCC *dcc) +{ + time_t sec; + + sec = time (0) - dcc->starttime; + if (sec < 1) + sec = 1; + if (dcc->type == TYPE_SEND) + dcc->cps = (dcc->ack - dcc->resumable) / sec; + else + dcc->cps = (dcc->pos - dcc->resumable) / sec; +} + +static void +dcc_send_ack (struct DCC *dcc) +{ + /* send in 32-bit big endian */ + guint32 pos = htonl (dcc->pos & 0xffffffff); + send (dcc->sok, (char *) &pos, 4, 0); +} + +static gboolean +dcc_read (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + char *old; + char buf[4096]; + int n; + gboolean need_ack = FALSE; + + if (dcc->fp == -1) + { + + /* try to create the download dir (even if it exists, no harm) */ + mkdir_utf8 (prefs.dccdir); + + if (dcc->resumable) + { + dcc->fp = open (dcc->destfile_fs, O_WRONLY | O_APPEND | OFLAGS); + dcc->pos = dcc->resumable; + dcc->ack = dcc->resumable; + } else + { + if (access (dcc->destfile_fs, F_OK) == 0) + { + n = 0; + do + { + n++; + snprintf (buf, sizeof (buf), "%s.%d", dcc->destfile_fs, n); + } + while (access (buf, F_OK) == 0); + + g_free (dcc->destfile_fs); + dcc->destfile_fs = g_strdup (buf); + + old = dcc->destfile; + dcc->destfile = xchat_filename_to_utf8 (buf, -1, 0, 0, 0); + + EMIT_SIGNAL (XP_TE_DCCRENAME, dcc->serv->front_session, + old, dcc->destfile, NULL, NULL, 0); + g_free (old); + } + dcc->fp = + open (dcc->destfile_fs, OFLAGS | O_TRUNC | O_WRONLY | O_CREAT, + prefs.dccpermissions); + } + } + if (dcc->fp == -1) + { + /* the last executed function is open(), errno should be valid */ + EMIT_SIGNAL (XP_TE_DCCFILEERR, dcc->serv->front_session, dcc->destfile, + errorstring (errno), NULL, NULL, 0); + dcc_close (dcc, STAT_FAILED, FALSE); + return TRUE; + } + while (1) + { + if (dcc->throttled) + { + if (need_ack) + dcc_send_ack (dcc); + + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + return FALSE; + } + + if (!dcc->iotag) + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read, dcc); + + n = recv (dcc->sok, buf, sizeof (buf), 0); + if (n < 1) + { + if (n < 0) + { + if (would_block ()) + { + if (need_ack) + dcc_send_ack (dcc); + return TRUE; + } + } + EMIT_SIGNAL (XP_TE_DCCRECVERR, dcc->serv->front_session, dcc->file, + dcc->destfile, dcc->nick, + errorstring ((n < 0) ? sock_error () : 0), 0); + /* send ack here? but the socket is dead */ + /*if (need_ack) + dcc_send_ack (dcc);*/ + dcc_close (dcc, STAT_FAILED, FALSE); + return TRUE; + } + + if (write (dcc->fp, buf, n) == -1) /* could be out of hdd space */ + { + EMIT_SIGNAL (XP_TE_DCCRECVERR, dcc->serv->front_session, dcc->file, + dcc->destfile, dcc->nick, errorstring (errno), 0); + if (need_ack) + dcc_send_ack (dcc); + dcc_close (dcc, STAT_FAILED, FALSE); + return TRUE; + } + + dcc->lasttime = time (0); + dcc->pos += n; + need_ack = TRUE; /* send ack when we're done recv()ing */ + + if (dcc->pos >= dcc->size) + { + dcc_send_ack (dcc); + dcc_close (dcc, STAT_DONE, FALSE); + dcc_calc_average_cps (dcc); /* this must be done _after_ dcc_close, or dcc_remove_from_sum will see the wrong value in dcc->cps */ + sprintf (buf, "%d", dcc->cps); + EMIT_SIGNAL (XP_TE_DCCRECVCOMP, dcc->serv->front_session, + dcc->file, dcc->destfile, dcc->nick, buf, 0); + return TRUE; + } + } +} + +static void +dcc_open_query (server *serv, char *nick) +{ + if (prefs.autodialog) + open_query (serv, nick, FALSE); +} + +static gboolean +dcc_did_connect (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + int er; + struct sockaddr_in addr; + +#ifdef WIN32 + if (condition & G_IO_ERR) + { + int len; + + /* find the last errno for this socket */ + len = sizeof (er); + getsockopt (dcc->sok, SOL_SOCKET, SO_ERROR, (char *)&er, &len); + EMIT_SIGNAL (XP_TE_DCCCONFAIL, dcc->serv->front_session, + dcctypes[dcc->type], dcc->nick, errorstring (er), + NULL, 0); + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return FALSE; + } + +#else + memset (&addr, 0, sizeof (addr)); + addr.sin_port = htons (dcc->port); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl (dcc->addr); + + /* check if it's already connected; This always fails on winXP */ + if (connect (dcc->sok, (struct sockaddr *) &addr, sizeof (addr)) != 0) + { + er = sock_error (); + if (er != EISCONN) + { + EMIT_SIGNAL (XP_TE_DCCCONFAIL, dcc->serv->front_session, + dcctypes[dcc->type], dcc->nick, errorstring (er), + NULL, 0); + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return FALSE; + } + } +#endif + + return TRUE; +} + +static gboolean +dcc_connect_finished (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + char host[128]; + + if (dcc->iotag) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + } + + if (!dcc_did_connect (source, condition, dcc)) + return TRUE; + + dcc->dccstat = STAT_ACTIVE; + snprintf (host, sizeof host, "%s:%d", net_ip (dcc->addr), dcc->port); + + switch (dcc->type) + { + case TYPE_RECV: + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read, dcc); + EMIT_SIGNAL (XP_TE_DCCCONRECV, dcc->serv->front_session, + dcc->nick, host, dcc->file, NULL, 0); + break; + case TYPE_SEND: + /* passive send */ + dcc->fastsend = prefs.fastdccsend; + if (dcc->fastsend) + dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE, dcc_send_data, dcc); + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read_ack, dcc); + dcc_send_data (NULL, 0, (gpointer)dcc); + EMIT_SIGNAL (XP_TE_DCCCONSEND, dcc->serv->front_session, + dcc->nick, host, dcc->file, NULL, 0); + break; + case TYPE_CHATSEND: /* pchat */ + dcc_open_query (dcc->serv, dcc->nick); + case TYPE_CHATRECV: /* normal chat */ + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read_chat, dcc); + dcc->dccchat = malloc (sizeof (struct dcc_chat)); + dcc->dccchat->pos = 0; + EMIT_SIGNAL (XP_TE_DCCCONCHAT, dcc->serv->front_session, + dcc->nick, host, NULL, NULL, 0); + break; + } + dcc->starttime = time (0); + dcc->lasttime = dcc->starttime; + fe_dcc_update (dcc); + + return TRUE; +} + +static gboolean +read_proxy (struct DCC *dcc) +{ + struct proxy_state *proxy = dcc->proxy; + while (proxy->bufferused < proxy->buffersize) + { + int ret = recv (dcc->sok, &proxy->buffer[proxy->bufferused], + proxy->buffersize - proxy->bufferused, 0); + if (ret > 0) + proxy->bufferused += ret; + else + { + if (would_block ()) + return FALSE; + else + { + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + if (dcc->iotag) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + } + return FALSE; + } + } + } + return TRUE; +} + +static gboolean +write_proxy (struct DCC *dcc) +{ + struct proxy_state *proxy = dcc->proxy; + while (proxy->bufferused < proxy->buffersize) + { + int ret = send (dcc->sok, &proxy->buffer[proxy->bufferused], + proxy->buffersize - proxy->bufferused, 0); + if (ret >= 0) + proxy->bufferused += ret; + else + { + if (would_block ()) + return FALSE; + else + { + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + if (dcc->wiotag) + { + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + } + return FALSE; + } + } + } + return TRUE; +} + +static gboolean +proxy_read_line (struct DCC *dcc) +{ + struct proxy_state *proxy = dcc->proxy; + while (1) + { + proxy->buffersize = proxy->bufferused + 1; + if (!read_proxy (dcc)) + return FALSE; + if (proxy->buffer[proxy->bufferused - 1] == '\n' + || proxy->bufferused == MAX_PROXY_BUFFER) + { + proxy->buffer[proxy->bufferused - 1] = 0; + return TRUE; + } + } +} + +static gboolean +dcc_wingate_proxy_traverse (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + struct proxy_state *proxy = dcc->proxy; + if (proxy->phase == 0) + { + proxy->buffersize = snprintf ((char*) proxy->buffer, MAX_PROXY_BUFFER, + "%s %d\r\n", net_ip(dcc->addr), + dcc->port); + proxy->bufferused = 0; + dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, + dcc_wingate_proxy_traverse, dcc); + ++proxy->phase; + } + if (proxy->phase == 1) + { + if (!read_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + dcc_connect_finished (source, 0, dcc); + } + return TRUE; +} + +struct sock_connect +{ + char version; + char type; + guint16 port; + guint32 address; + char username[10]; +}; +static gboolean +dcc_socks_proxy_traverse (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + struct proxy_state *proxy = dcc->proxy; + + if (proxy->phase == 0) + { + struct sock_connect sc; + sc.version = 4; + sc.type = 1; + sc.port = htons (dcc->port); + sc.address = htonl (dcc->addr); + strncpy (sc.username, prefs.username, 9); + memcpy (proxy->buffer, &sc, sizeof (sc)); + proxy->buffersize = 8 + strlen (sc.username) + 1; + proxy->bufferused = 0; + dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, + dcc_socks_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 1) + { + if (!write_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + proxy->bufferused = 0; + proxy->buffersize = 8; + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, + dcc_socks_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 2) + { + if (!read_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + if (proxy->buffer[1] == 90) + dcc_connect_finished (source, 0, dcc); + else + { + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + } + } + + return TRUE; +} + +struct sock5_connect1 +{ + char version; + char nmethods; + char method; +}; +static gboolean +dcc_socks5_proxy_traverse (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + struct proxy_state *proxy = dcc->proxy; + int auth = prefs.proxy_auth && prefs.proxy_user[0] && prefs.proxy_pass[0]; + + if (proxy->phase == 0) + { + struct sock5_connect1 sc1; + + sc1.version = 5; + sc1.nmethods = 1; + sc1.method = 0; + if (auth) + sc1.method = 2; + memcpy (proxy->buffer, &sc1, 3); + proxy->buffersize = 3; + proxy->bufferused = 0; + dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, + dcc_socks5_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 1) + { + if (!write_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + proxy->bufferused = 0; + proxy->buffersize = 2; + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, + dcc_socks5_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 2) + { + if (!read_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + + /* did the server say no auth required? */ + if (proxy->buffer[0] == 5 && proxy->buffer[1] == 0) + auth = 0; + + /* Set up authentication I/O */ + if (auth) + { + int len_u=0, len_p=0; + + /* authentication sub-negotiation (RFC1929) */ + if ( proxy->buffer[0] != 5 || proxy->buffer[1] != 2 ) /* UPA not supported by server */ + { + PrintText (dcc->serv->front_session, "SOCKS\tServer doesn't support UPA authentication.\n"); + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return TRUE; + } + + memset (proxy->buffer, 0, MAX_PROXY_BUFFER); + + /* form the UPA request */ + len_u = strlen (prefs.proxy_user); + len_p = strlen (prefs.proxy_pass); + proxy->buffer[0] = 1; + proxy->buffer[1] = len_u; + memcpy (proxy->buffer + 2, prefs.proxy_user, len_u); + proxy->buffer[2 + len_u] = len_p; + memcpy (proxy->buffer + 3 + len_u, prefs.proxy_pass, len_p); + + proxy->buffersize = 3 + len_u + len_p; + proxy->bufferused = 0; + dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, + dcc_socks5_proxy_traverse, dcc); + ++proxy->phase; + } + else + { + if (proxy->buffer[0] != 5 || proxy->buffer[1] != 0) + { + PrintText (dcc->serv->front_session, "SOCKS\tAuthentication required but disabled in settings.\n"); + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return TRUE; + } + proxy->phase += 2; + } + } + + if (proxy->phase == 3) + { + if (!write_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + proxy->buffersize = 2; + proxy->bufferused = 0; + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, + dcc_socks5_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 4) + { + if (!read_proxy (dcc)) + return TRUE; + if (dcc->iotag) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + } + if (proxy->buffer[1] != 0) + { + PrintText (dcc->serv->front_session, "SOCKS\tAuthentication failed. " + "Is username and password correct?\n"); + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return TRUE; + } + ++proxy->phase; + } + + if (proxy->phase == 5) + { + proxy->buffer[0] = 5; /* version (socks 5) */ + proxy->buffer[1] = 1; /* command (connect) */ + proxy->buffer[2] = 0; /* reserved */ + proxy->buffer[3] = 1; /* address type (IPv4) */ + proxy->buffer[4] = (dcc->addr >> 24) & 0xFF; /* IP address */ + proxy->buffer[5] = (dcc->addr >> 16) & 0xFF; + proxy->buffer[6] = (dcc->addr >> 8) & 0xFF; + proxy->buffer[7] = (dcc->addr & 0xFF); + proxy->buffer[8] = (dcc->port >> 8) & 0xFF; /* port */ + proxy->buffer[9] = (dcc->port & 0xFF); + proxy->buffersize = 10; + proxy->bufferused = 0; + dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, + dcc_socks5_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 6) + { + if (!write_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + proxy->buffersize = 4; + proxy->bufferused = 0; + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, + dcc_socks5_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 7) + { + if (!read_proxy (dcc)) + return TRUE; + if (proxy->buffer[0] != 5 || proxy->buffer[1] != 0) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + if (proxy->buffer[1] == 2) + PrintText (dcc->serv->front_session, "SOCKS\tProxy refused to connect to host (not allowed).\n"); + else + PrintTextf (dcc->serv->front_session, "SOCKS\tProxy failed to connect to host (error %d).\n", proxy->buffer[1]); + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return TRUE; + } + switch (proxy->buffer[3]) + { + case 1: proxy->buffersize += 6; break; + case 3: proxy->buffersize += 1; break; + case 4: proxy->buffersize += 18; break; + }; + ++proxy->phase; + } + + if (proxy->phase == 8) + { + if (!read_proxy (dcc)) + return TRUE; + /* handle domain name case */ + if (proxy->buffer[3] == 3) + { + proxy->buffersize = 5 + proxy->buffer[4] + 2; + } + /* everything done? */ + if (proxy->bufferused == proxy->buffersize) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + dcc_connect_finished (source, 0, dcc); + } + } + return TRUE; +} + +static gboolean +dcc_http_proxy_traverse (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + struct proxy_state *proxy = dcc->proxy; + + if (proxy->phase == 0) + { + char buf[256]; + char auth_data[128]; + char auth_data2[68]; + int n, n2; + + n = snprintf (buf, sizeof (buf), "CONNECT %s:%d HTTP/1.0\r\n", + net_ip(dcc->addr), dcc->port); + if (prefs.proxy_auth) + { + n2 = snprintf (auth_data2, sizeof (auth_data2), "%s:%s", + prefs.proxy_user, prefs.proxy_pass); + base64_encode (auth_data, auth_data2, n2); + n += snprintf (buf+n, sizeof (buf)-n, "Proxy-Authorization: Basic %s\r\n", auth_data); + } + n += snprintf (buf+n, sizeof (buf)-n, "\r\n"); + proxy->buffersize = n; + proxy->bufferused = 0; + memcpy (proxy->buffer, buf, proxy->buffersize); + dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, + dcc_http_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 1) + { + if (!write_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + proxy->bufferused = 0; + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, + dcc_http_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 2) + { + if (!proxy_read_line (dcc)) + return TRUE; + /* "HTTP/1.0 200 OK" */ + if (proxy->bufferused < 12 || + memcmp (proxy->buffer, "HTTP/", 5) || memcmp (proxy->buffer + 9, "200", 3)) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + PrintText (dcc->serv->front_session, proxy->buffer); + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return TRUE; + } + proxy->bufferused = 0; + ++proxy->phase; + } + + if (proxy->phase == 3) + { + while (1) + { + /* read until blank line */ + if (proxy_read_line (dcc)) + { + if (proxy->bufferused < 1 || + (proxy->bufferused == 2 && proxy->buffer[0] == '\r')) + { + break; + } + if (proxy->bufferused > 1) + PrintText (dcc->serv->front_session, proxy->buffer); + proxy->bufferused = 0; + } + else + return TRUE; + } + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + dcc_connect_finished (source, 0, dcc); + } + + return TRUE; +} + +static gboolean +dcc_proxy_connect (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + + if (!dcc_did_connect (source, condition, dcc)) + return TRUE; + + dcc->proxy = malloc (sizeof (struct proxy_state)); + if (!dcc->proxy) + { + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return TRUE; + } + memset (dcc->proxy, 0, sizeof (struct proxy_state)); + + switch (prefs.proxy_type) + { + case 1: return dcc_wingate_proxy_traverse (source, condition, dcc); + case 2: return dcc_socks_proxy_traverse (source, condition, dcc); + case 3: return dcc_socks5_proxy_traverse (source, condition, dcc); + case 4: return dcc_http_proxy_traverse (source, condition, dcc); + } + return TRUE; +} + +static int dcc_listen_init (struct DCC *, struct session *); + +static void +dcc_connect (struct DCC *dcc) +{ + int ret; + char tbuf[400]; + + if (dcc->dccstat == STAT_CONNECTING) + return; + dcc->dccstat = STAT_CONNECTING; + + if (dcc->pasvid && dcc->port == 0) + { + /* accepted a passive dcc send */ + ret = dcc_listen_init (dcc, dcc->serv->front_session); + if (!ret) + { + dcc_close (dcc, STAT_FAILED, FALSE); + return; + } + /* possible problems with filenames containing spaces? */ + if (dcc->type == TYPE_RECV) + snprintf (tbuf, sizeof (tbuf), strchr (dcc->file, ' ') ? + "DCC SEND \"%s\" %u %d %"DCC_SFMT" %d" : + "DCC SEND %s %u %d %"DCC_SFMT" %d", dcc->file, + dcc->addr, dcc->port, dcc->size, dcc->pasvid); + else + snprintf (tbuf, sizeof (tbuf), "DCC CHAT chat %u %d %d", + dcc->addr, dcc->port, dcc->pasvid); + dcc->serv->p_ctcp (dcc->serv, dcc->nick, tbuf); + } + else + { + dcc->sok = dcc_connect_sok (dcc); + if (dcc->sok == -1) + { + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return; + } + if (DCC_USE_PROXY ()) + dcc->iotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, dcc_proxy_connect, dcc); + else + dcc->iotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, dcc_connect_finished, dcc); + } + + fe_dcc_update (dcc); +} + +static gboolean +dcc_send_data (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + char *buf; + int len, sent, sok = dcc->sok; + + if (prefs.dcc_blocksize < 1) /* this is too little! */ + prefs.dcc_blocksize = 1024; + + if (prefs.dcc_blocksize > 102400) /* this is too much! */ + prefs.dcc_blocksize = 102400; + + if (dcc->throttled) + { + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + return FALSE; + } + + if (!dcc->fastsend) + { + if (dcc->ack < dcc->pos) + return TRUE; + } + else if (!dcc->wiotag) + dcc->wiotag = fe_input_add (sok, FIA_WRITE, dcc_send_data, dcc); + + buf = malloc (prefs.dcc_blocksize); + if (!buf) + return TRUE; + + lseek (dcc->fp, dcc->pos, SEEK_SET); + len = read (dcc->fp, buf, prefs.dcc_blocksize); + if (len < 1) + goto abortit; + sent = send (sok, buf, len, 0); + + if (sent < 0 && !(would_block ())) + { +abortit: + free (buf); + EMIT_SIGNAL (XP_TE_DCCSENDFAIL, dcc->serv->front_session, + file_part (dcc->file), dcc->nick, + errorstring (sock_error ()), NULL, 0); + dcc_close (dcc, STAT_FAILED, FALSE); + return TRUE; + } + if (sent > 0) + { + dcc->pos += sent; + dcc->lasttime = time (0); + } + + /* have we sent it all yet? */ + if (dcc->pos >= dcc->size) + { + /* it's all sent now, so remove the WRITE/SEND handler */ + if (dcc->wiotag) + { + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + } + } + + free (buf); + + return TRUE; +} + +static gboolean +dcc_handle_new_ack (struct DCC *dcc) +{ + guint32 ack; + char buf[16]; + gboolean done = FALSE; + + memcpy (&ack, dcc->ack_buf, 4); + dcc->ack = ntohl (ack); + + /* this could mess up when xfering >32bit files */ + if (dcc->size <= 0xffffffff) + { + /* fix for BitchX */ + if (dcc->ack < dcc->resumable) + dcc->ackoffset = TRUE; + if (dcc->ackoffset) + dcc->ack += dcc->resumable; + } + + /* DCC complete check */ + if (dcc->pos >= dcc->size && dcc->ack >= (dcc->size & 0xffffffff)) + { + dcc->ack = dcc->size; /* force 100% ack for >4 GB */ + dcc_close (dcc, STAT_DONE, FALSE); + dcc_calc_average_cps (dcc); /* this must be done _after_ dcc_close, or dcc_remove_from_sum will see the wrong value in dcc->cps */ + sprintf (buf, "%d", dcc->cps); + EMIT_SIGNAL (XP_TE_DCCSENDCOMP, dcc->serv->front_session, + file_part (dcc->file), dcc->nick, buf, NULL, 0); + done = TRUE; + } + else if ((!dcc->fastsend) && (dcc->ack >= (dcc->pos & 0xffffffff))) + { + dcc_send_data (NULL, 0, (gpointer)dcc); + } + +#ifdef USE_DCC64 + /* take the top 32 of "bytes send" and bottom 32 of "ack" */ + dcc->ack = (dcc->pos & G_GINT64_CONSTANT (0xffffffff00000000)) | + (dcc->ack & 0xffffffff); + /* dcc->ack is only used for CPS and PERCENTAGE calcs from now on... */ +#endif + + return done; +} + +static gboolean +dcc_read_ack (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + int len; + + while (1) + { + /* try to fill up 4 bytes */ + len = recv (dcc->sok, dcc->ack_buf, 4 - dcc->ack_pos, 0); + if (len < 1) + { + if (len < 0) + { + if (would_block ()) /* ok - keep waiting */ + return TRUE; + } + EMIT_SIGNAL (XP_TE_DCCSENDFAIL, dcc->serv->front_session, + file_part (dcc->file), dcc->nick, + errorstring ((len < 0) ? sock_error () : 0), NULL, 0); + dcc_close (dcc, STAT_FAILED, FALSE); + return TRUE; + } + + dcc->ack_pos += len; + if (dcc->ack_pos >= 4) + { + dcc->ack_pos = 0; + if (dcc_handle_new_ack (dcc)) + return TRUE; + } + /* loop again until would_block() returns true */ + } +} + +static gboolean +dcc_accept (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + char host[128]; + struct sockaddr_in CAddr; + int sok; + socklen_t len; + + len = sizeof (CAddr); + sok = accept (dcc->sok, (struct sockaddr *) &CAddr, &len); + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + closesocket (dcc->sok); + if (sok < 0) + { + dcc->sok = -1; + dcc_close (dcc, STAT_FAILED, FALSE); + return TRUE; + } + set_nonblocking (sok); + dcc->sok = sok; + dcc->addr = ntohl (CAddr.sin_addr.s_addr); + + if (dcc->pasvid) + return dcc_connect_finished (NULL, 0, dcc); + + dcc->dccstat = STAT_ACTIVE; + dcc->lasttime = dcc->starttime = time (0); + dcc->fastsend = prefs.fastdccsend; + + snprintf (host, sizeof (host), "%s:%d", net_ip (dcc->addr), dcc->port); + + switch (dcc->type) + { + case TYPE_SEND: + if (dcc->fastsend) + dcc->wiotag = fe_input_add (sok, FIA_WRITE, dcc_send_data, dcc); + dcc->iotag = fe_input_add (sok, FIA_READ|FIA_EX, dcc_read_ack, dcc); + dcc_send_data (NULL, 0, (gpointer)dcc); + EMIT_SIGNAL (XP_TE_DCCCONSEND, dcc->serv->front_session, + dcc->nick, host, dcc->file, NULL, 0); + break; + + case TYPE_CHATSEND: + dcc_open_query (dcc->serv, dcc->nick); + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read_chat, dcc); + dcc->dccchat = malloc (sizeof (struct dcc_chat)); + dcc->dccchat->pos = 0; + EMIT_SIGNAL (XP_TE_DCCCONCHAT, dcc->serv->front_session, + dcc->nick, host, NULL, NULL, 0); + break; + } + + fe_dcc_update (dcc); + + return TRUE; +} + +guint32 +dcc_get_my_address (void) /* the address we'll tell the other person */ +{ + struct hostent *dns_query; + guint32 addr = 0; + + if (prefs.ip_from_server && prefs.dcc_ip) + addr = prefs.dcc_ip; + else if (prefs.dcc_ip_str[0]) + { + dns_query = gethostbyname ((const char *) prefs.dcc_ip_str); + + if (dns_query != NULL && + dns_query->h_length == 4 && + dns_query->h_addr_list[0] != NULL) + { + /*we're offered at least one IPv4 address: we take the first*/ + addr = *((guint32*) dns_query->h_addr_list[0]); + } + } + + return addr; +} + +static int +dcc_listen_init (struct DCC *dcc, session *sess) +{ + guint32 my_addr; + struct sockaddr_in SAddr; + int i, bindretval = -1; + socklen_t len; + + dcc->sok = socket (AF_INET, SOCK_STREAM, 0); + if (dcc->sok == -1) + return FALSE; + + memset (&SAddr, 0, sizeof (struct sockaddr_in)); + + len = sizeof (SAddr); + getsockname (dcc->serv->sok, (struct sockaddr *) &SAddr, &len); + + SAddr.sin_family = AF_INET; + + /*if local_ip is specified use that*/ + if (prefs.local_ip != 0xffffffff) + { + my_addr = prefs.local_ip; + SAddr.sin_addr.s_addr = prefs.local_ip; + } + /*otherwise use the default*/ + else + my_addr = SAddr.sin_addr.s_addr; + + /*if we have a valid portrange try to use that*/ + if (prefs.first_dcc_send_port > 0) + { + SAddr.sin_port = 0; + i = 0; + while ((prefs.last_dcc_send_port > ntohs(SAddr.sin_port)) && + (bindretval == -1)) + { + SAddr.sin_port = htons (prefs.first_dcc_send_port + i); + i++; + /*printf("Trying to bind against port: %d\n",ntohs(SAddr.sin_port));*/ + bindretval = bind (dcc->sok, (struct sockaddr *) &SAddr, sizeof (SAddr)); + } + + /* with a small port range, reUseAddr is needed */ + len = 1; + setsockopt (dcc->sok, SOL_SOCKET, SO_REUSEADDR, (char *) &len, sizeof (len)); + + } else + { + /* try random port */ + SAddr.sin_port = 0; + bindretval = bind (dcc->sok, (struct sockaddr *) &SAddr, sizeof (SAddr)); + } + + if (bindretval == -1) + { + /* failed to bind */ + PrintText (sess, "Failed to bind to any address or port.\n"); + return FALSE; + } + + len = sizeof (SAddr); + getsockname (dcc->sok, (struct sockaddr *) &SAddr, &len); + + dcc->port = ntohs (SAddr.sin_port); + + /*if we have a dcc_ip, we use that, so the remote client can connect*/ + /*else we try to take an address from dcc_ip_str*/ + /*if something goes wrong we tell the client to connect to our LAN ip*/ + dcc->addr = dcc_get_my_address (); + + /*if nothing else worked we use the address we bound to*/ + if (dcc->addr == 0) + dcc->addr = my_addr; + + dcc->addr = ntohl (dcc->addr); + + set_nonblocking (dcc->sok); + listen (dcc->sok, 1); + set_blocking (dcc->sok); + + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_accept, dcc); + + return TRUE; +} + +static struct session *dccsess; +static char *dccto; /* lame!! */ +static int dccmaxcps; +static int recursive = FALSE; + +static void +dcc_send_wild (char *file) +{ + dcc_send (dccsess, dccto, file, dccmaxcps, 0); +} + +void +dcc_send (struct session *sess, char *to, char *file, int maxcps, int passive) +{ + char outbuf[512]; + struct stat st; + struct DCC *dcc; + char *file_fs; + + /* this is utf8 */ + file = expand_homedir (file); + + if (!recursive && (strchr (file, '*') || strchr (file, '?'))) + { + char path[256]; + char wild[256]; + char *path_fs; /* local filesystem encoding */ + + safe_strcpy (wild, file_part (file), sizeof (wild)); + path_part (file, path, sizeof (path)); + if (path[0] != '/' || path[1] != '\0') + path[strlen (path) - 1] = 0; /* remove trailing slash */ + + dccsess = sess; + dccto = to; + dccmaxcps = maxcps; + + free (file); + + /* for_files() will use opendir, so we need local FS encoding */ + path_fs = xchat_filename_from_utf8 (path, -1, 0, 0, 0); + if (path_fs) + { + recursive = TRUE; + for_files (path_fs, wild, dcc_send_wild); + recursive = FALSE; + g_free (path_fs); + } + + return; + } + + dcc = new_dcc (); + if (!dcc) + return; + dcc->file = file; + dcc->maxcps = maxcps; + + /* get the local filesystem encoding */ + file_fs = xchat_filename_from_utf8 (file, -1, 0, 0, 0); + + if (stat (file_fs, &st) != -1) + { + +#ifndef USE_DCC64 + if (sizeof (st.st_size) > 4 && st.st_size > 4294967295U) + { + PrintText (sess, "Cannot send files larger than 4 GB.\n"); + goto xit; + } +#endif + + if (!(*file_part (file_fs)) || S_ISDIR (st.st_mode) || st.st_size < 1) + { + PrintText (sess, "Cannot send directories or empty files.\n"); + goto xit; + } + + dcc->starttime = dcc->offertime = time (0); + dcc->serv = sess->server; + dcc->dccstat = STAT_QUEUED; + dcc->size = st.st_size; + dcc->type = TYPE_SEND; + dcc->fp = open (file_fs, OFLAGS | O_RDONLY); + if (dcc->fp != -1) + { + g_free (file_fs); + if (passive || dcc_listen_init (dcc, sess)) + { + char havespaces = 0; + file = dcc->file; + while (*file) + { + if (*file == ' ') + { + if (prefs.dcc_send_fillspaces) + *file = '_'; + else + havespaces = 1; + } + file++; + } + dcc->nick = strdup (to); + if (prefs.autoopendccsendwindow) + { + if (fe_dcc_open_send_win (TRUE)) /* already open? add */ + fe_dcc_add (dcc); + } else + fe_dcc_add (dcc); + + if (passive) + { + dcc->pasvid = new_id(); + snprintf (outbuf, sizeof (outbuf), (havespaces) ? + "DCC SEND \"%s\" 199 0 %"DCC_SFMT" %d" : + "DCC SEND %s 199 0 %"DCC_SFMT" %d", + file_part (dcc->file), + dcc->size, dcc->pasvid); + } else + { + snprintf (outbuf, sizeof (outbuf), (havespaces) ? + "DCC SEND \"%s\" %u %d %"DCC_SFMT : + "DCC SEND %s %u %d %"DCC_SFMT, + file_part (dcc->file), dcc->addr, + dcc->port, dcc->size); + } + sess->server->p_ctcp (sess->server, to, outbuf); + + EMIT_SIGNAL (XP_TE_DCCOFFER, sess, file_part (dcc->file), + to, dcc->file, NULL, 0); + } else + { + dcc_close (dcc, 0, TRUE); + } + return; + } + } + PrintTextf (sess, _("Cannot access %s\n"), dcc->file); + PrintTextf (sess, "%s %d: %s\n", _("Error"), errno, errorstring (errno)); +xit: + g_free (file_fs); + dcc_close (dcc, 0, TRUE); +} + +static struct DCC * +find_dcc_from_id (int id, int type) +{ + struct DCC *dcc; + GSList *list = dcc_list; + while (list) + { + dcc = (struct DCC *) list->data; + if (dcc->pasvid == id && + dcc->dccstat == STAT_QUEUED && dcc->type == type) + return dcc; + list = list->next; + } + return 0; +} + +static struct DCC * +find_dcc_from_port (int port, int type) +{ + struct DCC *dcc; + GSList *list = dcc_list; + while (list) + { + dcc = (struct DCC *) list->data; + if (dcc->port == port && + dcc->dccstat == STAT_QUEUED && dcc->type == type) + return dcc; + list = list->next; + } + return 0; +} + +struct DCC * +find_dcc (char *nick, char *file, int type) +{ + GSList *list = dcc_list; + struct DCC *dcc; + while (list) + { + dcc = (struct DCC *) list->data; + if (nick == NULL || !rfc_casecmp (nick, dcc->nick)) + { + if (type == -1 || dcc->type == type) + { + if (!file[0]) + return dcc; + if (!strcasecmp (file, file_part (dcc->file))) + return dcc; + if (!strcasecmp (file, dcc->file)) + return dcc; + } + } + list = list->next; + } + return 0; +} + +/* called when we receive a NICK change from server */ + +void +dcc_change_nick (struct server *serv, char *oldnick, char *newnick) +{ + struct DCC *dcc; + GSList *list = dcc_list; + + while (list) + { + dcc = (struct DCC *) list->data; + if (dcc->serv == serv) + { + if (!serv->p_cmp (dcc->nick, oldnick)) + { + if (dcc->nick) + free (dcc->nick); + dcc->nick = strdup (newnick); + } + } + list = list->next; + } +} + +/* is the destination file the same? new_dcc is not opened yet */ + +static int +is_same_file (struct DCC *dcc, struct DCC *new_dcc) +{ + struct stat st_a, st_b; + + /* if it's the same filename, must be same */ + if (strcmp (dcc->destfile, new_dcc->destfile) == 0) + return TRUE; + + /* now handle case-insensitive Filesystems: HFS+, FAT */ +#ifdef WIN32 +#warning no win32 implementation - behaviour may be unreliable +#else + /* this fstat() shouldn't really fail */ + if ((dcc->fp == -1 ? stat (dcc->destfile_fs, &st_a) : fstat (dcc->fp, &st_a)) == -1) + return FALSE; + if (stat (new_dcc->destfile_fs, &st_b) == -1) + return FALSE; + + /* same inode, same device, same file! */ + if (st_a.st_ino == st_b.st_ino && + st_a.st_dev == st_b.st_dev) + return TRUE; +#endif + + return FALSE; +} + +static int +is_resumable (struct DCC *dcc) +{ + dcc->resumable = 0; + + /* Check the file size */ + if (access (dcc->destfile_fs, W_OK) == 0) + { + struct stat st; + + if (stat (dcc->destfile_fs, &st) != -1) + { + if (st.st_size < dcc->size) + { + dcc->resumable = st.st_size; + dcc->pos = st.st_size; + } + else + dcc->resume_error = 2; + } else + { + dcc->resume_errno = errno; + dcc->resume_error = 1; + } + } else + { + dcc->resume_errno = errno; + dcc->resume_error = 1; + } + + /* Now verify that this DCC is not already in progress from someone else */ + + if (dcc->resumable) + { + GSList *list = dcc_list; + struct DCC *d; + while (list) + { + d = list->data; + if (d->type == TYPE_RECV && d->dccstat != STAT_ABORTED && + d->dccstat != STAT_DONE && d->dccstat != STAT_FAILED) + { + if (d != dcc && is_same_file (d, dcc)) + { + dcc->resume_error = 3; /* dccgui.c uses it */ + dcc->resumable = 0; + dcc->pos = 0; + break; + } + } + list = list->next; + } + } + + return dcc->resumable; +} + +void +dcc_get (struct DCC *dcc) +{ + switch (dcc->dccstat) + { + case STAT_QUEUED: + if (dcc->type != TYPE_CHATSEND) + { + if (dcc->type == TYPE_RECV && prefs.autoresume && dcc->resumable) + { + dcc_resume (dcc); + } + else + { + dcc->resumable = 0; + dcc->pos = 0; + dcc_connect (dcc); + } + } + break; + case STAT_DONE: + case STAT_FAILED: + case STAT_ABORTED: + dcc_close (dcc, 0, TRUE); + break; + } +} + +/* for "Save As..." dialog */ + +void +dcc_get_with_destfile (struct DCC *dcc, char *file) +{ + g_free (dcc->destfile); + dcc->destfile = g_strdup (file); /* utf-8 */ + + /* get the local filesystem encoding */ + g_free (dcc->destfile_fs); + dcc->destfile_fs = xchat_filename_from_utf8 (dcc->destfile, -1, 0, 0, 0); + + /* since destfile changed, must check resumability again */ + is_resumable (dcc); + + dcc_get (dcc); +} + +void +dcc_get_nick (struct session *sess, char *nick) +{ + struct DCC *dcc; + GSList *list = dcc_list; + while (list) + { + dcc = (struct DCC *) list->data; + if (!sess->server->p_cmp (nick, dcc->nick)) + { + if (dcc->dccstat == STAT_QUEUED && dcc->type == TYPE_RECV) + { + dcc->resumable = 0; + dcc->pos = 0; + dcc->ack = 0; + dcc_connect (dcc); + return; + } + } + list = list->next; + } + if (sess) + EMIT_SIGNAL (XP_TE_DCCIVAL, sess, NULL, NULL, NULL, NULL, 0); +} + +static struct DCC * +new_dcc (void) +{ + struct DCC *dcc = malloc (sizeof (struct DCC)); + if (!dcc) + return 0; + memset (dcc, 0, sizeof (struct DCC)); + dcc->sok = -1; + dcc->fp = -1; + dcc_list = g_slist_prepend (dcc_list, dcc); + return (dcc); +} + +void +dcc_chat (struct session *sess, char *nick, int passive) +{ + char outbuf[512]; + struct DCC *dcc; + + dcc = find_dcc (nick, "", TYPE_CHATSEND); + if (dcc) + { + switch (dcc->dccstat) + { + case STAT_ACTIVE: + case STAT_QUEUED: + case STAT_CONNECTING: + EMIT_SIGNAL (XP_TE_DCCCHATREOFFER, sess, nick, NULL, NULL, NULL, 0); + return; + case STAT_ABORTED: + case STAT_FAILED: + dcc_close (dcc, 0, TRUE); + } + } + dcc = find_dcc (nick, "", TYPE_CHATRECV); + if (dcc) + { + switch (dcc->dccstat) + { + case STAT_QUEUED: + dcc_connect (dcc); + break; + case STAT_FAILED: + case STAT_ABORTED: + dcc_close (dcc, 0, TRUE); + } + return; + } + /* offer DCC CHAT */ + + dcc = new_dcc (); + if (!dcc) + return; + dcc->starttime = dcc->offertime = time (0); + dcc->serv = sess->server; + dcc->dccstat = STAT_QUEUED; + dcc->type = TYPE_CHATSEND; + dcc->nick = strdup (nick); + if (passive || dcc_listen_init (dcc, sess)) + { + if (prefs.autoopendccchatwindow) + { + if (fe_dcc_open_chat_win (TRUE)) /* already open? add only */ + fe_dcc_add (dcc); + } else + fe_dcc_add (dcc); + + if (passive) + { + dcc->pasvid = new_id (); + snprintf (outbuf, sizeof (outbuf), "DCC CHAT chat 199 %d %d", + dcc->port, dcc->pasvid); + } else + { + snprintf (outbuf, sizeof (outbuf), "DCC CHAT chat %u %d", + dcc->addr, dcc->port); + } + dcc->serv->p_ctcp (dcc->serv, nick, outbuf); + EMIT_SIGNAL (XP_TE_DCCCHATOFFERING, sess, nick, NULL, NULL, NULL, 0); + } else + { + dcc_close (dcc, 0, TRUE); + } +} + +static void +dcc_malformed (struct session *sess, char *nick, char *data) +{ + EMIT_SIGNAL (XP_TE_MALFORMED, sess, nick, data, NULL, NULL, 0); +} + +int +dcc_resume (struct DCC *dcc) +{ + char tbuf[500]; + + if (dcc->dccstat == STAT_QUEUED && dcc->resumable) + { + dcc->resume_sent = 1; + /* filename contains spaces? Quote them! */ + snprintf (tbuf, sizeof (tbuf) - 10, strchr (dcc->file, ' ') ? + "DCC RESUME \"%s\" %d %"DCC_SFMT : + "DCC RESUME %s %d %"DCC_SFMT, + dcc->file, dcc->port, dcc->resumable); + + if (dcc->pasvid) + sprintf (tbuf + strlen (tbuf), " %d", dcc->pasvid); + + dcc->serv->p_ctcp (dcc->serv, dcc->nick, tbuf); + return 1; + } + + return 0; +} + +static void +dcc_confirm_send (void *ud) +{ + struct DCC *dcc = (struct DCC *) ud; + dcc_get (dcc); +} + +static void +dcc_deny_send (void *ud) +{ + struct DCC *dcc = (struct DCC *) ud; + dcc_abort (dcc->serv->front_session, dcc); +} + +static void +dcc_confirm_chat (void *ud) +{ + struct DCC *dcc = (struct DCC *) ud; + dcc_connect (dcc); +} + +static void +dcc_deny_chat (void *ud) +{ + struct DCC *dcc = (struct DCC *) ud; + dcc_abort (dcc->serv->front_session, dcc); +} + +static struct DCC * +dcc_add_chat (session *sess, char *nick, int port, guint32 addr, int pasvid) +{ + struct DCC *dcc; + + dcc = new_dcc (); + if (dcc) + { + dcc->serv = sess->server; + dcc->type = TYPE_CHATRECV; + dcc->dccstat = STAT_QUEUED; + dcc->addr = addr; + dcc->port = port; + dcc->pasvid = pasvid; + dcc->nick = strdup (nick); + dcc->starttime = time (0); + + EMIT_SIGNAL (XP_TE_DCCCHATOFFER, sess->server->front_session, nick, + NULL, NULL, NULL, 0); + + if (prefs.autoopendccchatwindow) + { + if (fe_dcc_open_chat_win (TRUE)) /* already open? add only */ + fe_dcc_add (dcc); + } else + fe_dcc_add (dcc); + + if (prefs.autodccchat == 1) + dcc_connect (dcc); + else if (prefs.autodccchat == 2) + { + char buff[128]; + snprintf (buff, sizeof (buff), "%s is offering DCC Chat. Do you want to accept?", nick); + fe_confirm (buff, dcc_confirm_chat, dcc_deny_chat, dcc); + } + } + + return dcc; +} + +static struct DCC * +dcc_add_file (session *sess, char *file, DCC_SIZE size, int port, char *nick, guint32 addr, int pasvid) +{ + struct DCC *dcc; + char tbuf[512]; + + dcc = new_dcc (); + if (dcc) + { + dcc->file = strdup (file); + + dcc->destfile = g_malloc (strlen (prefs.dccdir) + strlen (nick) + + strlen (file) + 4); + + strcpy (dcc->destfile, prefs.dccdir); + if (prefs.dccdir[strlen (prefs.dccdir) - 1] != '/') + strcat (dcc->destfile, "/"); + if (prefs.dccwithnick) + { +#ifdef WIN32 + char *t = strlen (dcc->destfile) + dcc->destfile; + strcpy (t, nick); + while (*t) + { + if (*t == '\\' || *t == '|') + *t = '_'; + t++; + } +#else + strcat (dcc->destfile, nick); +#endif + strcat (dcc->destfile, "."); + } + strcat (dcc->destfile, file); + + /* get the local filesystem encoding */ + dcc->destfile_fs = xchat_filename_from_utf8 (dcc->destfile, -1, 0, 0, 0); + + dcc->resumable = 0; + dcc->pos = 0; + dcc->serv = sess->server; + dcc->type = TYPE_RECV; + dcc->dccstat = STAT_QUEUED; + dcc->addr = addr; + dcc->port = port; + dcc->pasvid = pasvid; + dcc->size = size; + dcc->nick = strdup (nick); + dcc->maxcps = prefs.dcc_max_get_cps; + + is_resumable (dcc); + + /* autodccsend is really autodccrecv.. right? */ + + if (prefs.autodccsend == 1) + { + dcc_get (dcc); + } + else if (prefs.autodccsend == 2) + { + snprintf (tbuf, sizeof (tbuf), _("%s is offering \"%s\". Do you want to accept?"), nick, file); + fe_confirm (tbuf, dcc_confirm_send, dcc_deny_send, dcc); + } + if (prefs.autoopendccrecvwindow) + { + if (fe_dcc_open_recv_win (TRUE)) /* was already open? just add*/ + fe_dcc_add (dcc); + } else + fe_dcc_add (dcc); + } + sprintf (tbuf, "%"DCC_SFMT, size); + snprintf (tbuf + 24, 300, "%s:%d", net_ip (addr), port); + EMIT_SIGNAL (XP_TE_DCCSENDOFFER, sess->server->front_session, nick, + file, tbuf, tbuf + 24, 0); + + return dcc; +} + +void +handle_dcc (struct session *sess, char *nick, char *word[], + char *word_eol[]) +{ + char tbuf[512]; + struct DCC *dcc; + char *type = word[5]; + int port, pasvid = 0; + guint32 addr; + DCC_SIZE size; + int psend = 0; + + if (!strcasecmp (type, "CHAT")) + { + port = atoi (word[8]); + addr = strtoul (word[7], NULL, 10); + + if (port == 0) + pasvid = atoi (word[9]); + else if (word[9][0] != 0) + { + pasvid = atoi (word[9]); + psend = 1; + } + + if (!addr /*|| (port < 1024 && port != 0)*/ + || port > 0xffff || (port == 0 && pasvid == 0)) + { + dcc_malformed (sess, nick, word_eol[4] + 2); + return; + } + + if (psend) + { + dcc = find_dcc_from_id (pasvid, TYPE_CHATSEND); + if (dcc) + { + dcc->addr = addr; + dcc->port = port; + dcc_connect (dcc); + } else + { + dcc_malformed (sess, nick, word_eol[4] + 2); + } + return; + } + + dcc = find_dcc (nick, "", TYPE_CHATSEND); + if (dcc) + dcc_close (dcc, 0, TRUE); + + dcc = find_dcc (nick, "", TYPE_CHATRECV); + if (dcc) + dcc_close (dcc, 0, TRUE); + + dcc_add_chat (sess, nick, port, addr, pasvid); + return; + } + + if (!strcasecmp (type, "Resume")) + { + port = atoi (word[7]); + + if (port == 0) + { /* PASSIVE */ + pasvid = atoi(word[9]); + dcc = find_dcc_from_id(pasvid, TYPE_SEND); + } else + { + dcc = find_dcc_from_port (port, TYPE_SEND); + } + if (!dcc) + dcc = find_dcc (nick, word[6], TYPE_SEND); + if (dcc) + { + size = BIG_STR_TO_INT (word[8]); + dcc->resumable = size; + if (dcc->resumable < dcc->size) + { + dcc->pos = dcc->resumable; + dcc->ack = dcc->resumable; + lseek (dcc->fp, dcc->pos, SEEK_SET); + + /* Checking if dcc is passive and if filename contains spaces */ + if (dcc->pasvid) + snprintf (tbuf, sizeof (tbuf), strchr (file_part (dcc->file), ' ') ? + "DCC ACCEPT \"%s\" %d %"DCC_SFMT" %d" : + "DCC ACCEPT %s %d %"DCC_SFMT" %d", + file_part (dcc->file), port, dcc->resumable, dcc->pasvid); + else + snprintf (tbuf, sizeof (tbuf), strchr (file_part (dcc->file), ' ') ? + "DCC ACCEPT \"%s\" %d %"DCC_SFMT : + "DCC ACCEPT %s %d %"DCC_SFMT, + file_part (dcc->file), port, dcc->resumable); + + dcc->serv->p_ctcp (dcc->serv, dcc->nick, tbuf); + } + sprintf (tbuf, "%"DCC_SFMT, dcc->pos); + EMIT_SIGNAL (XP_TE_DCCRESUMEREQUEST, sess, nick, + file_part (dcc->file), tbuf, NULL, 0); + } + return; + } + if (!strcasecmp (type, "Accept")) + { + port = atoi (word[7]); + dcc = find_dcc_from_port (port, TYPE_RECV); + if (dcc && dcc->dccstat == STAT_QUEUED) + { + dcc_connect (dcc); + } + return; + } + if (!strcasecmp (type, "SEND")) + { + char *file = file_part (word[6]); + + port = atoi (word[8]); + addr = strtoul (word[7], NULL, 10); + size = BIG_STR_TO_INT (word[9]); + + if (port == 0) /* Passive dcc requested */ + pasvid = atoi (word[10]); + else if (word[10][0] != 0) + { + /* Requesting passive dcc. + * Destination user of an active dcc is giving his + * TRUE address/port/pasvid data. + * This information will be used later to + * establish the connection to the user. + * We can recognize this type of dcc using word[10] + * because this field is always null (no pasvid) + * in normal dcc sends. + */ + pasvid = atoi (word[10]); + psend = 1; + } + + + if (!addr || !size /*|| (port < 1024 && port != 0)*/ + || port > 0xffff || (port == 0 && pasvid == 0)) + { + dcc_malformed (sess, nick, word_eol[4] + 2); + return; + } + + if (psend) + { + /* Third Step of Passive send. + * Connecting to the destination and finally + * sending file. + */ + dcc = find_dcc_from_id (pasvid, TYPE_SEND); + if (dcc) + { + dcc->addr = addr; + dcc->port = port; + dcc_connect (dcc); + } else + { + dcc_malformed (sess, nick, word_eol[4] + 2); + } + return; + } + + dcc_add_file (sess, file, size, port, nick, addr, pasvid); + + } else + { + EMIT_SIGNAL (XP_TE_DCCGENERICOFFER, sess->server->front_session, + word_eol[4] + 2, nick, NULL, NULL, 0); + } +} + +void +dcc_show_list (struct session *sess) +{ + int i = 0; + struct DCC *dcc; + GSList *list = dcc_list; + + EMIT_SIGNAL (XP_TE_DCCHEAD, sess, NULL, NULL, NULL, NULL, 0); + while (list) + { + dcc = (struct DCC *) list->data; + i++; + PrintTextf (sess, " %s %-10.10s %-7.7s %-7"DCC_SFMT" %-7"DCC_SFMT" %s\n", + dcctypes[dcc->type], dcc->nick, + _(dccstat[dcc->dccstat].name), dcc->size, dcc->pos, + file_part (dcc->file)); + list = list->next; + } + if (!i) + PrintText (sess, _("No active DCCs\n")); +} |