/* * dcc.c: Things dealing client to client connections. * * Copyright (c) 1991, 1992 Troy Rollo. * Copyright (c) 1992-1996 Matthew Green. * Copyright 1995, 2015 EPIC Software Labs * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notices, the above paragraph (the one permitting redistribution), * this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The names of the author(s) may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "irc.h" #include "list.h" #include "sedcrypt.h" #include "ctcp.h" #include "dcc.h" #include "functions.h" #include "hook.h" #include "ircaux.h" #include "lastlog.h" #include "newio.h" #include "output.h" #include "parse.h" #include "server.h" #include "status.h" #include "vars.h" #include "window.h" #include "termx.h" #include "reg.h" #include "alias.h" #include "timer.h" #define DCC_BLOCK_SIZE (1<<11) /* This should probably be configurable. */ #define DCC_RCV_BLOCK_SIZE (1<<16) /* These are the settings for ``flags'' */ #define DCC_CHAT 0x0001U #define DCC_FILEOFFER 0x0002U #define DCC_FILEREAD 0x0003U #define DCC_RAW 0x0004U #define DCC_RAW_LISTEN 0x0005U #define DCC_TYPES 0x000fU #define DCC_MY_OFFER 0x0010U #define DCC_ACTIVE 0x0020U #define DCC_THEIR_OFFER 0x0040U #define DCC_DELETE 0x0080U #define DCC_TWOCLIENTS 0x0100U #define DCC_REJECTED 0x0200U #define DCC_QUOTED 0x0400U #define DCC_CONNECTING 0x0800U #define DCC_RESUME_REQ 0x1000U #define DCC_STATES 0xfff0U static char *dcc_target (const char *name) { size_t len; char * ret; len = strlen(name); ret = new_malloc(len + 2); snprintf(ret, len + 2, "=%s", name); return ret; } typedef struct DCC_struct { unsigned flags; int family; int locked; /* XXX - Sigh */ int socket; int file; int held; int packet_size; int full_line_buffer; long refnum; intmax_t filesize; char * local_filename; char * description; char * othername; char * user; char * userhost; struct DCC_struct * next; SSu offer; /* Their offer */ SSu peer_sockaddr; /* Their saddr */ SSu local_sockaddr; /* Our saddr */ unsigned short want_port; /* HOST ORDER */ intmax_t bytes_read; /* INTMAX */ intmax_t bytes_sent; /* IM */ intmax_t bytes_acked; /* IM */ intmax_t resume_size; /* IM */ Timeval lasttime; Timeval starttime; Timeval holdtime; double heldtime; int (*open_callback) (struct DCC_struct *); int server; int updates_status; } DCC_list; static DCC_list * ClientList = NULL; static char DCC_current_transfer_buffer[256]; static int dcc_global_lock = 0; static int dccs_rejected = 0; static int dcc_refnum = 0; static int dcc_updates_status = 1; static char * default_dcc_port = NULL; #define DCC_SUBCOMMAND(x) static void x (int argc, char **argv, const char *subargs) static void dcc_chat (char *); DCC_SUBCOMMAND(dcc_chat_subcmd); static void dcc_get (char *); #ifdef MIRC_BROKEN_DCC_RESUME static void dcc_resume (char *); #endif DCC_SUBCOMMAND(dcc_get_subcmd); static void dcc_close (char *); DCC_SUBCOMMAND(dcc_close_subcmd); static void dcc_closeall (char *); DCC_SUBCOMMAND(dcc_closeall_subcmd); static void dcc_filesend (char *); static void dcc_send_raw (char *); static void dcc_list (char *args); static void dcc_rename (char *); static DCC_list * dcc_searchlist (unsigned, const char *, const char *, const char *, int); static void dcc_erase (DCC_list *); static void dcc_garbage_collect (void); static int dcc_connected (int); static int dcc_connect (DCC_list *); static int dcc_listen (DCC_list *); static void dcc_send_booster_ctcp (DCC_list *dcc); static void do_dcc (int fd); static void process_dcc_chat (DCC_list *); static void process_incoming_listen (DCC_list *); static void process_incoming_raw (DCC_list *); static void process_dcc_send (DCC_list *); static void process_incoming_file (DCC_list *); static void output_reject_ctcp (int, char *, char *); static void DCC_close_filesend (DCC_list *, const char *, const char *); static void update_transfer_buffer (DCC_list *, const char *format, ...); static char * dcc_urlencode (const char *); static char * dcc_urldecode (const char *); static void fill_in_default_port (DCC_list *dcc); #ifdef MIRC_BROKEN_DCC_RESUME static void dcc_getfile_resume_demanded (const char *, char *, char *, char *); static void dcc_getfile_resume_start (const char *, char *, char *, char *); #endif /* * These are the possible arguments to /DCC */ typedef void (*dcc_function) (char *); struct { const char * name; dcc_function function; } dcc_commands[] = { { "CHAT", dcc_chat }, /* DCC_CHAT */ { "SEND", dcc_filesend }, /* DCC_FILEOFFER */ { "GET", dcc_get }, /* DCC_FILEREAD */ { "RAW", dcc_send_raw }, /* DCC_RAW */ { "CLOSE", dcc_close }, { "CLOSEALL", dcc_closeall }, { "LIST", dcc_list }, { "RENAME", dcc_rename }, #ifdef MIRC_BROKEN_DCC_RESUME { "RESUME", dcc_resume }, #endif { NULL, (dcc_function) NULL } }; /* * These are the possible types of DCCs that can be open. */ static const char *dcc_types[] = { "", "CHAT", /* DCC_CHAT */ "SEND", /* DCC_FILEOFFER */ "GET", /* DCC_FILEREAD */ "RAW", /* DCC_RAW */ "RAW_LISTEN", NULL }; static DCC_list * get_dcc_by_filedesc (int fd) { DCC_list * dcc = NULL; for (dcc = ClientList; dcc ; dcc = dcc->next) { /* Never return deleted entries */ if (dcc->flags & DCC_DELETE) continue; if (dcc->socket == fd) break; } return dcc; } static DCC_list * get_dcc_by_refnum (int refnum) { DCC_list * dcc = NULL; for (dcc = ClientList; dcc; dcc = dcc->next) { if (dcc->flags & DCC_DELETE) continue; if (dcc->refnum == refnum) break; } return dcc; } /************************************************************************/ /* * remove_from_dcc_list: What do you think it does? */ static void dcc_remove_from_list (DCC_list *erased) { if (x_debug & DEBUG_DCC_XMIT) yell("Removing %p from dcc list", (void *)erased); if (erased == ClientList) ClientList = erased->next; else { DCC_list *prev = NULL; for (prev = ClientList; prev; prev = prev->next) if (prev->next == erased) break; if (prev) prev->next = erased->next; } } /* * We use a primitive form of cooperative reference counting to figure out * when it's ok to delete an item. Whenever anyone iterates over ClientList, * they "lock" with NULL to claim a reference on all DCC items. When they * are done, they "unlock" with NULL to release the references to all DCC * items. It is also possible to claim a reference on a particular DCC item. * All references must be released later, naturally. A DCC item is garbage * collected when it is marked as "DELETE"d, it has no reference locks, and * there are no global reference locks. */ static void dcc_garbage_collect (void) { DCC_list *dcc; int need_update = 0; if (dcc_global_lock) /* XXX Yea, yea, yea */ { if (x_debug & DEBUG_DCC_XMIT) yell("Garbage collection waiting for global lock"); return; } dcc = ClientList; while (dcc) { if ((dcc->flags & DCC_DELETE) && dcc->locked == 0) { if ((dcc->flags & DCC_TYPES) == DCC_FILEOFFER || (dcc->flags & DCC_TYPES) == DCC_FILEREAD) need_update = 1; if (x_debug & DEBUG_DCC_XMIT) yell("DCC %p being GC'd", (void *)dcc); dcc_erase(dcc); dcc = ClientList; /* Start over */ } else dcc = dcc->next; } if (need_update) update_transfer_buffer(NULL, NULL); /* Whatever */ } /* * Note that 'erased' does not neccesarily have to be on ClientList. * In fact, it may very well NOT be on ClientList. The handling at the * beginning of the function is only to sanity check that it isnt on * ClientList when we blow it away. */ static void dcc_erase (DCC_list *erased) { if (x_debug & DEBUG_DCC_XMIT) yell("DCC %p being erased", (void *)erased); dcc_remove_from_list(erased); /* * Automatically grok for REJECTs */ if (!(erased->flags & DCC_REJECTED)) { if (erased->flags & (DCC_MY_OFFER|DCC_THEIR_OFFER)) if (get_int_var(DCC_AUTO_SEND_REJECTS_VAR)) do { unsigned my_type = erased->flags & DCC_TYPES; char *dummy_ptr = NULL; char *nopath; static time_t last_reject = 0; time_t right_now; time(&right_now); if (right_now - last_reject < 2) break; /* Throttle flood attempts */ last_reject = right_now; if (erased->description && (nopath = strrchr(erased->description, '/'))) nopath++; else nopath = erased->description; malloc_sprintf(&dummy_ptr, "%s %s %s", erased->user, (my_type == DCC_FILEOFFER ? "GET" : (my_type == DCC_FILEREAD ? "SEND" : dcc_types[my_type])), nopath ? nopath : ""); erased->flags |= DCC_REJECTED; /* * And output it to the user */ if (is_server_registered(erased->server)) { if (!dead && *(erased->user) != '=') isonbase(erased->server, dummy_ptr, output_reject_ctcp); else output_reject_ctcp(erased->server, dummy_ptr, erased->user); } } while (0); } /* * In any event, blow it away. */ erased->socket = new_close(erased->socket); close(erased->file); erased->file = -1; new_free(&erased->description); /* Magic check failure here */ new_free(&erased->local_filename); new_free(&erased->user); new_free(&erased->othername); new_free(&erased->userhost); new_free((char **)&erased); if (x_debug & DEBUG_DCC_XMIT) yell("DCC erased"); } /* * close_all_dcc: We call this when we create a new process so that * we don't leave any fd's lying around, that won't close when we * want them to.. */ void close_all_dcc (void) { DCC_list *dcc; int l; dccs_rejected = 0; while ((dcc = ClientList)) dcc_erase(dcc); if (dccs_rejected) { l = message_from(NULL, LEVEL_DCC); say("Waiting for DCC REJECTs to be sent"); sleep(1); pop_message_from(l); } } /* * Place the dcc on hold. Return 1 * (fail) if it was already on hold. */ static int dcc_hold (DCC_list *dcc) { new_hold_fd(dcc->socket); if (dcc->held) return 1; else { get_time(&dcc->holdtime); dcc->held = 1; return 0; } } /* * Remove the hold. Return 1 * (fail) if it was already unheld. */ static int dcc_unhold (DCC_list *dcc) { Timeval right_now; new_unhold_fd(dcc->socket); if (!dcc->held) return 1; else { get_time(&right_now); dcc->heldtime += time_diff(dcc->holdtime, right_now); get_time(&dcc->holdtime); dcc->held = 0; return 0; } } static int lock_dcc (DCC_list *dcc) { if (x_debug & DEBUG_DCC_XMIT) yell("DCC %p being locked", (void *)dcc); if (dcc) dcc->locked++; dcc_global_lock++; return 0; } static int unlock_dcc (DCC_list *dcc) { if (x_debug & DEBUG_DCC_XMIT) yell("DCC %p being unlocked", (void *)dcc); if (dcc) dcc->locked--; dcc_global_lock--; if (dcc_global_lock == 0) dcc_garbage_collect(); /* XXX Maybe unnecessary */ return 0; } /************************************************************************/ /* * These functions handle important DCC jobs. */ static DCC_list *dcc_create ( unsigned type, /* Type of connection we want */ const char * user, /* Nick of the remote peer */ const char * description, /* * DCC Type specific information, * Usually a full pathname for * SEND/GET, "listen" or "connect" * for RAW, or NULL for others. */ const char * othername, /* Alias filename for SEND/GET */ int family, intmax_t filesize) { DCC_list *new_client; new_client = new_malloc(sizeof(DCC_list)); new_client->flags = type; new_client->family = family; new_client->locked = 0; new_client->socket = -1; new_client->file = -1; new_client->filesize = filesize; new_client->held = 0; new_client->local_filename = NULL; new_client->next = ClientList; new_client->user = malloc_strdup(user); new_client->userhost = (FromUserHost && *FromUserHost) ? malloc_strdup(FromUserHost) : malloc_strdup(unknown_userhost); new_client->description = malloc_strdup(description); new_client->othername = malloc_strdup(othername); new_client->bytes_read = 0; new_client->bytes_sent = 0; new_client->bytes_acked = 0; new_client->starttime.tv_sec = 0; new_client->starttime.tv_usec = 0; new_client->holdtime.tv_sec = 0; new_client->holdtime.tv_usec = 0; new_client->heldtime = 0.0; new_client->want_port = 0; new_client->resume_size = 0; new_client->open_callback = NULL; new_client->refnum = dcc_refnum++; new_client->server = from_server; new_client->updates_status = 1; new_client->packet_size = 0; new_client->full_line_buffer = 0; memset(&new_client->offer.ss, 0, sizeof(new_client->offer.ss)); memset(&new_client->peer_sockaddr.ss, 0, sizeof(new_client->peer_sockaddr.ss)); memset(&new_client->local_sockaddr.ss, 0, sizeof(new_client->local_sockaddr.ss)); get_time(&new_client->lasttime); if (x_debug & DEBUG_DCC_XMIT) yell("DCC %p created", (void *)new_client); ClientList = new_client; return new_client; } /* * dcc_searchlist searches through the dcc_list and finds the client * with the the flag described in type set. This function should never * return a delete'd entry. */ static DCC_list *dcc_searchlist ( unsigned type, /* What kind of connection we want */ const char * user, /* Nick of the remote peer */ const char * description, /* * DCC Type specific information, * Usually a full pathname for * SEND/GET, "listen" or "connect" * for RAW, or NULL for others. */ const char * othername, /* Alias filename for SEND/GET */ int active) /* Only get active/non-active? */ { DCC_list *client; const char *last = NULL; char *decoded_description; decoded_description = description ? dcc_urldecode(description) : NULL; if (x_debug & DEBUG_DCC_SEARCH) yell("entering dcc_sl. desc (%s) decdesc (%s) user (%s) " "type(%d) other (%s) active (%d)", description, decoded_description, user, type, othername, active); /* * Walk all of the DCC connections that we know about. */ lock_dcc(NULL); for (client = ClientList; client ; client = client->next) { /* Never return deleted entries */ if (client->flags & DCC_DELETE) continue; /* * Tell the user if they care */ if (x_debug & DEBUG_DCC_SEARCH) { yell("checking against name (%s) user (%s) type (%d) " "flag (%d) other (%s) active (%x)", client->description, client->user, client->flags & DCC_TYPES, client->flags, client->othername, client->flags & DCC_ACTIVE); } /* * Ok. first of all, it has to be the right type. * XXX - Doing (unsigned) -1 is a hack. */ if (type != (unsigned)-1 && ((client->flags & DCC_TYPES) != type)) continue; /* * Its OK if the user matches the client's user */ if (user && my_stricmp(user, client->user)) continue; /* * If "name" is NULL, then that acts as a wildcard. * If "description" is NULL, then that also acts as a wildcard. * If "name" is not the same as "description", then it could * be that "description" is a filename. Check to see if * "name" is the last component in "description" and * accept that. * Otherwise, "othername" must exist and be the same. * In all other cases, reject this entry. */ if (description && client->description && my_stricmp(description, client->description) && my_stricmp(decoded_description, client->description)) { /* * OK. well, 'name' isnt 'description', try looking * for a last segment. If there isnt one, choke. */ if ((last = strrchr(client->description, '/')) == NULL) continue; /* * If 'name' isnt 'last' then we'll have to look at * 'othername' to see if that matches. */ if (my_stricmp(description, last + 1) && my_stricmp(decoded_description, last + 1)) { if (!othername || !client->othername) continue; if (my_stricmp(othername, client->othername)) continue; } } /* * Active == 0 -> Only PENDING unopened connections, please * No deleted entries, either. */ if (active == 0) { if (client->flags & DCC_ACTIVE) continue; } /* * Active == 1 -> Only ACTIVE and OPEN connections, please */ else if (active == 1) { if ((client->flags & DCC_ACTIVE) == 0) continue; } /* * Active == -1 -> Only NON DELETED entries, please. */ else if (active == -1) (void) 0; if (x_debug & DEBUG_DCC_SEARCH) yell("We have a winner!"); /* Looks like we have a winner! */ unlock_dcc(NULL); return client; } new_free(&decoded_description); unlock_dcc(NULL); return NULL; } /* * dcc_get_bucket searches through the dcc_list and collects the clients * we can do dcc get on. This function should never return a delete'd entry. */ static int dcc_get_bucket (Bucket *b, const char *user, const char *fname) { DCC_list *client; const char *last = NULL; char *decoded_description; int count = 0; decoded_description = fname ? dcc_urldecode(fname) : NULL; if (x_debug & DEBUG_DCC_SEARCH) yell("entering dcc_g_b. desc (%s) decdesc (%s) user (%s) ", fname, decoded_description, user); /* * Walk all of the DCC connections that we know about. */ lock_dcc(NULL); for (client = ClientList; client ; client = client->next) { /* Skip deleted entries */ if (client->flags & DCC_DELETE) continue; /* Skip already ACTIVE connections */ if (client->flags & DCC_ACTIVE) continue; /* Skip non-DCC GETs */ if ((client->flags & DCC_TYPES) != DCC_FILEREAD) continue; /* Skip DCC GETs with no filename (!!!) */ if (!client->description) continue; /* Skip DCCs from other people */ if (user && my_stricmp(user, client->user)) continue; last = strrchr(client->description, '/'); /* * If "fname" is NULL, then that also acts as a wildcard. * If "fname" is not the same as "description", then it could * be that "description" is a filename. Check to see if * "fname" is the last component in "description" and * accept that. * In all other cases, reject this entry. */ #define ACCEPT { if (b) \ add_to_bucket(b, empty_string, client); \ count++; \ continue; } #define CHECKVAL(x) \ if (! x ) \ ACCEPT \ else if (!my_stricmp( x , client->description)) \ ACCEPT \ else if (last && !my_stricmp( x , last + 1)) \ ACCEPT CHECKVAL(fname) CHECKVAL(decoded_description) } new_free(&decoded_description); unlock_dcc(NULL); return count; } /* * Added by Chaos: Is used in edit.c for checking redirect. */ int dcc_chat_active (const char *user) { int retval; int l; l = message_from(user, LEVEL_DCC); retval = dcc_searchlist(DCC_CHAT, user, NULL, NULL, 1) ? 1 : 0; pop_message_from(l); return retval; } /************************************************************************/ static int dcc_get_connect_addrs (DCC_list *dcc) { ssize_t c; const char * type; int retval; type = dcc_types[dcc->flags & DCC_TYPES]; #define DGETS(x, y) dgets( x , (char *) & y , sizeof y , -1); /* * */ /* This is the errno value from getsockopt() */ c = DGETS(dcc->socket, retval) if (c < (ssize_t)sizeof(retval) || retval) goto something_broke; /* This is the socket error returned by getsockopt() */ c = DGETS(dcc->socket, retval) if (c < (ssize_t)sizeof(retval) || retval) goto something_broke; /* * */ /* This is the errno value from getsockname() */ c = DGETS(dcc->socket, retval) if (c < (ssize_t)sizeof(retval) || retval) goto something_broke; /* This is the address returned by getsockname() */ c = DGETS(dcc->socket, dcc->local_sockaddr.ss) if (c < (ssize_t)sizeof(dcc->local_sockaddr.ss)) goto something_broke; /* * */ /* This is the errno value from getpeername() */ c = DGETS(dcc->socket, retval) if (c < (ssize_t)sizeof(retval) || retval) goto something_broke; /* This is the address returned by getpeername() */ c = DGETS(dcc->socket, dcc->peer_sockaddr.ss) if (c < (ssize_t)sizeof(dcc->peer_sockaddr.ss)) goto something_broke; return 0; something_broke: { int l = message_from(dcc->user, LEVEL_DCC); say("DCC %s connection with %s could not be established: %s", type, dcc->user, retval ? strerror(retval) : "(internal error)"); dcc->flags |= DCC_DELETE; pop_message_from(l); return -1; } } /* * This is called when a client connection completes (successfully). * If the connection failed, we will forcibly eject the dcc. */ static int dcc_connected (int fd) { DCC_list * dcc; const char * type; int jvs_blah; if (!(dcc = get_dcc_by_filedesc(fd))) return -1; /* Don't want it */ type = dcc_types[dcc->flags & DCC_TYPES]; /* * Set up the connection to be useful */ new_open(dcc->socket, do_dcc, NEWIO_RECV, 1, dcc->server); dcc->flags &= ~DCC_THEIR_OFFER; dcc->flags |= DCC_ACTIVE; /* * If this was a two-peer connection, then tell the user * that the connection was successfull. */ lock_dcc(dcc); if ((dcc->flags & DCC_TYPES) != DCC_RAW && (dcc->flags & DCC_TYPES) != DCC_RAW_LISTEN) { char p_addr[256]; char p_port[24]; char * encoded_description; int l; l = message_from(dcc->user, LEVEL_DCC); if (inet_ntostr(&dcc->peer_sockaddr, p_addr, 256, p_port, 24, NI_NUMERICHOST)) { yell("dcc_connected: Couldn't convert socket address to p-addr/host for %s %s", type, dcc->user); strlcpy(p_addr, "unknown addr", sizeof(p_addr)); strlcpy(p_port, "???", sizeof(p_port)); } encoded_description = dcc_urlencode(dcc->description); /* * It would be nice if SEND also showed the filename * and size (since it's possible to have multiple * SEND requests queued), so we check for a file size * first. * Actually, that was wrong. We just check the type. * so that it doesnt choke on zero-length files. */ if (!strcmp(type, "SEND")) { if ((jvs_blah = do_hook(DCC_CONNECT_LIST, "%s %s %s %s %s " INTMAX_FORMAT, dcc->user, type, p_addr, p_port, encoded_description, dcc->filesize))) /* * Compatability with bitchx */ jvs_blah = do_hook(DCC_CONNECT_LIST, "%s GET %s %s %s " INTMAX_FORMAT, dcc->user, p_addr, p_port, encoded_description, dcc->filesize); } else { jvs_blah = do_hook(DCC_CONNECT_LIST, "%s %s %s %s", dcc->user, type, p_addr, p_port); } if (jvs_blah) { say("DCC %s connection with %s[%s:%s] established", type, dcc->user, p_addr, p_port); } new_free(&encoded_description); pop_message_from(l); } unlock_dcc(dcc); /* * Record the time the connection was started, * Clean up and then return. */ get_time(&dcc->starttime); get_time(&dcc->lasttime); if (dcc->open_callback) dcc->open_callback(dcc); return 0; /* Going to keep it. */ } static int do_expire_dcc_connects (void *stuff) { int old_server = from_server; int seconds; DCC_list *dcc; Timeval right_now; int l; /* * Initialize our idea of what is going on. */ if (from_server == NOSERV) from_server = get_window_server(0); get_time(&right_now); if ((seconds = get_int_var(DCC_CONNECT_TIMEOUT_VAR)) == 0) return 0; /* Do not time out if == 0 */ lock_dcc(NULL); for (dcc = ClientList ; dcc != NULL ; dcc = dcc->next) { if (!(dcc->flags & DCC_CONNECTING)) continue; if (time_diff(dcc->lasttime, right_now) >= seconds) { unsigned my_type = dcc->flags & DCC_TYPES; char *encoded_description; encoded_description = dcc_urlencode(dcc->description); l = message_from(dcc->user, LEVEL_DCC); if (do_hook(DCC_LOST_LIST,"%s %s %s CONNECT TIMED OUT", dcc->user, dcc_types[my_type], encoded_description ? encoded_description : "")) say("DCC %s:%s to %s -- connection timed out", dcc_types[my_type], dcc->description ? dcc->description : "", dcc->user); dcc->flags |= DCC_DELETE; pop_message_from(l); } } unlock_dcc(NULL); dcc_garbage_collect(); from_server = old_server; return 0; } /* * Whenever a DCC changes state from WAITING->ACTIVE, it calls this function * to initiate the internet connection for the transaction. */ static int dcc_connect (DCC_list *dcc) { int old_server = from_server; int retval = 0; SSu local; socklen_t locallen; int seconds; /* * Initialize our idea of what is going on. */ if (from_server == NOSERV) from_server = get_window_server(0); lock_dcc(dcc); do { if (!(dcc->flags & DCC_THEIR_OFFER)) { int l = message_from(dcc->user, LEVEL_DCC); say("Can't connect on a dcc that was not offered [%s]", dcc->user); dcc->flags |= DCC_DELETE; retval = -1; pop_message_from(l); break; } /* inet_vhostsockaddr doesn't usually return an error */ if (inet_vhostsockaddr(family(&dcc->offer), -1, NULL, &local, &locallen) < 0) { int l = message_from(dcc->user, LEVEL_DCC); say("Can't figure out your virtual host. " "Use /HOSTNAME to reset it and try again."); retval = -1; pop_message_from(l); break; } dcc->socket = client_connect(&local, locallen, &dcc->offer, sizeof(dcc->offer.ss)); if (dcc->socket < 0) { char *encoded_description = dcc_urlencode(dcc->description); int l = message_from(dcc->user, LEVEL_DCC); /* XXX Error message may need to be tuned here. */ if (do_hook(DCC_LOST_LIST,"%s %s %s ERROR", dcc->user, dcc_types[dcc->flags&DCC_TYPES], encoded_description ? encoded_description : "")) say("Unable to create connection: (%d)", dcc->socket); pop_message_from(l); if (encoded_description) new_free(&encoded_description); dcc->flags |= DCC_DELETE; retval = -1; break; } dcc->flags |= DCC_CONNECTING; new_open(dcc->socket, do_dcc, NEWIO_CONNECT, 0, dcc->server); if ((seconds = get_int_var(DCC_CONNECT_TIMEOUT_VAR)) > 0) { /* int l = message_from(dcc->user, LEVEL_DCC); say("A non-blocking connect() for your DCC has been initiated." " It could take a while to complete." " I'll check on it in %d seconds", seconds); pop_message_from(l); */ add_timer(0, empty_string, seconds, 1, do_expire_dcc_connects, NULL, NULL, GENERAL_TIMER, -1, 0, 0); } from_server = old_server; get_time(&dcc->lasttime); break; } while (0); unlock_dcc(dcc); from_server = old_server; return retval; } /* * Make an offer to another peer that they can't refuse. */ static int dcc_listen (DCC_list *dcc) { int old_server = from_server; char p_port[12]; int retval = 0; int l; /* * Initialize our idea of what is going on. */ if (from_server == NOSERV) from_server = get_window_server(0); l = message_from(dcc->user, LEVEL_DCC); do { if (dcc->flags & DCC_THEIR_OFFER) { dcc->flags |= DCC_DELETE; say("Mixup: dcc_offer on a remote offer [%d]", dcc->socket); retval = -1; break; } /* * Mark that we're waiting for the remote peer to answer, * and then open up a listen()ing socket for them. If our * first choice of port fails, try another one. If both * fail, then we give up. If the user insists on doing * random ports, then we will fallback to asking the system * for a port if our random port isnt available. */ dcc->flags |= DCC_MY_OFFER; while ((dcc->socket = ip_bindery(dcc->family, dcc->want_port, &dcc->local_sockaddr)) < 0) { /* XXX Maybe this shouldn't be done for $listen()s. */ char *encoded_description = NULL; int original_port = dcc->want_port; fill_in_default_port(dcc); encoded_description = dcc_urlencode(dcc->description); do_hook(DCC_LOST_LIST,"%s %s %s %ld %d PORT IN USE", dcc->user, dcc_types[dcc->flags & DCC_TYPES], encoded_description, dcc->refnum, original_port); new_free(&encoded_description); if (dcc->want_port == original_port) { dcc->flags |= DCC_DELETE; say("Unable to create connection on fd [%d] " "binding local port [%d] for inbound connection.", dcc->socket, dcc->want_port); retval = -1; goto dcc_listen_bail; } } #ifdef MIRC_BROKEN_DCC_RESUME /* * For stupid MIRC dcc resumes, we need to stash the * local port number, because the remote client will send * back that port number as its ID of what file it wants * to resume (rather than the filename. ick.) */ inet_ntostr(&dcc->local_sockaddr, NULL, 0, p_port, 12, 0); malloc_strcpy(&dcc->othername, p_port); #endif new_open(dcc->socket, do_dcc, NEWIO_ACCEPT, 1, dcc->server); /* * If this is to be a 2-peer connection, then we need to * send the remote peer a CTCP request. I suppose we should * do an ISON request first, but thats another project. */ if (dcc->flags & DCC_TWOCLIENTS) dcc_send_booster_ctcp(dcc); get_time(&dcc->lasttime); } while (0); dcc_listen_bail: pop_message_from(l); from_server = old_server; return retval; } /* * send_booster_ctcp: This is called by dcc_open and also by dcc_filesend * to send a CTCP handshake message to a remote peer. The reason its a * function is because its a rather large chunk of code, and it needs to be * done basically identically by both places. Whatever. * * XXX This function is not really protocol independant. */ static void dcc_send_booster_ctcp (DCC_list *dcc) { SSu my_sockaddr; char p_host[128]; char p_port[24]; char * nopath; const char * type = dcc_types[dcc->flags & DCC_TYPES]; int family_; int server = from_server < 0 ? primary_server : from_server; int l; if (!is_server_registered(server)) { int l = message_from(dcc->user, LEVEL_DCC); yell("You cannot use DCC while not connected to a server."); pop_message_from(l); return; } family_ = family(&dcc->local_sockaddr); /* * Use the external gateway address (visible to the server) if the * user wants us to use that. If the user is NOT using a vhost, * then use the address we have for ourselves connecting to the * server. If the user IS using a vhost, then obviously use that. */ if (get_int_var(DCC_USE_GATEWAY_ADDR_VAR)) my_sockaddr = get_server_uh_addr(server); else if (family_ == AF_INET && dcc->local_sockaddr.si.sin_addr.s_addr == htonl(INADDR_ANY)) my_sockaddr = get_server_local_addr(server); else if (family_ == AF_INET6 && memcmp(&dcc->local_sockaddr.si6.sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0) my_sockaddr = get_server_local_addr(server); else my_sockaddr = dcc->local_sockaddr; /* * If the family the user asked for is not the family of the address * we want to put in the handshake, something is very wrong. Tell * the user about it and give up. */ if (family_ != family(&my_sockaddr)) { int l = message_from(dcc->user, LEVEL_DCC); if (get_int_var(DCC_USE_GATEWAY_ADDR_VAR)) /* FROM_MESSAGE */ yell("When using /SET DCC_USE_GATEWAY_ADDR ON, I can only " "support DCC in the same address family (IPv4 or IPv6) " "as you are using to connect to the server."); else if (family_ == AF_INET) yell("I do not know what your IPv4 address is. You can tell " "me your IPv4 hostname with /HOSTNAME and retry the /DCC"); else if (family_ == AF_INET6) yell("I do not know what your IPv6 address is. You can tell " "me your IPv6 hostname with /HOSTNAME and retry the /DCC"); else yell("I do not know what your address is because you asked " "me for an address family that I don't support."); dcc->flags |= DCC_DELETE; pop_message_from(l); return; } if (family_ == AF_INET) my_sockaddr.si.sin_port = dcc->local_sockaddr.si.sin_port; else if (family_ == AF_INET6) my_sockaddr.si6.sin6_port = dcc->local_sockaddr.si6.sin6_port; else { int l = message_from(dcc->user, LEVEL_DCC); yell("Could not send a CTCP handshake becuase the address family is not supported."); dcc->flags |= DCC_DELETE; pop_message_from(l); return; } if (inet_ntostr(&my_sockaddr, p_host, 128, p_port, 24, GNI_INTEGER | NI_NUMERICHOST)) { int l = message_from(dcc->user, LEVEL_DCC); yell("dcc_send_booster_ctcp: I couldn't figure out your hostname (or the port you tried to use was invalid). I have to delete this DCC"); if (get_int_var(DCC_USE_GATEWAY_ADDR_VAR)) /* FROM_MESSAGE */ yell("dcc_send_booster_ctcp: You have /SET DCC_USE_GATEWAY_ADDR ON, which uses the hostname the irc server shows to the network. If you're using a fake hostname, that would cause this problem. Use /SET DCC_USE_GATEWAY_ADDR OFF if you have a fake hostname on irc (but that still won't work behind a NAT router"); dcc->flags |= DCC_DELETE; pop_message_from(l); return; } /* * If this is to be a 2-peer connection, then we need to * send the remote peer a CTCP request. I suppose we should * do an ISON request first, but thats another project. */ if (!(dcc->flags & DCC_TWOCLIENTS)) return; get_time(&dcc->starttime); get_time(&dcc->lasttime); if (((dcc->flags & DCC_TYPES) == DCC_FILEOFFER) && (nopath = strrchr(dcc->description, '/'))) nopath++; else nopath = dcc->description; l = message_from(dcc->user, LEVEL_DCC); /* * If this is a DCC SEND... */ lock_dcc(dcc); if ((dcc->flags & DCC_TYPES) == DCC_FILEOFFER) { char * url_name = dcc_urlencode(nopath); /* * Dont bother with the checksum. */ send_ctcp(1, dcc->user, "DCC", "%s %s %s %s "INTMAX_FORMAT, type, url_name, p_host, p_port, dcc->filesize); /* * Tell the user we sent out the request */ if (do_hook(DCC_OFFER_LIST, "%s %s %s "INTMAX_FORMAT, dcc->user, type, url_name, dcc->filesize)) say("Sent DCC %s request (%s "INTMAX_FORMAT") to %s", type, nopath, dcc->filesize, dcc->user); new_free(&url_name); } /* * Otherwise its a DCC CHAT request */ else { /* * Send out the handshake */ send_ctcp(1, dcc->user, "DCC", "%s %s %s %s", type, nopath, p_host, p_port); /* * And tell the user */ if (do_hook(DCC_OFFER_LIST, "%s %s", dcc->user, type)) say("Sent DCC %s request to %s", type, dcc->user); } unlock_dcc(dcc); pop_message_from(l); } /************************************************************************/ /* * This allows you to send (via /msg or /query) a message to a remote * dcc target. The two types of targets you may send to are a DCC CHAT * connection (you specify a nickname) or a DCC RAW connection (you specify * the file descriptor). Of course, since DCC RAW connections use digits * and DCC CHATs dont, sending a message to = then is presumed to * be a message to a DCC RAW. * * If ``noisy'' is 1, then we tell the user that we send the message, and * allow them to hook it. If ``noisy'' is 0, then we're doing this silently * (as in /redirect, or /ctcp) and we dont want to tell the user about it. * * XXXX This needs to be broken up into CHAT and RAW cases. */ static void dcc_message_transmit ( int type, /* What type of DCC to send over */ char *user, /* Who to send it to */ char *desc, /* THe 'desc' field in DCC_List */ char *text, /* What to send them */ const char *text_display, /* What to tell the user we sent */ int noisy, /* Do we tell the user? */ const char *cmd) /* * For /CTCP's, is this a PRIVMSG * or a NOTICE? */ { DCC_list *dcc; char tmp[DCC_BLOCK_SIZE+1]; char thing = '\0'; int list = 0; int old_from_server = from_server; int writeval; int l; tmp[0] = 0; switch (type) { case DCC_CHAT: { thing = '='; list = SEND_DCC_CHAT_LIST; break; } case DCC_RAW: { noisy = 0; break; } } if (!(dcc = dcc_searchlist(type, user, desc, NULL, -1))) { l = message_from(user, LEVEL_DCC); say("No active DCC %s:%s connection for %s", dcc_types[type], desc, user); pop_message_from(l); return; } l = message_from(dcc->user, LEVEL_DCC); /* * Check for CTCPs... whee. * Dont tag outbound ACTIONs, which break mirc. Blah. */ if (type == DCC_CHAT && *text == CTCP_DELIM_CHAR && strncmp(text + 1, "ACTION", 6)) { if (!cmd || !strcmp(cmd, "PRIVMSG")) strlcpy(tmp, "CTCP_MESSAGE ", sizeof tmp); else strlcpy(tmp, "CTCP_REPLY ", sizeof tmp); } if (x_debug & DEBUG_OUTBOUND) yell("-> [%s] [%s]", desc, text); if (dcc->flags & DCC_QUOTED) { char * dest; size_t destlen; if (!(dest = transform_string_dyn("-CTCP", text, 0, &destlen))) { yell("DMSG: Could not CTCP-dequote [%s]", text); dest = malloc_strdup(text); } writeval = write(dcc->socket, dest, destlen); new_free(&dest); } else { strlcat(tmp, text, sizeof tmp); strlcat(tmp, "\n", sizeof tmp); writeval = write(dcc->socket, tmp, strlen(tmp)); } if (writeval == -1) { dcc->flags |= DCC_DELETE; say("Outbound write() failed: %s", strerror(errno)); from_server = old_from_server; pop_message_from(l); return; } dcc->bytes_sent += writeval; if (noisy) { lock_dcc(dcc); if (do_hook(list, "%s %s", dcc->user, text_display)) put_it("=> %c%s%c %s", thing, dcc->user, thing, text_display); unlock_dcc(dcc); } get_time(&dcc->lasttime); pop_message_from(l); return; } /* * This is used to send a message to a remote DCC peer. This is called * by send_text. */ void dcc_chat_transmit (char *user, char *text, const char *orig, const char *type, int noisy) { int fd; int l = -1; char * target = NULL; do { /* * This converts a message being sent to a number into whatever * its local port is (which is what we think the nickname is). * Its just a 15 minute hack. dont read too much into it. */ if (is_number(user) && (fd = atol(user))) { DCC_list * dcc; if (!(dcc = get_dcc_by_filedesc(fd))) { l = message_from(NULL, LEVEL_DCC); put_it("Descriptor %d is not an open DCC RAW", fd); break; } target = dcc_target(dcc->user); l = message_from(target, LEVEL_DCC); dcc_message_transmit(DCC_RAW, dcc->user, dcc->description, text, orig, noisy, type); get_time(&dcc->lasttime); } else { target = dcc_target(user); l = message_from(target, LEVEL_DCC); dcc_message_transmit(DCC_CHAT, user, NULL, text, orig, noisy, type); } } while (0); dcc_garbage_collect(); pop_message_from(l); new_free(&target); } /* * * All these functions are user-generated -- that is, they are all called * when the user does /DCC or one of the DCC-oriented built in * functions. * */ /* * The /DCC command. Delegates the work to other functions. */ BUILT_IN_COMMAND(dcc_cmd) { const char *cmd; int i, l; if (!(cmd = next_arg(args, &args))) cmd = "LIST"; for (i = 0; dcc_commands[i].name != NULL; i++) { if (!my_stricmp(dcc_commands[i].name, cmd)) { l = message_from(NULL, LEVEL_DCC); lock_dcc(NULL); dcc_commands[i].function(args); unlock_dcc(NULL); pop_message_from(l); dcc_garbage_collect(); return; } } l = message_from(NULL, LEVEL_DCC); say("Unknown DCC command: %s", cmd); pop_message_from(l); } /* * Usage: /DCC CHAT [-p port]|[-6]|[-4] */ static void dcc_chat (char *args) { int argc; char * argv[10]; argc = split_args(args, argv, 10); dcc_chat_subcmd(argc, argv, NULL); } DCC_SUBCOMMAND(dcc_chat_subcmd) { char *user = NULL; DCC_list *dcc; unsigned short portnum = 0; /* Any port by default */ int family = AF_INET; /* IPv4 by default */ int i; int l; if (argc == 0) { l = message_from(NULL, LEVEL_DCC); say("Usage: /DCC CHAT [-p port]|[-6]|[-4]"); pop_message_from(l); return; } for (i = 0; i < argc; i++) { if (!strcmp(argv[i], "-4")) family = AF_INET; else if (!strcmp(argv[i], "-6")) family = AF_INET6; else if (!strcmp(argv[i], "-p")) { if (i + 1 == argc) say("DCC CHAT: Argument to -p missing -- ignored"); else if (!is_number(argv[i + 1])) say("DCC CHAT: Argument to -p non-numeric -- ignored"); else { portnum = my_atol(argv[i + 1]); i++; } } else if (*argv[i] == '-') say("DCC CHAT: Option %s not supported", argv[i]); else if (user) { say("DCC CHAT: Opening multiple chats per command not " "supported yet -- ignoring extra nick %s", argv[i]); } else malloc_strcpy(&user, argv[i]); } if ((dcc = dcc_searchlist(DCC_CHAT, user, NULL, NULL, -1))) { if ((dcc->flags & DCC_ACTIVE) || (dcc->flags & DCC_MY_OFFER)) { l = message_from(dcc->user, LEVEL_DCC); say("Sending a booster CTCP handshake for an existing DCC CHAT to %s", user); dcc_send_booster_ctcp(dcc); pop_message_from(l); return; } } else dcc = dcc_create(DCC_CHAT, user, "chat", NULL, family, 0); l = message_from(dcc->user, LEVEL_DCC); dcc->flags |= DCC_TWOCLIENTS; dcc->want_port = portnum; fill_in_default_port(dcc); if (dcc->flags & DCC_THEIR_OFFER) dcc_connect(dcc); else dcc_listen(dcc); pop_message_from(l); } /* * Usage: /DCC CLOSE [] */ static void dcc_close (char *args) { int argc; char * argv[10]; argc = split_args(args, argv, 10); dcc_close_subcmd(argc, argv, NULL); } DCC_SUBCOMMAND(dcc_close_subcmd) { DCC_list *dcc; int type; char *user = NULL, *file = NULL; int count = 0; if (argc < 2) { /* MESSAGE_FROM */ say("Usage: /DCC CLOSE []"); return; } if ((!my_stricmp(argv[0], "-all") || !my_stricmp(argv[0], "*"))) type = -1; else { int i; for (i = 0; dcc_types[i]; i++) if (!my_stricmp(argv[0], dcc_types[i])) break; if (!dcc_types[i]) { /* MESSAGE_FROM */ say("DCC CLOSE: Unknown DCC type: %s", argv[0]); return; } type = i; } if ((!my_stricmp(argv[1], "-all") || !my_stricmp(argv[1], "*"))) user = NULL; else user = argv[1]; if (argc >= 3) file = argv[2]; while ((dcc = dcc_searchlist(type, user, file, file, -1))) { char * encoded_description = NULL; unsigned my_type = dcc->flags & DCC_TYPES; count++; lock_dcc(dcc); if (dcc->description) { if (my_type == DCC_FILEOFFER) encoded_description = dcc_urlencode(dcc->description); else /* assume the other end encoded the filename */ encoded_description = malloc_strdup(dcc->description); } if (do_hook(DCC_LOST_LIST,"%s %s %s USER ABORTED CONNECTION", dcc->user, dcc_types[my_type], encoded_description ? encoded_description : "")) /* MESSAGE_FROM */ say("DCC %s:%s to %s closed", dcc_types[my_type], file ? file : "", dcc->user); if (encoded_description) new_free(&encoded_description); dcc->flags |= DCC_DELETE; unlock_dcc(dcc); } if (!count) /* MESSAGE_FROM */ say("No DCC %s:%s to %s found", (type == -1 ? "" : dcc_types[type]), (file ? file : ""), user); } /* * Usage: /DCC CLOSEALL * It leaves your DCC list very empty */ static void dcc_closeall (char *args) { /* Just a dummy placeholder */ dcc_closeall_subcmd(0, NULL, NULL); } DCC_SUBCOMMAND(dcc_closeall_subcmd) { char * my_argv[3]; my_argv[0] = LOCAL_COPY("-all"); my_argv[1] = LOCAL_COPY("-all"); dcc_close_subcmd(2, my_argv, NULL); } /* * Usage: /DCC GET [file|*] * The '*' file gets all offered files. */ static void dcc_get (char *args) { int argc; char * argv[10]; argv[0] = LOCAL_COPY("GET"); argc = split_args(args, &argv[1], 9); dcc_get_subcmd(argc + 1, argv, NULL); } #ifdef MIRC_BROKEN_DCC_RESUME /* * Usage: /DCC RESUME [file|*] * The '*' file gets all offered files. */ static void dcc_resume (char *args) { int argc; char * argv[10]; argv[0] = LOCAL_COPY("RESUME"); argc = split_args(args, &argv[1], 9); dcc_get_subcmd(argc + 1, argv, NULL); } #endif static void handle_invalid_savedir (const char *pathname) { /* MESSAGE_FROM */ say("DCC GET: Can't save file because %s is not a valid directory.", pathname); say("DCC GET: Check /SET DCC_STORE_PATH and try again."); } DCC_SUBCOMMAND(dcc_get_subcmd) { char *user; char *filename = NULL; DCC_list *dcc; Filename default_savedir = ""; Filename fullname = ""; Filename pathname = ""; int savedir_is_invalid = 0; int file; char *realfilename = NULL; int count = 0, i, j; Stat sb; int proto; const char * x = NULL; int resume; Bucket * b; if (argc < 2) { /* MESSAGE_FROM */ say("You must supply a nickname for DCC GET"); return; } /* Figure out if this is DCC GET or DCC RESUME */ if (!strcmp(argv[0], "RESUME")) resume = 1; else resume = 0; /* Figure out whose offer we will accept */ user = argv[1]; lock_dcc(NULL); /* Handle directory as the last argument */ if (!normalize_filename(argv[argc-1], pathname) && isdir(pathname)) { /* Pretend the user did /set dcc_store_path argv[argc-1] */ x = pathname; argv[argc-1] = NULL; argc--; } /* Handle a straight rename */ else if (argc == 4 && dcc_get_bucket(NULL, user, argv[2]) == 1 && dcc_get_bucket(NULL, user, argv[3]) == 0) { /* Pretend the user did /dcc rename get argv[2] argv[3] */ if (!(b = new_bucket())) { /* FROM_MESSAGE */ yell("DCC GET: new_bucket() failed. [1] help!"); return; } dcc_get_bucket(b, user, argv[2]); dcc = b->list[0].stuff; malloc_strcpy(&dcc->description, argv[3]); free_bucket(&b); /* Pretend the user did /dcc get argv[3] */ argv[2] = argv[3]; argc = 3; } /* Calculate "default_savedir" */ if (!x) x = get_string_var(DCC_STORE_PATH_VAR); if (!x || !*x) x = "./"; if (normalize_filename(x, default_savedir)) savedir_is_invalid = 1; if (argc == 2) { filename = NULL; j = 4; goto jumpstart_get; } for (j = 2; j < argc; j++) { filename = argv[j]; jumpstart_get: if (!(b = new_bucket())) { /* FROM_MESSAGE */ yell("DCC GET: new_bucket() failed. [2] Help!"); return; } count = dcc_get_bucket(b, user, filename); for (i = 0; i < count; i++) { /* Skip any null pointers. sigh. */ if (!(dcc = b->list[i].stuff)) continue; lock_dcc(dcc); /* * Figure out where we will save this file. If the user has * /DCC RENAMEd this file to an absolute path, we honor that. * Otherwise, we use the default directory from above * (09/24/2003) */ realfilename = dcc_urldecode(dcc->description); if (*realfilename == '/') strlcpy(fullname, realfilename, sizeof(fullname)); else { if (savedir_is_invalid == 1) { handle_invalid_savedir(x); savedir_is_invalid++; new_free(&realfilename); unlock_dcc(dcc); continue; } strlcpy(fullname, default_savedir, sizeof(fullname)); strlcat(fullname, "/", sizeof(fullname)); strlcat(fullname, realfilename, sizeof(fullname)); } new_free(&realfilename); dcc->local_filename = malloc_strdup(fullname); dcc->open_callback = NULL; #ifdef MIRC_BROKEN_DCC_RESUME if (resume && get_int_var(MIRC_BROKEN_DCC_RESUME_VAR) && stat(fullname, &sb) != -1) { dcc->bytes_sent = 0; dcc->bytes_read = dcc->resume_size = sb.st_size; if ((file = open(fullname, O_WRONLY|O_APPEND, 0644)) == -1) { /* MESSAGE_FROM */ say("Unable to open %s: %s", fullname, strerror(errno)); unlock_dcc(dcc); continue; } dcc->file = file; dcc->flags |= DCC_RESUME_REQ; if (family(&dcc->offer) == AF_INET) malloc_strcpy(&dcc->othername, ltoa(ntohs(dcc->offer.si.sin_port))); if (x_debug & DEBUG_DCC_XMIT) /* FROM_MESSAGE */ yell("SENDING DCC RESUME to [%s] [%s|%s|%ld]", user, filename, dcc->othername, (long)sb.st_size); /* Just in case we have to fool the protocol enforcement. */ if ((proto = get_server_protocol_state(from_server)) >= 0) set_server_protocol_state(from_server, 0); send_ctcp(1, user, "DCC", #if 1 strchr(dcc->description, ' ') ? "RESUME \"%s\" %s %ld" : "RESUME %s %s %ld", dcc->description, #else /* This is for testing mirc compatability */ "RESUME file.ext %s %ld", #endif dcc->othername, (long)sb.st_size); if (proto >= 0) set_server_protocol_state(from_server, proto); } else #endif { if ((file = open(fullname, O_WRONLY|O_TRUNC|O_CREAT, 0644))==-1) { /* MESSAGE_FROM */ say("Unable to open %s: %s", fullname, strerror(errno)); unlock_dcc(dcc); continue; } dcc->file = file; dcc->flags |= DCC_TWOCLIENTS; dcc_connect(dcc); /* Nonblocking should be ok here */ unlock_dcc(dcc); } } } unlock_dcc(NULL); if (!count) { if (filename) /* MESSAGE_FROM */ say("No file (%s) offered in SEND mode by %s", filename, user); else /* MESSAGE_FROM */ say("No file offered in SEND mode by %s", user); } } /* * Calculates transfer speed based on size, start time, and current time. */ static char * calc_speed (intmax_t sofar, Timeval sta, Timeval cur) { static char buf[7]; double tdiff = time_diff(sta, cur); if (sofar == 0 || tdiff <= 0.0) snprintf(buf, sizeof(buf), "N/A"); else snprintf(buf, sizeof(buf), "%4.1f", ((sofar / 1024.0) / tdiff)); return buf; } /* * Packs a file size into a smaller representation of Kb, Mb, or Gb. * I'm sure it can be done less kludgy. */ static char * calc_size (intmax_t fsize, char *retval, size_t retsize) { if (fsize < 0) snprintf(retval, retsize, "!ERR!"); else if (fsize < 1 << 10) snprintf(retval, retsize, INTMAX_FORMAT, fsize); else if (fsize < 1 << 20) snprintf(retval, retsize, "%3.1fKb", fsize / (double)(1 << 10)); else if (fsize < 1 << 30) snprintf(retval, retsize, "%3.1fMb", fsize / (double)(1 << 20)); else snprintf(retval, retsize, "%3.1fGb", fsize / (double)(1 << 30)); return retval; } /* * Usage: /DCC LIST */ static void dcc_list (char *args) { DCC_list *dcc; DCC_list *next; static const char *format = "%-7.7s%-3.3s %-9.9s %-9.9s %-20.20s %-6.6s %-5.5s %-6.6s %s"; char *encoded_description; int l; l = message_from(NULL, LEVEL_OTHER); /* Gulp */ if (do_hook(DCC_LIST_LIST, "Start * * * * * * *")) { put_it(format, "Type", " ", "Nick", "Status", "Start time", "Size", "Compl", "Kb/s", "Args"); } lock_dcc(NULL); for (dcc = ClientList ; dcc != NULL ; dcc = next) { unsigned flags = dcc->flags; next = dcc->next; lock_dcc(dcc); if ((flags & DCC_TYPES) == DCC_FILEOFFER) encoded_description = dcc_urlencode(dcc->description); else /* assume the other end encoded the filename */ encoded_description = malloc_strdup(dcc->description); if (do_hook(DCC_LIST_LIST, "%s %s %s %s " INTMAX_FORMAT " "INTMAX_FORMAT " "INTMAX_FORMAT" %s", dcc_types[flags & DCC_TYPES], zero, /* No encryption */ dcc->user, flags & DCC_DELETE ? "Closed" : flags & DCC_ACTIVE ? "Active" : flags & DCC_MY_OFFER ? "Waiting" : flags & DCC_THEIR_OFFER ? "Offered" : "Unknown", (intmax_t)dcc->starttime.tv_sec, dcc->filesize, dcc->bytes_sent ? dcc->bytes_sent : dcc->bytes_read, encoded_description)) { char * filename = strrchr(dcc->description, '/'); char completed[9]; char size[9]; char speed[9]; char buf[23]; const char * time_f; intmax_t tot_size; intmax_t act_sent; /* * Figure out something sane for the filename */ if (!filename || get_int_var(DCC_LONG_PATHNAMES_VAR)) filename = dcc->description; else if (filename && *filename) filename++; if (!filename) filename = LOCAL_COPY(ltoa(get_pending_bytes(dcc->socket))); /* * Figure out how many bytes we have sent for *this* * session, and how many bytes of the file have been * sent *in total*. */ if (dcc->bytes_sent) { tot_size = dcc->bytes_sent; act_sent = tot_size - dcc->resume_size; } else { tot_size = dcc->bytes_read; act_sent = tot_size - dcc->resume_size; } /* * Figure out something sane for the "completed" and * "size" fields. */ if (dcc->filesize) { double prop; long perc; prop = (double)tot_size / dcc->filesize; perc = prop * 100; snprintf(completed, sizeof completed, "%ld%%", perc); calc_size(dcc->filesize, size, sizeof(size)); } else { calc_size(tot_size, completed, sizeof(completed)); *size = 0; } /* * Figure out something sane for starting time */ if (dcc->starttime.tv_sec) { time_t blech = dcc->starttime.tv_sec; struct tm *btime = localtime(&blech); strftime(buf, 22, "%T %b %d %Y", btime); time_f = buf; } else time_f = empty_string; /* * Figure out something sane for the xfer speed. */ if (act_sent) { strlcpy(speed, calc_speed(act_sent, dcc->starttime, get_time(NULL)), sizeof speed); } else *speed = 0; /* * And do the dirty work. */ put_it(format, dcc_types[flags&DCC_TYPES], empty_string, /* No encryption */ dcc->user, flags & DCC_DELETE ? "Closed" : flags & DCC_ACTIVE ? "Active" : flags & DCC_MY_OFFER ? "Waiting" : flags & DCC_THEIR_OFFER ? "Offered" : "Unknown", time_f, size, completed, speed, filename); } if (encoded_description) new_free(&encoded_description); unlock_dcc(dcc); } unlock_dcc(NULL); do_hook(DCC_LIST_LIST, "End * * * * * * *"); pop_message_from(l); } /* * Usage: /DCC RAW [text] */ static void dcc_send_raw (char *args) { char *name; char *host; int l; if (!(name = next_arg(args, &args))) { /* MESSAGE_FROM */ say("No name specified for DCC RAW"); return; } if (!(host = next_arg(args, &args))) { /* MESSAGE_FROM */ say("No hostname specified for DCC RAW"); return; } l = message_from(name, LEVEL_DCC); dcc_message_transmit(DCC_RAW, name, host, args, args, 1, NULL); pop_message_from(l); } /* * Usage: /DCC RENAME [-CHAT] [] */ static void dcc_rename (char *args) { DCC_list *dcc; const char *user; const char *oldf; const char *newf; char *temp; int type = DCC_FILEREAD; if (!(user = next_arg(args, &args)) || !(temp = next_arg(args, &args))) { /* MESSAGE_FROM */ say("You must specify a nick and new filename for DCC RENAME"); return; } if ((newf = next_arg(args, &args)) != NULL) oldf = temp; else { newf= temp; oldf = NULL; } if (!my_strnicmp(user, "-CHAT", strlen(user))) { if (!oldf || !newf) { /* MESSAGE_FROM */ say("You must specify a new nickname for DCC RENAME -CHAT"); return; } user = oldf; oldf = "chat"; type = DCC_CHAT; } if ((dcc = dcc_searchlist(type, user, oldf, NULL, -1))) { if (type == DCC_FILEREAD && (dcc->flags & DCC_ACTIVE)) { /* MESSAGE_FROM */ say("Too late to rename that file"); return; } if (type == DCC_FILEREAD) { /* MESSAGE_FROM */ say("File %s from %s renamed to %s", dcc->description ? dcc->description : "", user, newf); malloc_strcpy(&dcc->description, newf); } else { if (is_channel(newf)) { /* MESSAGE_FROM */ say("I can't permit you to DCC RENAME to " "something that looks like a channel, " "sorry."); return; } /* MESSAGE_FROM */ say("DCC CHAT from %s changed to new nick %s", user, newf); malloc_strcpy(&dcc->user, newf); } } else /* MESSAGE_FROM */ say("%s has not yet offered you the file %s", user, oldf ? oldf : ""); } /* * Usage: /DCC SEND [] */ static void dcc_filesend (char *args) { char *user, *this_arg; Filename fullname; unsigned short portnum = 0; int filenames_parsed = 0; DCC_list *Client; Stat stat_buf; int family; /* * For sure, at least one argument is needed, the target */ if ((user = next_arg(args, &args))) { family = AF_INET; while (args && *args) { this_arg = new_next_arg(args, &args); /* * Check to see if its a flag */ if (*this_arg == '-') { if (this_arg[1] == 'p') { if (args && *args) portnum = my_atol(next_arg(args, &args)); } else if (this_arg[1] == '6') family = AF_INET6; else if (this_arg[1] == '4') family = AF_INET; continue; } /* * Ok. We have a filename they want to send. Check to * see what kind it is. */ if (normalize_filename(this_arg, fullname)) { /* MESSAGE_FROM */ say("%s is not a valid directory", fullname); continue; } /* * Make a note that we've seen a filename */ filenames_parsed++; #ifdef I_DONT_TRUST_MY_USERS /* * Dont allow the user to send a file that is in "/etc" or * a file that ends in "/passwd". Presumably this is for * your safety. If you can figure out how to get around this, * then i guess you dont need this protection. */ if (!strncmp(fullname, "/etc/", 5) || !end_strcmp(fullname, "/passwd", 7)) { /* MESSAGE_FROM */ say("Send Request Rejected"); continue; } #endif if (access(fullname, R_OK)) { /* MESSAGE_FROM */ say("Cannot send %s because you dont have read permission", fullname); continue; } if (isdir(fullname)) { /* MESSAGE_FROM */ say("Cannot send %s because it is a directory", fullname); continue; } stat(fullname, &stat_buf); if ((Client = dcc_searchlist(DCC_FILEOFFER, user, fullname, this_arg, -1))) { if ((Client->flags & DCC_ACTIVE) || (Client->flags & DCC_MY_OFFER)) { /* MESSAGE_FROM */ say("Sending a booster CTCP handshake for " "an existing DCC SEND:%s to %s", fullname, user); dcc_send_booster_ctcp(Client); continue; } } else Client = dcc_create(DCC_FILEOFFER, user, fullname, this_arg, family, stat_buf.st_size); Client->flags |= DCC_TWOCLIENTS; Client->want_port = portnum; fill_in_default_port(Client); dcc_listen(Client); } /* The WHILE */ } /* The IF */ if (!filenames_parsed) /* FROM_MESSAGE */ yell("Usage: /DCC SEND <[=]nick> [ ...]"); } /* * Usage: $listen( ) */ char *dcc_raw_listen (int family, unsigned short port) { DCC_list *Client; const char * PortName = empty_string; int l; lock_dcc(NULL); l = message_from(NULL, LEVEL_DCC); do { if (port && port < 1024) { /* MESSAGE_FROM */ say("May not bind to a privileged port"); break; } PortName = LOCAL_COPY(ltoa(port)); if ((Client = dcc_searchlist(DCC_RAW_LISTEN, PortName, NULL, NULL, -1))) { if ((Client->flags & DCC_ACTIVE) || (Client->flags & DCC_MY_OFFER)) { /* MESSAGE_FROM */ say("A previous DCC RAW_LISTEN on %s exists", PortName); break; } } else Client = dcc_create(DCC_RAW_LISTEN, PortName, "raw_listen", NULL, family, 0); lock_dcc(Client); Client->want_port = port; if (dcc_listen(Client)) /* Not a connect(). */ { unlock_dcc(Client); break; } get_time(&Client->starttime); Client->flags |= DCC_ACTIVE; Client->user = malloc_strdup(ltoa(Client->want_port)); unlock_dcc(Client); } while (0); unlock_dcc(NULL); dcc_garbage_collect(); pop_message_from(l); return malloc_strdup(PortName); } /* * Usage: $connect( ) */ char *dcc_raw_connect (const char *host, const char *port, int family) { DCC_list * Client = NULL; SSu my_sockaddr; char retval[12]; int l; *retval = 0; lock_dcc(NULL); l = message_from(NULL, LEVEL_DCC); do { memset(&my_sockaddr.ss, 0, sizeof(my_sockaddr.ss)); my_sockaddr.sa.sa_family = family; if (inet_strton(host, port, &my_sockaddr, AI_ADDRCONFIG)) { /* MESSAGE_FROM */ say("Unknown host: %s", host); break; } if ((Client = dcc_searchlist(DCC_RAW, port, host, NULL, -1))) { if (Client->flags & DCC_ACTIVE) { /* MESSAGE_FROM */ say("A previous DCC RAW to %s on %s exists", host, port); break; } } else Client = dcc_create(DCC_RAW, port, host, NULL, family, 0); lock_dcc(Client); Client->offer.ss = my_sockaddr.ss; Client->flags = DCC_THEIR_OFFER | DCC_RAW; if (dcc_connect(Client)) /* Nonblocking from here */ { unlock_dcc(Client); break; } unlock_dcc(Client); snprintf(retval, sizeof retval, "%d", Client->socket); } while (0); unlock_dcc(NULL); pop_message_from(l); dcc_garbage_collect(); return malloc_strdup(retval); } /* * * All the rest of this file is dedicated to automatic replies generated * by the client in response to external stimuli from DCC connections. * */ /* * When a user does a CTCP DCC, it comes here for preliminary parsing. * * XXX This function is not really family independant (but it's close) */ void register_dcc_offer (const char *user, char *type, char *description, char *address, char *port, char *size, char *extra, char *rest) { DCC_list * dcc = NULL; int dtype; char * c; unsigned short realport; /* Bleh */ char p_addr[256]; int err; SSu offer; intmax_t filesize; int l; /* * Ensure that nobody will mess around with us while we're working. */ lock_dcc(NULL); l = message_from(NULL, LEVEL_DCC); do { /* If we're debugging, give the user the raw handshake details. */ if (x_debug & DEBUG_DCC_SEARCH) yell("register_dcc_offer: [%s|%s|%s|%s|%s|%s|%s]", user, type, description, address, port, size, extra); /* If they offer us a path with directory, ignore the directory. */ if ((c = strrchr(description, '/'))) description = c + 1; /* If they offer us a hidden ("dot") file, mangle the dot. */ if (*description == '.') *description = '_'; /* If they give us a file size, set the global variable */ if (size && *size) filesize = STRNUM(size); else filesize = 0; /* * Figure out if it's a type of offer we support. */ if (!my_stricmp(type, "CHAT")) dtype = DCC_CHAT; else if (!my_stricmp(type, "SEND")) { if (!size || !*size || !is_number(size)) { /* MESSAGE_FROM */ say("DCC SEND (%s) received from %s without a file size", description, user); break; } dtype = DCC_FILEREAD; } #ifdef MIRC_BROKEN_DCC_RESUME else if (!my_stricmp(type, "RESUME")) { /* * Dont be deceieved by the arguments we're passing it. * The arguments are "out of order" because MIRC doesnt * send them in the traditional order. Ugh. */ if (!port || !*port) { /* MESSAGE_FROM */ say("DCC RESUME received from %s without a resume location", user); break; } dcc_getfile_resume_demanded(user, description, address, port); break; } else if (!my_stricmp(type, "ACCEPT")) { /* * See the comment above. */ dcc_getfile_resume_start (user, description, address, port); break; } #endif else { /* MESSAGE_FROM */ say("Unknown DCC %s (%s) received from %s", type, description, user); break; } /* CHECK HANDSHAKE ADDRESS FOR VALIDITY */ /* * Convert the handshake address to a sockaddr. If it cannot be * figured out, warn the user and punt. */ memset(&offer.ss, 0, sizeof(offer.ss)); offer.sa.sa_family = AF_UNSPEC; if ((err = inet_strton(address, port, &offer, AI_NUMERICHOST))) { /* MESSAGE_FROM */ say("DCC %s (%s) request from %s had mangled return address [%s] (%d)", type, description, user, address, err); break; } /* * Convert the sockaddr back to a name that we can print. * What we've got now is the original handshake address, a * sockaddr, and a p-addr. */ if (inet_ntostr(&offer, p_addr, 256, NULL, 0, NI_NUMERICHOST)) { /* MESSAGE_FROM */ say("DCC %s (%s) request from %s could not be converted back " "into a p-addr [%s] [%s]", type, description, user, address, port); break; } /* * Check for invalid or illegal IP addresses. */ if (family(&offer) == AF_INET) { if (offer.si.sin_addr.s_addr == 0) { /* FROM_MESSAGE */ yell("### DCC handshake from %s ignored becuase it had " "an null address", user); break; } } else if (family(&offer) == AF_INET6) { /* Reserved for future expansion */ } #ifdef HACKED_DCC_WARNING /* * Check for hacked (forged) IP addresses. A handshake is considered * forged if the address in the handshake is not the same address that * the user is using on irc. This is not turned on by default because * it can make epic block on dns lookups, which rogue users can use * to make your life painful, and also because a lot of networks are * using faked hostnames on irc, which makes this a waste of time. */ if (family(&offer) == AF_INET) { char * fromhost; SSu irc_addr; if (!(fromhost = strchr(FromUserHost, '@'))) { /* FROM_MESSAGE */ yell("### Incoming handshake from a non-user peer!"); break; } fromhost++; irc_addr.sa.sa_family = family(&offer); if (inet_strton(fromhost, port, &irc_addr, AI_ADDRCONFIG)) { /* FROM_MESSAGE */ yell("### Incoming handshake has an address or port " "[%s:%s] that could not be figured out!", fromhost, port); yell("### Please use caution in deciding whether to " "accept it or not"); } else if (family(&offer) == AF_INET) { if (irc_addr.si.sin_addr.s_addr != offer.si.sin_addr.s_addr) { /* MESSAGE_FROM */ say("WARNING: Fake dcc handshake detected! [%x]", V4ADDR(offer.si).s_addr); say("Unless you know where this dcc request is coming from"); say("It is recommended you ignore it!"); } } } else if (family(&offer) == AF_INET6) { /* Reserved for future expansion */ } #endif /* CHECK HANDSHAKE PORT FOR VALIDITY */ if ((realport = strtoul(port, NULL, 10)) < 1024) { /* MESSAGE_FROM */ say("DCC %s (%s) request from %s rejected because it " "specified reserved port number (%hu) [%s]", type, description, user, realport, port); break; } /******************************************************************* * So now we've checked the address, and it's ok, we've checked the * port, and it's ok, and we've checked the file size, and it's ok. * We are now ready to get down to business! *******************************************************************/ /* Get ourselves a new dcc entry. */ if ((dcc = dcc_searchlist(dtype, user, description, NULL, -1))) { /* * If DCC_MY_OFFER is set, that means we have already sent this * person this same DCC offer they sent us. Now we have a race * condition. They will try to accept ours and we will try to * accept theirs. Let's see who wins! */ if (dcc->flags & DCC_MY_OFFER) { /* Revoke the offer I made to them. */ dcc->flags |= DCC_DELETE; /* * If they offered us a DCC CHAT, create a new entry for * this new offer to us. * * DCC CHAT collision -- do it automatically. */ if (dtype == DCC_CHAT) { char *copy; /* MESSAGE_FROM */ say("DCC CHAT already requested by %s, connecting.", user); dcc_create(dtype, user, "chat", NULL, family(&offer), filesize); copy = LOCAL_COPY(user); dcc_chat(copy); break; } /* * Otherwise, we're trying to send the same file to each other. * We just punt here because I don't know what else to do. */ else { /* MESSAGE_FROM */ say("DCC %s collision for %s:%s", type, user, description); send_ctcp(0, user, "DCC", "DCC %s collision occured while connecting to %s (%s)", type, get_server_nickname(from_server), description); break; } } /* * If DCC_ACTIVE is set, that means we already have an open * connection to them, either a file transfer or a dcc chat. * We definitely can't have another one open, so punt on this * new one. */ if (dcc->flags & DCC_ACTIVE) { /* MESSAGE_FROM */ say("Received DCC %s request from %s while previous " "session still active", type, user); break; } } else dcc = dcc_create(dtype, user, description, NULL, family(&offer), filesize); /* * Otherwise, we're good to go! Mark this dcc as being something * they offered to us, and copy over the port and address. */ dcc->flags |= DCC_THEIR_OFFER; memcpy(&dcc->offer.ss, &offer.ss, sizeof offer.ss); /* * DCC SEND and CHAT have different arguments, so they can't * very well use the exact same hooked data. Both now are * identical for $0-4, and SEND adds filename/size in $5-6 */ lock_dcc(dcc); if ((dcc->flags & DCC_TYPES) == DCC_FILEREAD) { if (do_hook(DCC_REQUEST_LIST, "%s %s %s %s %s %s "INTMAX_FORMAT, user, type, description, p_addr, port, dcc->description, dcc->filesize)) goto display_it; } else if (do_hook(DCC_REQUEST_LIST, "%s %s %s %s %s", user, type, description, p_addr, port)) goto display_it; unlock_dcc(dcc); /* All done! */ break; display_it: /* * This is kind of a detour we take because the user didn't grab * the ONs and we need to output something to tell them about it. */ unlock_dcc(dcc); /* * For DCC SEND offers, if we already have the file, we need to warn * the user so they don't accidentally delete one of their files! */ if ((dcc->flags & DCC_TYPES) == DCC_FILEREAD) { Stat statit; char * realfilename; if (get_string_var(DCC_STORE_PATH_VAR)) realfilename = malloc_sprintf(NULL, "%s/%s", get_string_var(DCC_STORE_PATH_VAR), dcc->description); else realfilename = malloc_strdup(dcc->description); if (stat(realfilename, &statit) == 0) { int xclose = 0, resume = 0, ren = 0, get = 0; if ((intmax_t)statit.st_size < dcc->filesize) { /* MESSAGE_FROM */ say("WARNING: File [%s] exists but is smaller than " "the file offered.", realfilename); xclose = resume = ren = get = 1; } else if ((intmax_t)statit.st_size == dcc->filesize) { /* MESSAGE_FROM */ say("WARNING: File [%s] exists, and its the same size.", realfilename); xclose = ren = get = 1; } else { /* MESSAGE_FROM */ say("WARNING: File [%s] already exists.", realfilename); xclose = ren = get = 1; } if (xclose) /* MESSAGE_FROM */ say("Use /DCC CLOSE GET %s %s to not get the file.", user, dcc->description); #ifdef MIRC_BROKEN_DCC_RESUME if (resume) /* MESSAGE_FROM */ say("Use /DCC RESUME %s %s to continue the " "copy where it left off.", user, dcc->description); #endif if (get) /* MESSAGE_FROM */ say("Use /DCC GET %s %s to overwrite the " "existing file.", user, dcc->description); if (ren) /* MESSAGE_FROM */ say("Use /DCC RENAME %s %s newname to save it to a " "different filename.", user, dcc->description); } new_free(&realfilename); } /* Thanks, Tychy! (lherron@imageek.york.cuny.edu) */ if ((dcc->flags & DCC_TYPES) == DCC_FILEREAD) /* MESSAGE_FROM */ say("DCC %s (%s "INTMAX_FORMAT") request received " "from %s!%s [%s (%s)]", type, description, dcc->filesize, user, FromUserHost, p_addr, port); else /* MESSAGE_FROM */ say("DCC %s (%s) request received from %s!%s [%s (%s)]", type, description, user, FromUserHost, p_addr, port); } while (0); if (dcc) { get_time(&dcc->lasttime); get_time(&dcc->starttime); } unlock_dcc(NULL); dcc_garbage_collect(); pop_message_from(l); return; } /* * Check all DCCs for data, and if they have any, perform whatever * actions are required. */ void do_dcc (int fd) { DCC_list *Client; int previous_server; int l; int found_it = 0; /* Sanity */ if (fd < 0) return; /* Whats with all this double-pointer chicanery anyhow? */ lock_dcc(NULL); for (Client = ClientList; Client; Client = Client->next) { if (Client->socket == fd) { found_it = 1; previous_server = from_server; from_server = FROMSERV; l = message_from(NULL, LEVEL_DCC); if (Client->flags & DCC_DELETE) { /* MESSAGE_FROM */ say("DCC fd %d is ready, client is deleted. Closing.", fd); Client->socket = new_close(Client->socket); } else switch (Client->flags & DCC_TYPES) { case DCC_CHAT: process_dcc_chat(Client); break; case DCC_RAW_LISTEN: process_incoming_listen(Client); break; case DCC_RAW: process_incoming_raw(Client); break; case DCC_FILEOFFER: process_dcc_send(Client); break; case DCC_FILEREAD: process_incoming_file(Client); break; } get_time(&Client->lasttime); pop_message_from(l); from_server = previous_server; } /* * Don't time out raw_listen sockets. */ else if ((Client->flags & DCC_TYPES) == DCC_RAW_LISTEN) /* nothing */(void)0; /* * This shouldnt be neccesary any more, but what the hey, * I'm still paranoid. */ if (!Client) break; } if (!found_it) { /* FROM_MESSAGE */ yell("DCC callback for fd %d but it doesn't exist any more! " "Closing it. Wish me luck!", fd); new_close(fd); } unlock_dcc(NULL); dcc_garbage_collect(); } /*********************************** DCC CHAT *******************************/ /* * This is a "unix_accept" callback, which is invoked when a remote user * accepts our dcc chat offer. unix_accept sends us the file descriptor * (from accept(2)) and the getpeeraddr() from that socket. * * At this point, we can close our listen(2) socket (in Client->socket) and * use the new accept(2) socket instead, and start the connection. */ static void process_dcc_chat_connection (DCC_list *Client) { char p_addr[256]; char p_port[24]; int fd; int c1, c2; c1 = DGETS(Client->socket, fd) c2 = DGETS(Client->socket, Client->peer_sockaddr.ss) if (c1 != sizeof(fd) || c2 != sizeof(Client->peer_sockaddr.ss)) { Client->flags |= DCC_DELETE; /* FROM_MESSAGE */ yell("### DCC Error: accept() failed."); return; } Client->socket = new_close(Client->socket); if ((Client->socket = fd) > 0) new_open(Client->socket, do_dcc, NEWIO_RECV, 1, Client->server); else { Client->flags |= DCC_DELETE; /* FROM_MESSAGE */ yell("### DCC Error: accept() failed. Punting."); return; } Client->flags &= ~DCC_MY_OFFER; Client->flags |= DCC_ACTIVE; inet_ntostr(&Client->peer_sockaddr, p_addr, 256, p_port, 24, NI_NUMERICHOST); lock_dcc(Client); if (do_hook(DCC_CONNECT_LIST, "%s CHAT %s %s", Client->user, p_addr, p_port)) /* MESSAGE_FROM */ say("DCC chat connection to %s[%s:%s] established", Client->user, p_addr, p_port); unlock_dcc(Client); get_time(&Client->starttime); } static void process_dcc_chat_error (DCC_list *Client) { lock_dcc(Client); if (do_hook(DCC_LOST_LIST, "%s CHAT ERROR", Client->user)) /* MESSAGE_FROM */ say("DCC CHAT connection to %s lost", Client->user); Client->flags |= DCC_DELETE; unlock_dcc(Client); return; } static char * process_dcc_chat_ctcps (DCC_list *Client, char *tmp) { int ctcp_request = 0, ctcp_reply = 0; int l; char *target = NULL, *extra = NULL; #define CTCP_MESSAGE "CTCP_MESSAGE " #define CTCP_REPLY "CTCP_REPLY " if (*tmp == CTCP_DELIM_CHAR) ctcp_request = 1; else if (!strncmp(tmp, CTCP_MESSAGE, strlen(CTCP_MESSAGE))) { ov_strcpy(tmp, tmp + strlen(CTCP_MESSAGE)); ctcp_request = 1; } else if (!strncmp(tmp, CTCP_REPLY, strlen(CTCP_REPLY))) { ov_strcpy(tmp, tmp + strlen(CTCP_REPLY)); ctcp_reply = 1; } if (ctcp_request == 1 || ctcp_reply == 1) { const char *OFUH = FromUserHost; if (Client->userhost && *Client->userhost) FromUserHost = Client->userhost; else FromUserHost = unknown_userhost; /* * So 'inbound_recode' will decode 'tmp' in place, * UNLESS 'tmp' isn't big enough to hold the new text. * In that case, 'extra' is malloc()ed and it will * hold the new text. * So if 'extra' is null, 'tmp' already holds it. * If 'extra' is not null, 'tmp' needs to point to it. */ target = dcc_target(Client->user); inbound_recode(target, -1, empty_string, tmp, &extra); if (extra) tmp = extra; l = message_from(target, LEVEL_CTCP); if (ctcp_request == 1) tmp = do_ctcp(1, target, nickname, tmp); else tmp = do_ctcp(0, target, nickname, tmp); pop_message_from(l); FromUserHost = OFUH; new_free(&target); new_free(&extra); } if (!tmp || !*tmp) return NULL; return tmp; } static void process_dcc_chat_data (DCC_list *Client) { char tmp[IO_BUFFER_SIZE + 1]; ssize_t bytesread; int l; const char * OFUH = FromUserHost; char * target; const char * utf8_text = NULL; char * extra = NULL; /* Get a new line via dgets. */ bytesread = dgets(Client->socket, tmp, IO_BUFFER_SIZE, 1); /* * bytesread == 0 means there was new data, but it was an incomplete * line. Since we allow dgets() to buffer for us, we just ignore * this and wait for the rest of the line later. */ if (bytesread == 0) return; /* * bytesread == -1 means the connection just totaly died. */ if (bytesread == -1) { process_dcc_chat_error(Client); return; } /* * Otherwise, handle a new DCC CHAT message. */ Client->bytes_read += bytesread; chomp(tmp); /* Tell the user... */ if (x_debug & DEBUG_INBOUND) yell("DCC: [%d] <- [%s]", Client->socket, tmp); /* Handle any CTCPs... */ /* If the message is empty, ignore it... */ if (!process_dcc_chat_ctcps(Client, tmp) || !*tmp) return; /* Otherwise throw the message to the user. */ target = dcc_target(Client->user); utf8_text = inbound_recode(target, -1, empty_string, tmp, &extra); l = message_from(target, LEVEL_DCC); lock_dcc(Client); if (Client->userhost && *Client->userhost) FromUserHost = Client->userhost; else FromUserHost = unknown_userhost; if (do_hook(DCC_CHAT_LIST, "%s %s", Client->user, utf8_text)) { char timestr[256]; *timestr = 0; if (get_server_away(NOSERV)) snprintf(timestr, sizeof(timestr), " <%s>", my_ctime(time(NULL))); put_it("=%s= %s%s", Client->user, utf8_text, timestr); } FromUserHost = OFUH; unlock_dcc(Client); pop_message_from(l); new_free(&target); new_free(&extra); } /* * This is a unix_connect() callback which is invoked when a connect(2)ing * socket is ready to write (which means the connection is complete or failed) * This is used when we connect to someone else's offer */ static void process_dcc_chat_connected (DCC_list *dcc) { lock_dcc(dcc); if (x_debug & DEBUG_SERVER_CONNECT) yell("process_dcc_chat_connected: dcc [%s] now ready to write", dcc->user); if (dcc_get_connect_addrs(dcc)) { if (do_hook(DCC_LOST_LIST, "%s CHAT ERROR", dcc->user)) /* MESSAGE_FROM */ say("DCC CHAT connection to %s lost", dcc->user); dcc->flags |= DCC_DELETE; unlock_dcc(dcc); return; } dcc_connected(dcc->socket); dcc->flags &= ~DCC_CONNECTING; unlock_dcc(dcc); } static void process_dcc_chat (DCC_list *Client) { if (Client->flags & DCC_MY_OFFER) process_dcc_chat_connection(Client); else if (Client->flags & DCC_CONNECTING) process_dcc_chat_connected(Client); else process_dcc_chat_data(Client); } /****************************** DCC RAW *************************************/ /* * This is a unix_accept() callback invoked whenever we accept(2) an incoming * connection to our listen(2)ing socket. This creates a new DCC RAW, and * the original DCC RAW listen is unchanged. */ static void process_incoming_listen (DCC_list *Client) { SSu remaddr; int new_socket; char fdstr[10]; DCC_list *NewClient; char host[1025]; socklen_t len; char p_port[24]; char l_port[24]; char trash[1025] = ""; int c1, c2; memset(&remaddr.ss, 0, sizeof(remaddr.ss)); c1 = DGETS(Client->socket, new_socket) c2 = DGETS(Client->socket, remaddr.ss) if (c1 != sizeof(new_socket) || c2 != sizeof(remaddr.ss)) { Client->flags |= DCC_DELETE; /* FROM_MESSAGE */ yell("### DCC Error: accept() failed."); return; } if (new_socket < 0) { /* FROM_MESSAGE */ yell("### DCC Error: accept() failed. Punting."); return; } *host = 0; inet_ntostr(&remaddr, host, sizeof(host), p_port, sizeof(p_port), 0); strlcpy(fdstr, ltoa(new_socket), sizeof fdstr); if (!(NewClient = dcc_searchlist(DCC_RAW, fdstr, host, NULL, 0))) NewClient = dcc_create(DCC_RAW, fdstr, host, NULL, 0, 0); NewClient->socket = new_socket; memcpy(&NewClient->peer_sockaddr.ss, &remaddr.ss, sizeof(remaddr.ss)); memcpy(&NewClient->offer.ss, &remaddr.ss, sizeof(remaddr.ss)); len = sizeof(NewClient->local_sockaddr.ss); getsockname(NewClient->socket, &NewClient->local_sockaddr.sa, &len); inet_ntostr(&Client->local_sockaddr, trash, sizeof(trash), l_port, sizeof(l_port), 0); NewClient->flags |= DCC_ACTIVE; NewClient->flags |= DCC_QUOTED & Client->flags; NewClient->bytes_read = NewClient->bytes_sent = 0; get_time(&NewClient->starttime); new_open(NewClient->socket, do_dcc, NEWIO_RECV, 1, NewClient->server); lock_dcc(Client); if (do_hook(DCC_RAW_LIST, "%s %s N %s", NewClient->user, NewClient->description, l_port)) if (do_hook(DCC_CONNECT_LIST,"%s RAW %s %s", NewClient->user, NewClient->description, l_port)) /* MESSAGE_FROM */ say("DCC RAW connection to %s on %s via %s established", NewClient->description, NewClient->user, p_port); unlock_dcc(Client); } /* * This handles when someone sends you a line of info over a DCC RAW * connection (that was established with a $listen() or $connect()). */ static void process_dcc_raw_data (DCC_list *Client) { char tmp[IO_BUFFER_SIZE + 1]; char *bufptr; char * freeme = NULL; ssize_t bytesread; bufptr = tmp; if (Client->flags & DCC_QUOTED) bytesread = dgets(Client->socket, bufptr, IO_BUFFER_SIZE, -1); else if (Client->packet_size > 0) bytesread = dgets(Client->socket, bufptr, Client->packet_size, 2); else if (Client->full_line_buffer) bytesread = dgets(Client->socket, bufptr, IO_BUFFER_SIZE, 1); else bytesread = dgets(Client->socket, bufptr, IO_BUFFER_SIZE, 0); switch (bytesread) { CLOSE: case -1: { lock_dcc(Client); if (do_hook(DCC_RAW_LIST, "%s %s C", Client->user, Client->description)) if (do_hook(DCC_LOST_LIST,"%s RAW %s", Client->user, Client->description)) /* MESSAGE_FROM */ say("DCC RAW connection to %s on %s lost", Client->user, Client->description); Client->flags |= DCC_DELETE; unlock_dcc(Client); break; } case 0: { if (Client->flags & DCC_QUOTED) goto CLOSE; /* FALLTHROUGH */ FALLTHROUGH } default: { from_server = primary_server; /* Colten asked for it */ if (Client->flags & DCC_QUOTED) { char * dest; if (!(dest = transform_string_dyn("+CTCP", bufptr, bytesread, NULL))) /* FROM_MESSAGE */ yell("DCC RAW: Could not CTCP enquote [%s]", bufptr); else freeme = bufptr = dest; } else if (bytesread > 0 && tmp[strlen(tmp) - 1] == '\n') tmp[strlen(tmp) - 1] = '\0'; Client->bytes_read += bytesread; lock_dcc(Client); if (do_hook(DCC_RAW_LIST, "%s %s D %s", Client->user, Client->description, bufptr)) /* MESSAGE_FROM */ say("Raw data on %s from %s: %s", Client->user, Client->description, bufptr); unlock_dcc(Client); } } new_free(&freeme); return; } /* * This is a unix_connect() callback called when your $connect() is writable * either connect()ed or it failed. */ static void process_dcc_raw_connected (DCC_list *dcc) { lock_dcc(dcc); if (x_debug & DEBUG_SERVER_CONNECT) yell("process_dcc_raw_connected: dcc [%s] now ready to write", dcc->user); if (dcc_get_connect_addrs(dcc)) { if (do_hook(DCC_LOST_LIST, "%s RAW ERROR", dcc->user)) /* MESSAGE_FROM */ say("DCC RAW connection to %s lost", dcc->user); dcc->flags |= DCC_DELETE; unlock_dcc(dcc); return; } if (family(&dcc->peer_sockaddr) == AF_INET) malloc_strcpy(&dcc->othername, ltoa(ntohs(dcc->peer_sockaddr.si.sin_port))); else if (family(&dcc->peer_sockaddr) == AF_INET6) malloc_strcpy(&dcc->othername, ltoa(ntohs(dcc->peer_sockaddr.si6.sin6_port))); else malloc_strcpy(&dcc->othername, ""); dcc->user = malloc_strdup(ltoa(dcc->socket)); do_hook(DCC_RAW_LIST, "%s %s E %s", dcc->user, dcc->description, dcc->othername); dcc_connected(dcc->socket); dcc->flags &= ~DCC_CONNECTING; unlock_dcc(dcc); } static void process_incoming_raw (DCC_list *Client) { if (Client->flags & DCC_CONNECTING) process_dcc_raw_connected(Client); else process_dcc_raw_data(Client); } /****************************** DCC SEND ************************************/ /* * When youre sending a file, and your peer sends an ACK, this handles * whether or not to send the next packet. */ static void process_dcc_send_connection (DCC_list *dcc) { int new_fd; #ifdef HAVE_SO_SNDLOWAT int size; #endif char p_addr[256]; char p_port[24]; char *encoded_description; int c1, c2; c1 = DGETS(dcc->socket, new_fd) c2 = DGETS(dcc->socket, dcc->peer_sockaddr.ss) if (c1 != sizeof(new_fd) || c2 != sizeof(dcc->peer_sockaddr.ss)) { dcc->flags |= DCC_DELETE; /* FROM_MESSAGE */ yell("### DCC Error: accept() failed."); return; } dcc->socket = new_close(dcc->socket); if ((dcc->socket = new_fd) < 0) { dcc->flags |= DCC_DELETE; /* FROM_MESSAGE */ yell("### DCC Error: accept() failed. Punting."); return; } new_open(dcc->socket, do_dcc, NEWIO_RECV, 1, dcc->server); dcc->flags &= ~DCC_MY_OFFER; dcc->flags |= DCC_ACTIVE; get_time(&dcc->starttime); #ifdef HAVE_SO_SNDLOWAT /* * Give a hint to the OS how many bytes we need to send * for each write() */ size = DCC_BLOCK_SIZE; if (setsockopt(dcc->socket, SOL_SOCKET, SO_SNDLOWAT, &size, sizeof(size)) < 0) /* MESSAGE_FROM */ say("setsockopt failed: %s", strerror(errno)); #endif inet_ntostr(&dcc->peer_sockaddr, p_addr, 256, p_port, 24, NI_NUMERICHOST); /* * Tell the user */ lock_dcc(dcc); encoded_description = dcc_urlencode(dcc->description); if (do_hook(DCC_CONNECT_LIST, "%s SEND %s %s %s "INTMAX_FORMAT, dcc->user, p_addr, p_port, dcc->description, dcc->filesize)) /* MESSAGE_FROM */ say("DCC SEND connection to %s[%s:%s] established", dcc->user, p_addr, p_port); new_free(&encoded_description); unlock_dcc(dcc); /* * Open up the file to be sent */ if ((dcc->file = open(dcc->description, O_RDONLY)) == -1) { dcc->flags |= DCC_DELETE; /* MESSAGE_FROM */ say("Unable to open %s: %s", dcc->description, strerror(errno)); return; } /* * Set up any DCC RESUME as needed */ if (dcc->bytes_sent) { if (x_debug & DEBUG_DCC_XMIT) yell("Resuming at address ("INTMAX_FORMAT")", dcc->bytes_sent); lseek(dcc->file, dcc->bytes_sent, SEEK_SET); } } static void process_dcc_send_handle_ack (DCC_list *dcc) { char * encoded_description; uint32_t bytes; intmax_t provisional_bytes; if (x_debug & DEBUG_DCC_XMIT) yell("Reading a packet from [%s:%s("INTMAX_FORMAT")]", dcc->user, dcc->othername, dcc->bytes_acked); /* * The acknowledgement is /ALWAYS/ a 32 bit integers in network * order, no matter what. Even if we've sent more than 2^32 bits, * the value wraps around to 0 again. ugh. bleh. */ if (dgets(dcc->socket, (char *)&bytes, sizeof(bytes), -1) < (ssize_t)sizeof(bytes)) { lock_dcc(dcc); encoded_description = dcc_urlencode(dcc->description); if (do_hook(DCC_LOST_LIST,"%s SEND %s CONNECTION LOST", dcc->user, encoded_description)) /* MESSAGE_FROM */ say("DCC SEND:%s connection to %s lost", dcc->description, dcc->user); new_free(&encoded_description); dcc->flags |= DCC_DELETE; unlock_dcc(dcc); return; } bytes = ntohl(bytes); /* * XXX Ok. Rollover logic is atrocious, but I wrote it in a hurry * and if you want to "fix" it, be my guest. * * Acknowledgements are always 32 bits. This is de facto the right * 32 bits in the number of bytes transferred. The number of bytes * transferred might exceed 32 bits, so we need to extrapolate what * the extra bits will be. * * Normally, we will substitute the right 32 bits of the last value * of "bytes_acked" with the new bytes value. This is fine and * dandy as long as there is no overflow. * * But if there is overflow, then the extended "bytes" will be less * than the previous value of "bytes_acked". In this case, we will * substitute the right 32 bits of the bytes SENT, since this would * be in theory a value rolled over from the bytes ACKed. If this * resulting value is reasonable, then we run with it, and this * effects the rollover. * * Ugh. Why am I wasting time supporting dcc sends > 2gb anyways? */ provisional_bytes = dcc->bytes_acked; provisional_bytes = provisional_bytes >> 32; provisional_bytes = provisional_bytes << 32; provisional_bytes += bytes; if (provisional_bytes < dcc->bytes_acked) { provisional_bytes = dcc->bytes_sent; provisional_bytes = provisional_bytes >> 32; provisional_bytes = provisional_bytes << 32; provisional_bytes += bytes; } if (provisional_bytes > dcc->bytes_sent) { yell("### WARNING! The other peer claims to have recieved more bytes than"); yell("### I have actually sent so far. Please report this to "); yell("### problems@epicsol.org or #epic on EFNet right away."); yell("### Ask the person who you sent the file to look for garbage at the"); yell("### end of the file you just sent them. Please enclose that "); yell("### information as well as the following:"); yell("###"); yell("### bytesrecvd [%ld ("INTMAX_FORMAT")]", (long)bytes, provisional_bytes); yell("### dcc->bytes_sent ["INTMAX_FORMAT"]", dcc->bytes_sent); yell("### dcc->filesize ["INTMAX_FORMAT"]", dcc->filesize); yell("### dcc->bytes_acked ["INTMAX_FORMAT"]", dcc->bytes_acked); /* And just cope with it to avoid whining */ dcc->bytes_sent = provisional_bytes; } if (provisional_bytes >= dcc->bytes_acked) { if (x_debug & DEBUG_DCC_XMIT) yell("Bytes to %ld ACKed", (long)bytes); dcc->bytes_acked = provisional_bytes; } /* * If we've sent the whole file already... */ if (dcc->bytes_sent >= dcc->filesize) { /* * If theyve ACKed the last packet, we close * the connection. */ if (provisional_bytes >= dcc->filesize) DCC_close_filesend(dcc, "SEND", "TRANSFER COMPLETE"); /* * Either way there's nothing more to do. */ return; } } static void process_dcc_send_data (DCC_list *dcc) { intmax_t fill_window; ssize_t bytesread; char tmp[DCC_BLOCK_SIZE+1]; int old_from_server = from_server; char bytes_sent[10]; char filesize[10]; /* * We use a nonblocking sliding window algorithm. We send as many * packets as we can *without blocking*, up to but never more than * the value of /SET DCC_SLIDING_WINDOW. Whenever we recieve some * stimulus (like from an ACK) we re-fill the window. We always do * a my_iswritable() before we write() to make sure that it wont block. */ fill_window = get_int_var(DCC_SLIDING_WINDOW_VAR) * DCC_BLOCK_SIZE; if (fill_window < DCC_BLOCK_SIZE) fill_window = DCC_BLOCK_SIZE; /* Sanity */ while (dcc->bytes_sent - dcc->bytes_acked < fill_window) { /* * Check to make sure the write won't block. */ if (my_iswritable(dcc->socket, 0) <= 0) break; /* * Grab some more file. If this chokes, dont sweat it. */ if ((bytesread = read(dcc->file, tmp, DCC_BLOCK_SIZE)) <= 0) break; /* * Bug the user */ if (x_debug & DEBUG_DCC_XMIT) yell("Sending packet [%s [%s] (packet XXX) ("UINTMAX_FORMAT" bytes)]", dcc->user, dcc->othername, (uintmax_t)bytesread); /* * Attempt to write the file. If it chokes, whine. */ if (write(dcc->socket, tmp, bytesread) < bytesread) { dcc->flags |= DCC_DELETE; /* MESSAGE_FROM */ say("Outbound write() failed: %s", strerror(errno)); from_server = old_from_server; return; } dcc->bytes_sent += bytesread; /* XXX When should this ever be possible? */ if (!dcc->filesize) continue; calc_size(dcc->bytes_sent, bytes_sent, sizeof(bytes_sent)); calc_size(dcc->filesize, filesize, sizeof(filesize)); update_transfer_buffer(dcc, "(to %10s: %s of %s: %d%%)", dcc->user, bytes_sent, filesize, (int)(dcc->bytes_sent * 100.0 / dcc->filesize)); } } static void process_dcc_send (DCC_list *dcc) { if (dcc->flags & DCC_MY_OFFER) process_dcc_send_connection(dcc); else process_dcc_send_handle_ack(dcc); process_dcc_send_data(dcc); } /****************************** DCC GET ************************************/ /* * When youre recieving a DCC GET file, this is called when the sender * sends you a portion of the file. * * There's no need to stick to the packet size here. If we have more than * one in the buffer, there's no benefit in sending out acks for all. * It's probably more polite to hold back on clobbering the sender with * redundant useless acks too. */ static void process_dcc_get_data (DCC_list *dcc) { char tmp[DCC_RCV_BLOCK_SIZE+1]; intmax_t provisional_bytesread; uint32_t bytestemp; ssize_t bytesread; char bytes_read[10]; char filesize[10]; int l; l = message_from(dcc->user, LEVEL_DCC); /* Sanity check -- file size is not permitted to be omitted! */ if (!dcc->filesize) { say("DCC GET from %s lost -- Filesize is 0, no data to xfer", dcc->user); DCC_close_filesend(dcc, "GET", "TRANSFER COMPLETE"); pop_message_from(l); return; } /* Read the next chunk of the file from the remote peer */ if ((bytesread = dgets(dcc->socket, tmp, sizeof(tmp), -1)) <= 0) { if (dcc->bytes_read < dcc->filesize) { say("DCC GET to %s lost -- Remote peer closed connection", dcc->user); DCC_close_filesend(dcc, "GET", "REMOTE PEER CLOSED CONNECTION"); } else DCC_close_filesend(dcc, "GET", "TRANSFER COMPLETE"); pop_message_from(l); return; } /* Save the chunk to the local file */ if ((write(dcc->file, tmp, bytesread)) == -1) { dcc->flags |= DCC_DELETE; say("Write to local file [%d] failed: %s", dcc->file, strerror(errno)); pop_message_from(l); return; } /* Acknowledge receipt of the chunk */ dcc->bytes_read += bytesread; provisional_bytesread = dcc->bytes_read; provisional_bytesread = provisional_bytesread >> 32; provisional_bytesread = provisional_bytesread << 32; bytestemp = (uint32_t)(dcc->bytes_read - provisional_bytesread); bytestemp = htonl(bytestemp); if (write(dcc->socket, (char *)&bytestemp, sizeof(uint32_t)) == -1) { dcc->flags |= DCC_DELETE; yell("### Writing DCC GET checksum back to %s failed. Giving up.", dcc->user); pop_message_from(l); return; } /* TAKE THIS OUT IF IT CAUSES PROBLEMS */ if (dcc->bytes_read > dcc->filesize) { dcc->flags |= DCC_DELETE; yell("### DCC GET WARNING: incoming file is larger then the handshake said"); yell("### DCC GET: Closing connection"); pop_message_from(l); return; } /* Tell the user about it */ calc_size(dcc->bytes_read, bytes_read, sizeof(bytes_read)); calc_size(dcc->filesize, filesize, sizeof(filesize)); update_transfer_buffer(dcc, "(to %10s: %s of %s: %d%%)", dcc->user, bytes_read, filesize, (int)(dcc->bytes_read * 100.0 / dcc->filesize)); pop_message_from(l); } static void process_dcc_get_connected (DCC_list *dcc) { int l; lock_dcc(dcc); if (x_debug & DEBUG_SERVER_CONNECT) yell("process_dcc_get_connected: dcc [%s] now ready to write", dcc->user); if (dcc_get_connect_addrs(dcc)) { char *edesc = dcc_urlencode(dcc->description); l = message_from(dcc->user, LEVEL_DCC); if (do_hook(DCC_LOST_LIST, "%s GET %s ERROR", dcc->user, edesc)) say("DCC GET connection to %s lost", dcc->user); pop_message_from(l); new_free(&edesc); dcc->flags |= DCC_DELETE; unlock_dcc(dcc); return; } dcc_connected(dcc->socket); dcc->flags &= ~DCC_CONNECTING; unlock_dcc(dcc); } static void process_incoming_file (DCC_list *dcc) { if (dcc->flags & DCC_CONNECTING) process_dcc_get_connected(dcc); else process_dcc_get_data(dcc); } /***************************************************************************/ /***************************************************************************/ /* * This is a callback. When we want to do a CTCP DCC REJECT, we do * a WHOIS to make sure theyre still on irc, no sense sending it to * nobody in particular. When this gets called back, that means the * peer is indeed on irc, so we send them the REJECT. * * -- Changed this to be quasi-reentrant. yuck. */ static void output_reject_ctcp (int refnum, char *original, char *received) { /*char *nickname_requested;*/ char *nickname_recieved; char *type; char *description; int old_fs; /* * XXX This is, of course, a monsterous hack. */ /*nickname_requested = */next_arg(original, &original); type = next_arg(original, &original); description = next_arg(original, &original); nickname_recieved = next_arg(received, &received); if (nickname_recieved && *nickname_recieved) { /* XXX -- Ok, whatever. */ dccs_rejected++; old_fs = from_server; from_server = refnum; send_ctcp(0, nickname_recieved, "DCC", "REJECT %s %s", type, description); from_server = old_fs; } } /* * This is called when someone sends you a CTCP DCC REJECT. */ void dcc_reject (const char *from, char *type, char *args) { DCC_list * Client; char * description; int CType; int l; for (CType = 0; dcc_types[CType] != NULL; CType++) if (!my_stricmp(type, dcc_types[CType])) break; if (!dcc_types[CType]) return; l = message_from(from, LEVEL_DCC); description = next_arg(args, &args); if ((Client = dcc_searchlist(CType, from, description, description, -1))) { lock_dcc(Client); if (do_hook(DCC_LOST_LIST,"%s %s %s REJECTED", from, type, description ? description : "")) say("DCC %s:%s rejected by %s: closing", type, description ? description : "", from); Client->flags |= DCC_REJECTED; Client->flags |= DCC_DELETE; unlock_dcc(Client); } dcc_garbage_collect(); pop_message_from(l); } static void DCC_close_filesend (DCC_list *Client, const char *info, const char *errormsg) { char lame_ultrix[13]; /* should be plenty */ char lame_ultrix2[13]; char lame_ultrix3[13]; double xtime, xfer; char *encoded_description; int l; /* XXX - Can't we do this by calling calc_speed? */ xtime = time_diff(Client->starttime, get_time(NULL)); xtime -= Client->heldtime; if (Client->bytes_sent) xfer = Client->bytes_sent - Client->resume_size; else xfer = Client->bytes_read - Client->resume_size; snprintf(lame_ultrix, sizeof(lame_ultrix), "%2.4g", (xfer / 1024.0 / xtime)); /* Cant pass %g to put_it (lame ultrix/dgux), fix suggested by sheik. */ if (xfer <= 0) xfer = 1; snprintf(lame_ultrix2, sizeof(lame_ultrix2), "%2.4g", xfer / 1024.0); if (xtime <= 0) xtime = 1; snprintf(lame_ultrix3, sizeof(lame_ultrix3), "%2.6g", xtime); lock_dcc(Client); if ((Client->flags & DCC_TYPES) == DCC_FILEOFFER) encoded_description = dcc_urlencode(Client->description); else /* assume the other end encoded the filename */ encoded_description = malloc_strdup(Client->description); l = message_from(Client->user, LEVEL_DCC); if (do_hook(DCC_LOST_LIST,"%s %s %s %s %s", Client->user, info, encoded_description, lame_ultrix, errormsg)) say("DCC %s:%s [%skb] with %s completed in %s sec (%s kb/sec)", info, Client->description, lame_ultrix2, Client->user, lame_ultrix3, lame_ultrix); pop_message_from(l); new_free(&encoded_description); Client->flags |= DCC_DELETE; unlock_dcc(Client); } /* * Looks for the dcc transfer that is "current" (last recieved data) * and returns information for it */ char * DCC_get_current_transfer (void) { return DCC_current_transfer_buffer; } static void update_transfer_buffer (DCC_list *dcc, const char *format, ...) { if (!dcc_updates_status || (dcc && !dcc->updates_status)) return; if (format) { va_list args; va_start(args, format); vsnprintf(DCC_current_transfer_buffer, sizeof DCC_current_transfer_buffer, format, args); va_end(args); } else *DCC_current_transfer_buffer = 0; if (do_hook(DCC_ACTIVITY_LIST, "%ld", dcc ? dcc->refnum : -1)) update_all_status(); } static char * dcc_urlencode (const char *s) { const char * p1; char * p2; char * src; size_t srclen; char * dest; size_t destsize; src = LOCAL_COPY(s); for (p1 = s, p2 = src; *p1; p1++) { if (*p1 == '\\') continue; *p2++ = *p1; } *p2 = 0; srclen = strlen(src); destsize = srclen * 3 + 2; dest = new_malloc(destsize); transform_string(URL_xform, XFORM_ENCODE, NULL, src, srclen, dest, destsize); return dest; } static char * dcc_urldecode (const char *s) { char * p1; size_t srclen; char * dest; size_t destsize; srclen = strlen(s); destsize = srclen + 2; dest = new_malloc(destsize); transform_string(URL_xform, XFORM_DECODE, NULL, s, srclen, dest, destsize); for (p1 = dest; *p1; p1++) { if (*p1 != '.') break; *p1 = '_'; } return dest; } int wait_for_dcc (const char *descriptor) { DCC_list *dcc; char reason[1024]; int fd; if (!is_number(descriptor)) { /* FROM_MESSAGE */ yell("File descriptor (%s) should be a number", descriptor); return -1; } fd = atol(descriptor); if (!(dcc = get_dcc_by_filedesc(fd))) return -1; if (!(dcc->flags & DCC_CONNECTING)) return 0; snprintf(reason, 1024, "WAIT on DCC %s", descriptor); lock_stack_frame(); while (dcc->flags & DCC_CONNECTING) io(reason); unlock_stack_frame(); return 0; } static void fill_in_default_port (DCC_list *dcc) { if (default_dcc_port) { char dccref[10]; char *newport = NULL; snprintf(dccref, sizeof(dccref), "%ld", dcc->refnum); newport = expand_alias(default_dcc_port, dccref); dcc->want_port = (unsigned short)my_atol(newport); new_free(&newport); } } /* * This stuff doesnt conform to the protocol. * Thanks mirc for disregarding the protocol. */ #ifdef MIRC_BROKEN_DCC_RESUME /* * When the peer demands DCC RESUME * We send out a DCC ACCEPT */ static void dcc_getfile_resume_demanded (const char *user, char *filename, char *port, char *offset) { DCC_list *Client; int proto; char *realfilename = filename; if (!get_int_var(MIRC_BROKEN_DCC_RESUME_VAR)) return; if (x_debug & DEBUG_DCC_XMIT) yell("GOT DCC RESUME REQUEST from [%s] [%s|%s|%s]", user, filename, port, offset); if (!strcmp(filename, "file.ext")) filename = NULL; if (!(Client = dcc_searchlist(DCC_FILEOFFER, user, filename, port, 0))) { if (x_debug & DEBUG_DCC_XMIT) yell("Resume request that doesnt exist. Hmmm."); return; /* Its a fake. */ } if (!offset) return; /* Its a fake */ Client->bytes_acked = Client->bytes_sent = Client->resume_size = STR2INT(offset); Client->bytes_read = 0; /* Just in case we have to fool the protocol enforcement. */ if ((proto = get_server_protocol_state(from_server)) >= 0) set_server_protocol_state(from_server, 0); send_ctcp(1, user, "DCC", "ACCEPT %s %s %s", realfilename, port, offset); if (proto >= 0) set_server_protocol_state(from_server, proto); /* Wait for them to open the connection */ } /* * When we get the DCC ACCEPT * We start the connection */ static void dcc_getfile_resume_start (const char *nick, char *filename, char *port, char *offset) { DCC_list *Client; if (!get_int_var(MIRC_BROKEN_DCC_RESUME_VAR)) return; if (x_debug & DEBUG_DCC_XMIT) yell("GOT CONFIRMATION FOR DCC RESUME from [%s] [%s]", nick, filename); if (!strcmp(filename, "file.ext")) filename = NULL; if (!(Client = dcc_searchlist(DCC_FILEREAD, nick, filename, port, 0))) return; /* Its fake. */ if (!(Client->flags & DCC_RESUME_REQ)) { if (x_debug & DEBUG_DCC_XMIT) yell("Unsolicited DCC ACCEPT from [%s] [%s]", nick, filename); return; } Client->flags |= DCC_TWOCLIENTS; dcc_connect(Client); } #endif char * dccctl (char *input) { long ref; char * listc; int len; DCC_list * client; char * retval = NULL; size_t clue = 0; GET_FUNC_ARG(listc, input); len = strlen(listc); if (!my_strnicmp(listc, "REFNUMS", len)) { for (client = ClientList; client; client = client->next) malloc_strcat_word_c(&retval, space, ltoa(client->refnum), DWORD_DWORDS, &clue); } else if (!my_strnicmp(listc, "REFBASE", len)) { int oldref = dcc_refnum; if (input && *input) GET_INT_ARG(dcc_refnum, input); RETURN_INT(oldref); } else if (!my_strnicmp(listc, "GET", len)) { GET_INT_ARG(ref, input); if (!(client = get_dcc_by_refnum(ref))) RETURN_EMPTY; GET_FUNC_ARG(listc, input); len = strlen(listc); if (!my_strnicmp(listc, "REFNUM", len)) { RETURN_INT(client->refnum); } else if (!my_strnicmp(listc, "TYPE", len)) { RETURN_STR(dcc_types[client->flags & DCC_TYPES]); } else if (!my_strnicmp(listc, "DESCRIPTION", len)) { RETURN_STR(client->description); } else if (!my_strnicmp(listc, "FILENAME", len)) { RETURN_STR(client->local_filename); } else if (!my_strnicmp(listc, "USER", len)) { RETURN_STR(client->user); } else if (!my_strnicmp(listc, "USERHOST", len)) { RETURN_STR(client->userhost); } else if (!my_strnicmp(listc, "OTHERNAME", len)) { RETURN_STR(client->othername); } else if (!my_strnicmp(listc, "SIZE", len)) { return INT2STR(client->filesize); } else if (!my_strnicmp(listc, "FILESIZE", len)) { /* DEPRECATED */ return INT2STR(client->filesize); } else if (!my_strnicmp(listc, "RESUMESIZE", len)) { return INT2STR(client->resume_size); } else if (!my_strnicmp(listc, "READBYTES", len)) { return INT2STR(client->bytes_read); } else if (!my_strnicmp(listc, "SENTBYTES", len)) { return INT2STR(client->bytes_sent); } else if (!my_strnicmp(listc, "SERVER", len)) { RETURN_INT(client->server); } else if (!my_strnicmp(listc, "LOCKED", len)) { RETURN_INT(client->locked); } else if (!my_strnicmp(listc, "HELD", len)) { RETURN_INT(client->held); } else if (!my_strnicmp(listc, "HELDTIME", len)) { RETURN_FLOAT(client->heldtime); } else if (!my_strnicmp(listc, "QUOTED", len)) { RETURN_INT(client->flags & DCC_QUOTED && 1); } else if (!my_strnicmp(listc, "PACKET_SIZE", len)) { RETURN_INT(client->packet_size); } else if (!my_strnicmp(listc, "FULL_LINE_BUFFER", len)) { RETURN_INT(client->full_line_buffer); } else if (!my_strnicmp(listc, "FLAGS", len)) { /* This is pretty much a crock. */ RETURN_INT(client->flags); } else if (!my_strnicmp(listc, "LASTTIME", len)) { malloc_strcat_word_c(&retval, space, ltoa(client->lasttime.tv_sec), DWORD_NO, &clue); malloc_strcat_word_c(&retval, space, ltoa(client->lasttime.tv_usec), DWORD_NO, &clue); } else if (!my_strnicmp(listc, "STARTTIME", len)) { malloc_strcat_word_c(&retval, space, ltoa(client->starttime.tv_sec), DWORD_NO, &clue); malloc_strcat_word_c(&retval, space, ltoa(client->starttime.tv_usec), DWORD_NO, &clue); } else if (!my_strnicmp(listc, "HOLDTIME", len)) { malloc_strcat_word_c(&retval, space, ltoa(client->holdtime.tv_sec), DWORD_NO, &clue); malloc_strcat_word_c(&retval, space, ltoa(client->holdtime.tv_usec), DWORD_NO, &clue); } else if (!my_strnicmp(listc, "OFFERADDR", len)) { char host[1025], port[25]; if (inet_ntostr(&client->offer, host, sizeof(host), port, sizeof(port), NI_NUMERICHOST)) RETURN_EMPTY; malloc_strcat_word_c(&retval, space, host, DWORD_NO, &clue); malloc_strcat_word_c(&retval, space, port, DWORD_NO, &clue); } else if (!my_strnicmp(listc, "REMADDR", len)) { char host[1025], port[25]; if (inet_ntostr(&client->peer_sockaddr, host, sizeof(host), port, sizeof(port), NI_NUMERICHOST)) RETURN_EMPTY; malloc_strcat_word_c(&retval, space, host, DWORD_NO, &clue); malloc_strcat_word_c(&retval, space, port, DWORD_NO, &clue); } else if (!my_strnicmp(listc, "LOCADDR", len)) { char host[1025], port[25]; if (inet_ntostr(&client->local_sockaddr, host, sizeof(host), port, sizeof(port), NI_NUMERICHOST)) RETURN_EMPTY; malloc_strcat_word_c(&retval, space, host, DWORD_NO, &clue); malloc_strcat_word_c(&retval, space, port, DWORD_NO, &clue); } else if (!my_strnicmp(listc, "READABLE", len)) { int retint; retint = my_isreadable(client->socket, 0) > 0; RETURN_INT(retint); } else if (!my_strnicmp(listc, "WRITABLE", len)) { int retint; retint = my_iswritable(client->socket, 0) > 0; RETURN_INT(retint); } else if (!my_strnicmp(listc, "UPDATES_STATUS", len)) { RETURN_INT(client->updates_status); } else if (!my_strnicmp(listc, "WANT_PORT", len)) { RETURN_INT(client->want_port); } else { RETURN_EMPTY; } } else if (!my_strnicmp(listc, "SET", len)) { GET_INT_ARG(ref, input); if (!(client = get_dcc_by_refnum(ref))) RETURN_EMPTY; GET_FUNC_ARG(listc, input); len = strlen(listc); if (!my_strnicmp(listc, "REFNUM", len)) { long newref; GET_INT_ARG(newref, input); client->refnum = newref; RETURN_INT(1); } else if (!my_strnicmp(listc, "DESCRIPTION", len)) { malloc_strcpy(&client->description, input); } else if (!my_strnicmp(listc, "FILENAME", len)) { malloc_strcpy(&client->local_filename, input); } else if (!my_strnicmp(listc, "USER", len)) { malloc_strcpy(&client->user, input); } else if (!my_strnicmp(listc, "USERHOST", len)) { malloc_strcpy(&client->userhost, input); } else if (!my_strnicmp(listc, "OTHERNAME", len)) { malloc_strcpy(&client->othername, input); } else if (!my_strnicmp(listc, "WANT_PORT", len)) { long newref; GET_INT_ARG(newref, input); client->want_port = (unsigned short)newref; RETURN_INT(1); } else if (!my_strnicmp(listc, "HELD", len)) { long hold, held; GET_INT_ARG(hold, input); if (hold) held = dcc_hold(client); else held = dcc_unhold(client); RETURN_INT(held); } else if (!my_strnicmp(listc, "QUOTED", len)) { long quoted; GET_INT_ARG(quoted, input); if (quoted) client->flags |= DCC_QUOTED; else client->flags &= ~DCC_QUOTED; } else if (!my_strnicmp(listc, "FULL_LINE_BUFFER", len)) { long buffered; GET_INT_ARG(buffered, input); if (buffered) client->full_line_buffer = 1; else client->full_line_buffer = 0; } else if (!my_strnicmp(listc, "PACKET_SIZE", len)) { long packet_size; /* Validation? */ GET_INT_ARG(packet_size, input); client->packet_size = packet_size; } else if (!my_strnicmp(listc, "OFFERADDR", len)) { char *host, *port; SSu a; GET_FUNC_ARG(host, input); GET_FUNC_ARG(port, input); a.sa.sa_family = AF_UNSPEC; if ((client->flags & DCC_ACTIVE) || inet_strton(host, port, &a, AI_ADDRCONFIG)) RETURN_EMPTY; memcpy(&client->offer.ss, &a.ss, sizeof(a.ss)); RETURN_INT(1); } else if (!my_strnicmp(listc, "UPDATES_STATUS", len)) { long updates; GET_INT_ARG(updates, input); client->updates_status = updates; RETURN_INT(1); } else { RETURN_EMPTY; } RETURN_INT(1); } else if (!my_strnicmp(listc, "TYPEMATCH", len)) { for (client = ClientList; client; client = client->next) if (wild_match(input, dcc_types[client->flags & DCC_TYPES])) malloc_strcat_word_c(&retval, space, ltoa(client->refnum), DWORD_NO, &clue); } else if (!my_strnicmp(listc, "DESCMATCH", len)) { for (client = ClientList; client; client = client->next) if (wild_match(input, client->description ? client->description : EMPTY)) malloc_strcat_word_c(&retval, space, ltoa(client->refnum), DWORD_NO, &clue); } else if (!my_strnicmp(listc, "FILEMATCH", len)) { for (client = ClientList; client; client = client->next) if (wild_match(input, client->local_filename ? client->local_filename : EMPTY)) malloc_strcat_word_c(&retval, space, ltoa(client->refnum), DWORD_NO, &clue); } else if (!my_strnicmp(listc, "USERMATCH", len)) { for (client = ClientList; client; client = client->next) if (wild_match(input, client->user ? client->user : EMPTY)) malloc_strcat_word_c(&retval, space, ltoa(client->refnum), DWORD_NO, &clue); } else if (!my_strnicmp(listc, "USERHOSTMATCH", len)) { for (client = ClientList; client; client = client->next) if (wild_match(input, client->userhost ? client->userhost : EMPTY)) malloc_strcat_word_c(&retval, space, ltoa(client->refnum), DWORD_NO, &clue); } else if (!my_strnicmp(listc, "OTHERMATCH", len)) { for (client = ClientList; client; client = client->next) if (wild_match(input, client->othername ? client->othername : EMPTY)) malloc_strcat_word_c(&retval, space, ltoa(client->refnum), DWORD_NO, &clue); } else if (!my_strnicmp(listc, "LOCKED", len)) { for (client = ClientList; client; client = client->next) if (client->locked) malloc_strcat_word_c(&retval, space, ltoa(client->refnum), DWORD_NO, &clue); } else if (!my_strnicmp(listc, "HELD", len)) { for (client = ClientList; client; client = client->next) if (client->held) malloc_strcat_word_c(&retval, space, ltoa(client->refnum), DWORD_NO, &clue); } else if (!my_strnicmp(listc, "UNHELD", len)) { for (client = ClientList; client; client = client->next) if (!client->held) malloc_strcat_word_c(&retval, space, ltoa(client->refnum), DWORD_NO, &clue); } else if (!my_strnicmp(listc, "READABLES", len)) { for (client = ClientList; client; client = client->next) { if (my_isreadable(client->socket, 0)) malloc_strcat_word_c(&retval, space, ltoa(client->refnum), DWORD_NO, &clue); } } else if (!my_strnicmp(listc, "WRITABLES", len)) { for (client = ClientList; client; client = client->next) { if (my_iswritable(client->socket, 0)) malloc_strcat_word_c(&retval, space, ltoa(client->refnum), DWORD_NO, &clue); } } else if (!my_strnicmp(listc, "UPDATES_STATUS", len)) { int oldval; oldval = dcc_updates_status; if (input && *input) { int newval; GET_INT_ARG(newval, input); dcc_updates_status = newval; } RETURN_INT(oldval); } else if (!my_strnicmp(listc, "DEFAULT_PORT", len)) { if (empty(input)) RETURN_STR(default_dcc_port); else malloc_strcpy(&default_dcc_port, input); RETURN_INT(1); } else if (!my_strnicmp(listc, "FD_TO_REFNUM", len)) { int fd; GET_INT_ARG(fd, input); for (client = ClientList; client; client = client->next) { if (client->socket == fd) RETURN_INT(client->refnum); } RETURN_INT(-1); } else RETURN_EMPTY; RETURN_MSTR(retval); } #if 0 void help_topics_dcc (FILE *f) { int x; for (x = 0; dcc_commands[x].name; x++) fprintf(f, "dcc %s\n", dcc_commands[x].name); } #endif