/* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA * * Wayne Conrad, 3 Apr 1999: Color-coded DCC file transfer status windows * Bernhard Valenti 2000-11-21: Fixed DCC send behind nat * * 2001-03-08 Added support for getting "dcc_ip" config parameter. * Jim Seymour (jseymour@LinxNet.com) */ /* Required to make lseek use off64_t, but doesn't work on Windows */ #define _FILE_OFFSET_BITS 64 #include #include #include #include #include #include #include #define WANTSOCKET #define WANTARPA #define WANTDNS #include "inet.h" #ifdef WIN32 #include #include #else #include #endif #include "hexchat.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 "hexchatc.h" /* Setting _FILE_OFFSET_BITS to 64 doesn't change lseek to use off64_t on Windows, so override lseek to the version that does */ #ifdef WIN32 #define lseek _lseeki64 #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 gint64 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; gint64 oldcps; double timediff, startdiff; int glob_throttle_bit, wasthrottled; gint64 *cpssum; int glob_limit; goffset 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.hex_dcc_global_max_send_cps; } else { pos = dcc->pos; glob_throttle_bit = 0x2; cpssum = &dcc_getcpssum; glob_limit = prefs.hex_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 = (gint64) ((posdiff / timediff) * (timediff / startdiff) + 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; } gboolean is_dcc_completed (struct DCC *dcc) { if (dcc != NULL) return (dcc->dccstat == STAT_FAILED || dcc->dccstat == STAT_DONE || dcc->dccstat == STAT_ABORTED); return FALSE; } /* this is called from hexchat.c:hexchat_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.hex_dcc_stall_timeout > 0) { if (!dcc->throttled && tim - dcc->lasttime > prefs.hex_dcc_stall_timeout) { 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.hex_dcc_timeout) { if (prefs.hex_dcc_timeout > 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.hex_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; } g_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 = g_strdup (host); /* cppcheck-suppress memleak */ return TRUE; } return FALSE; } #define DCC_USE_PROXY() (prefs.hex_net_proxy_host[0] && prefs.hex_net_proxy_type>0 && prefs.hex_net_proxy_type<5 && prefs.hex_net_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.hex_net_proxy_host, &addr)) { closesocket (sok); return -1; } addr.sin_port = htons (prefs.hex_net_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 receive, move the */ /* completed file to the completed directory */ if(dcc->type == TYPE_RECV) { /* mgl: change this to handle the case where dccwithnick is set */ move_file (prefs.hex_dcc_dir, prefs.hex_dcc_completed_dir, file_part (dcc->destfile), prefs.hex_dcc_permissions); } } } dcc->dccstat = dccstat; if (dcc->dccchat) { g_free (dcc->dccchat); dcc->dccchat = NULL; } if (destroy) { dcc_list = g_slist_remove (dcc_list, dcc); fe_dcc_remove (dcc); g_free (dcc->proxy); g_free (dcc->file); g_free (dcc->destfile); g_free (dcc->nick); g_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->write_converter, text, len); send (dcc->sok, "\n", 1, 0); dcc->size += len; fe_dcc_update (dcc); return dcc; } return NULL; } /* 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; int ret, i; char portbuf[32]; message_tags_data no_tags = MESSAGE_TAGS_DATA_INIT; line = text_convert_invalid (line, -1, dcc->serv->read_converter, unicode_fallback_string, NULL); 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, 0); /* did the plugin close it? */ if (!g_slist_find (dcc_list, dcc)) { g_free (line); return 1; } /* did the plugin eat the event? */ if (ret) { g_free (line); return 0; } url_check_line (line); if (line[0] == 1 && !g_ascii_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, &no_tags); } else { inbound_privmsg (dcc->serv, dcc->nick, "", line, FALSE, &no_tags); } g_free (line); 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) */ g_mkdir (prefs.hex_dcc_dir, 0700); if (dcc->resumable) { gchar *filename_fs = g_filename_from_utf8(dcc->destfile, -1, NULL, NULL, NULL); dcc->fp = g_open(dcc->destfile, O_WRONLY | O_APPEND | OFLAGS, 0); g_free (filename_fs); dcc->pos = dcc->resumable; dcc->ack = dcc->resumable; } else { gchar *filename_fs; if (g_access (dcc->destfile, F_OK) == 0) { n = 0; do { n++; g_snprintf (buf, sizeof (buf), "%s.%d", dcc->destfile, n); } while (g_access (buf, F_OK) == 0); old = dcc->destfile; dcc->destfile = g_strdup (buf); EMIT_SIGNAL (XP_TE_DCCRENAME, dcc->serv->front_session, old, dcc->destfile, NULL, NULL, 0); g_free (old); } filename_fs = g_filename_from_utf8 (dcc->destfile, -1, NULL, NULL, NULL); dcc->fp = g_open (filename_fs, OFLAGS | O_TRUNC | O_WRONLY | O_CREAT, prefs.hex_dcc_permissions); g_free (filename_fs); } } 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 */ /* cppcheck-suppress deallocuse */ sprintf (buf, "%" G_GINT64_FORMAT, 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.hex_gui_autoopen_dialog) open_query (serv, nick, FALSE); } static gboolean dcc_did_connect (GIOChannel *source, GIOCondition condition, struct DCC *dcc) { int er; #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 struct sockaddr_in addr; 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; g_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.hex_dcc_fast_send; 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 = g_new0 (struct dcc_chat, 1); 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 = g_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.hex_irc_user_name, 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.hex_net_proxy_auth && prefs.hex_net_proxy_user[0] && prefs.hex_net_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.hex_net_proxy_user); len_p = strlen (prefs.hex_net_proxy_pass); proxy->buffer[0] = 1; proxy->buffer[1] = len_u; memcpy (proxy->buffer + 2, prefs.hex_net_proxy_user, len_u); proxy->buffer[2 + len_u] = len_p; memcpy (proxy->buffer + 3 + len_u, prefs.hex_net_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 = g_snprintf (buf, sizeof (buf), "CONNECT %s:%d HTTP/1.0\r\n", net_ip(dcc->addr), dcc->port); if (prefs.hex_net_proxy_auth) { n2 = g_snprintf (auth_data2, sizeof (auth_data2), "%s:%s", prefs.hex_net_proxy_user, prefs.hex_net_proxy_pass); base64_encode (auth_data, auth_data2, n2); n += g_snprintf (buf+n, sizeof (buf)-n, "Proxy-Authorization: Basic %s\r\n", auth_data); } n += g_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 = g_new0 (struct proxy_state, 1); switch (prefs.hex_net_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) g_snprintf (tbuf, sizeof (tbuf), strchr (dcc->file, ' ') ? "DCC SEND \"%s\" %u %d %" G_GUINT64_FORMAT " %d" : "DCC SEND %s %u %d %" G_GUINT64_FORMAT " %d", dcc->file, dcc->addr, dcc->port, dcc->size, dcc->pasvid); else g_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.hex_dcc_blocksize < 1) /* this is too little! */ prefs.hex_dcc_blocksize = 1024; if (prefs.hex_dcc_blocksize > 102400) /* this is too much! */ prefs.hex_dcc_blocksize = 102400; if (dcc->throttled) { fe_input_remove (dcc->wiotag); dcc->wiotag = 0; return FALSE; } if (!dcc->fastsend) { if (dcc->ack < (dcc->pos & 0xFFFFFFFF)) return TRUE; } else if (!dcc->wiotag) dcc->wiotag = fe_input_add (sok, FIA_WRITE, dcc_send_data, dcc); buf = g_malloc (prefs.hex_dcc_blocksize); lseek (dcc->fp, dcc->pos, SEEK_SET); len = read (dcc->fp, buf, prefs.hex_dcc_blocksize); if (len < 1) goto abortit; sent = send (sok, buf, len, 0); if (sent < 0 && !(would_block ())) { abortit: g_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; } } g_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 */ /* cppcheck-suppress deallocuse */ sprintf (buf, "%" G_GINT64_FORMAT, 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); } /* 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... */ 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.hex_dcc_fast_send; g_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 = g_new0 (struct dcc_chat, 1); 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.hex_dcc_ip_from_server && prefs.dcc_ip) addr = prefs.dcc_ip; else if (prefs.hex_dcc_ip[0]) { dns_query = gethostbyname ((const char *) prefs.hex_dcc_ip); 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.hex_dcc_port_first > 0) { SAddr.sin_port = 0; i = 0; while ((prefs.hex_dcc_port_last > ntohs(SAddr.sin_port)) && (bindretval == -1)) { SAddr.sin_port = htons (prefs.hex_dcc_port_first + 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 hex_dcc_ip*/ /*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 gint64 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 *filename, gint64 maxcps, int passive) { char outbuf[512]; GFileInfo *file_info; GFile *file; struct DCC *dcc; gchar *filename_fs; GFileType file_type; goffset file_size; filename = expand_homedir (filename); if (!recursive && (strchr (filename, '*') || strchr (filename, '?'))) { char path[256]; char wild[256]; safe_strcpy (wild, file_part (filename), sizeof (wild)); path_part (filename, path, sizeof (path)); if (path[0] != '/' || path[1] != '\0') path[strlen (path) - 1] = 0; /* remove trailing slash */ dccsess = sess; dccto = to; dccmaxcps = maxcps; g_free (filename); recursive = TRUE; for_files (path, wild, dcc_send_wild); recursive = FALSE; return; } dcc = new_dcc (); if (!dcc) { g_free (filename); return; } dcc->file = filename; dcc->maxcps = maxcps; filename_fs = g_filename_from_utf8 (filename, -1, NULL, NULL, NULL); if (filename_fs == NULL) { PrintTextf (sess, _("Cannot access %s\n"), dcc->file); PrintTextf (sess, "%s %d: %s\n", _("Error"), errno, errorstring (errno)); dcc_close (dcc, 0, TRUE); /* dcc_close will free dcc->file */ return; } file = g_file_new_for_path (filename_fs); if (file == NULL) { PrintTextf (sess, _("Cannot access %s\n"), dcc->file); PrintTextf (sess, "%s %d: %s\n", _("Error"), errno, errorstring (errno)); dcc_close (dcc, 0, TRUE); /* dcc_close will free dcc->file */ g_free (filename_fs); return; } file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_SIZE "," G_FILE_ATTRIBUTE_STANDARD_TYPE, G_FILE_QUERY_INFO_NONE, NULL, NULL); g_object_unref (file); if (file_info == NULL) { PrintTextf (sess, _("Cannot access %s\n"), dcc->file); PrintTextf (sess, "%s %d: %s\n", _("Error"), errno, errorstring (errno)); dcc_close (dcc, 0, TRUE); /* dcc_close will free dcc->file */ g_free (filename_fs); return; } file_type = g_file_info_get_file_type (file_info); file_size = g_file_info_get_size (file_info); g_object_unref (file_info); if (*file_part (filename) == '\0' || file_type == G_FILE_TYPE_DIRECTORY || file_size <= 0) { PrintText (sess, "Cannot send directories or empty files.\n"); dcc_close (dcc, 0, TRUE); /* dcc_close will free dcc->file */ g_free (filename_fs); return; } dcc->starttime = dcc->offertime = time (0); dcc->serv = sess->server; dcc->dccstat = STAT_QUEUED; dcc->size = file_size; dcc->type = TYPE_SEND; dcc->fp = g_open (filename_fs, OFLAGS | O_RDONLY, 0); g_free (filename_fs); if (dcc->fp == -1) { PrintText (sess, "Cannot send directories or empty files.\n"); dcc_close (dcc, 0, TRUE); /* dcc_close will free dcc->file */ return; } if (passive || dcc_listen_init (dcc, sess)) { char havespaces = 0; while (*filename) { if (*filename == ' ') { if (prefs.hex_dcc_send_fillspaces) *filename = '_'; else havespaces = 1; } filename++; } dcc->nick = g_strdup (to); if (prefs.hex_gui_autoopen_send) { 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(); g_snprintf (outbuf, sizeof (outbuf), (havespaces) ? "DCC SEND \"%s\" 199 0 %" G_GUINT64_FORMAT " %d" : "DCC SEND %s 199 0 %" G_GUINT64_FORMAT " %d", file_part (dcc->file), dcc->size, dcc->pasvid); } else { g_snprintf (outbuf, sizeof (outbuf), (havespaces) ? "DCC SEND \"%s\" %u %d %" G_GUINT64_FORMAT : "DCC SEND %s %u %d %" G_GUINT64_FORMAT, 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); } } 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 NULL; } 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 NULL; } 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 (!g_ascii_strcasecmp (file, file_part (dcc->file))) return dcc; if (!g_ascii_strcasecmp (file, dcc->file)) return dcc; } } list = list->next; } return NULL; } /* 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)) { g_free (dcc->nick); dcc->nick = g_strdup (newnick); } } list = list->next; } } /* is the destination file the same? new_dcc is not opened yet */ static gboolean is_same_file (struct DCC *dcc, struct DCC *new_dcc) { gboolean result = FALSE; gchar *filename_fs = NULL, *new_filename_fs = NULL; GFile *file = NULL, *new_file = NULL; GFileInfo *file_info = NULL, *new_file_info = NULL; char *file_id = NULL, *new_file_id = NULL; char *filesystem_id = NULL, *new_filesystem_id = NULL; /* if it's the same filename, must be same */ if (strcmp (dcc->destfile, new_dcc->destfile) == 0) { return TRUE; } filename_fs = g_filename_from_utf8 (dcc->file, -1, NULL, NULL, NULL); if (filename_fs == NULL) { goto exit; } new_filename_fs = g_filename_from_utf8 (new_dcc->file, -1, NULL, NULL, NULL); if (new_filename_fs == NULL) { goto exit; } file = g_file_new_for_path (filename_fs); if (file == NULL) { goto exit; } new_file = g_file_new_for_path (new_filename_fs); if (new_file == NULL) { goto exit; } file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_ID_FILE "," G_FILE_ATTRIBUTE_ID_FILESYSTEM, G_FILE_QUERY_INFO_NONE, NULL, NULL); if (file_info == NULL) { goto exit; } new_file_info = g_file_query_info (new_file, G_FILE_ATTRIBUTE_ID_FILE "," G_FILE_ATTRIBUTE_ID_FILESYSTEM, G_FILE_QUERY_INFO_NONE, NULL, NULL); if (new_file_info == NULL) { goto exit; } file_id = g_file_info_get_attribute_as_string (file_info, G_FILE_ATTRIBUTE_ID_FILE); new_file_id = g_file_info_get_attribute_as_string (new_file_info, G_FILE_ATTRIBUTE_ID_FILE); filesystem_id = g_file_info_get_attribute_as_string (file_info, G_FILE_ATTRIBUTE_ID_FILE); new_filesystem_id = g_file_info_get_attribute_as_string (new_file_info, G_FILE_ATTRIBUTE_ID_FILE); if (file_id != NULL && new_file_id != NULL && filesystem_id != NULL && new_filesystem_id != NULL && strcmp (file_id, new_file_id) == 0 && strcmp (filesystem_id, new_filesystem_id) == 0) { result = TRUE; } exit: g_free (filename_fs); g_free (new_filename_fs); if (file != NULL) { g_object_unref (file); } if (new_file != NULL) { g_object_unref (new_file); } if (file_info != NULL) { g_object_unref (file_info); } if (new_file_info != NULL) { g_object_unref (new_file_info); } g_free (file_id); g_free (new_file_id); g_free(filesystem_id); g_free(new_filesystem_id); return result; } static void update_is_resumable (struct DCC *dcc) { gchar *filename_fs = g_filename_from_utf8 (dcc->destfile, -1, NULL, NULL, NULL); dcc->resumable = 0; /* Check the file size */ if (filename_fs != NULL && g_access(filename_fs, W_OK) == 0) { GFile *file = g_file_new_for_path (filename_fs); if (file != NULL) { GFileInfo *file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_SIZE "," G_FILE_ATTRIBUTE_STANDARD_TYPE, G_FILE_QUERY_INFO_NONE, NULL, NULL); if (file_info != NULL) { goffset file_size_offset = g_file_info_get_size (file_info); guint64 file_size = (file_size_offset >= 0) ? (guint64) file_size_offset : 0; if (file_size < dcc->size) { dcc->resumable = file_size; dcc->pos = file_size; } else { dcc->resume_error = 2; } g_object_unref (file_info); } else { dcc->resume_errno = errno; dcc->resume_error = 1; } g_object_unref(file); } 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; } } } void dcc_get (struct DCC *dcc) { switch (dcc->dccstat) { case STAT_QUEUED: if (dcc->type != TYPE_CHATSEND) { if (dcc->type == TYPE_RECV && prefs.hex_dcc_auto_resume && 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 */ /* since destfile changed, must check resumability again */ update_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 = g_new0 (struct DCC, 1); 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 = g_strdup (nick); if (passive || dcc_listen_init (dcc, sess)) { if (prefs.hex_gui_autoopen_chat) { 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 (); g_snprintf (outbuf, sizeof (outbuf), "DCC CHAT chat 199 %d %d", dcc->port, dcc->pasvid); } else { g_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! */ g_snprintf (tbuf, sizeof (tbuf) - 10, strchr (dcc->file, ' ') ? "DCC RESUME \"%s\" %d %" G_GUINT64_FORMAT : "DCC RESUME %s %d %" G_GUINT64_FORMAT, 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 = g_strdup (nick); dcc->starttime = time (0); EMIT_SIGNAL (XP_TE_DCCCHATOFFER, sess->server->front_session, nick, NULL, NULL, NULL, 0); if (prefs.hex_gui_autoopen_chat) { if (fe_dcc_open_chat_win (TRUE)) /* already open? add only */ fe_dcc_add (dcc); } else fe_dcc_add (dcc); if (prefs.hex_dcc_auto_chat) { dcc_connect (dcc); } else { char buff[128]; g_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, guint64 size, int port, char *nick, guint32 addr, int pasvid) { struct DCC *dcc; char tbuf[512]; dcc = new_dcc (); if (dcc) { dcc->file = g_strdup (file); dcc->destfile = g_malloc (strlen (prefs.hex_dcc_dir) + strlen (nick) + strlen (file) + 4); strcpy (dcc->destfile, prefs.hex_dcc_dir); if (prefs.hex_dcc_dir[strlen (prefs.hex_dcc_dir) - 1] != G_DIR_SEPARATOR) strcat (dcc->destfile, G_DIR_SEPARATOR_S); if (prefs.hex_dcc_save_nick) { #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); 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 = g_strdup (nick); dcc->maxcps = prefs.hex_dcc_max_get_cps; update_is_resumable (dcc); if (prefs.hex_dcc_auto_recv == 1) { g_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); } else if (prefs.hex_dcc_auto_recv == 2) { dcc_get (dcc); } if (prefs.hex_gui_autoopen_recv) { if (fe_dcc_open_recv_win (TRUE)) /* was already open? just add*/ fe_dcc_add (dcc); } else fe_dcc_add (dcc); } sprintf (tbuf, "%" G_GUINT64_FORMAT, size); g_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[], const message_tags_data *tags_data) { char tbuf[512]; struct DCC *dcc; char *type = word[5]; int port, pasvid = 0; guint32 addr; guint64 size; int psend = 0; if (!g_ascii_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 (!g_ascii_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 = g_ascii_strtoull (word[8], NULL, 10); 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) g_snprintf (tbuf, sizeof (tbuf), strchr (file_part (dcc->file), ' ') ? "DCC ACCEPT \"%s\" %d %" G_GUINT64_FORMAT " %d" : "DCC ACCEPT %s %d %" G_GUINT64_FORMAT " %d", file_part (dcc->file), port, dcc->resumable, dcc->pasvid); else g_snprintf (tbuf, sizeof (tbuf), strchr (file_part (dcc->file), ' ') ? "DCC ACCEPT \"%s\" %d %" G_GUINT64_FORMAT : "DCC ACCEPT %s %d %" G_GUINT64_FORMAT, file_part (dcc->file), port, dcc->resumable); dcc->serv->p_ctcp (dcc->serv, dcc->nick, tbuf); } sprintf (tbuf, "%" G_GUINT64_FORMAT, dcc->pos); EMIT_SIGNAL_TIMESTAMP (XP_TE_DCCRESUMEREQUEST, sess, nick, file_part (dcc->file), tbuf, NULL, 0, tags_data->timestamp); } return; } if (!g_ascii_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 (!g_ascii_strcasecmp (type, "SEND")) { char *file = file_part (word[6]); port = atoi (word[8]); addr = strtoul (word[7], NULL, 10); size = g_ascii_strtoull (word[9], NULL, 10); 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_TIMESTAMP (XP_TE_DCCGENERICOFFER, sess->server->front_session, word_eol[4] + 2, nick, NULL, NULL, 0, tags_data->timestamp); } } 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" G_GUINT64_FORMAT " %-7" G_GUINT64_FORMAT " %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")); }