/* dcc-server.c : irssi Copyright (C) 2003 Mark Trumbull This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "module.h" #include #include #include #include #include #include #include #include #include void sig_dccget_connected(GET_DCC_REC *dcc); GET_DCC_REC *dcc_get_create(IRC_SERVER_REC *server, CHAT_DCC_REC *chat, const char *nick, const char *arg); void dcc_chat_input(CHAT_DCC_REC *dcc); CHAT_DCC_REC *dcc_chat_create(IRC_SERVER_REC *server, CHAT_DCC_REC *chat, const char *nick, const char *arg); static void sig_dcc_destroyed(SERVER_DCC_REC *dcc) { if (!IS_DCC_SERVER(dcc)) return; if (dcc->sendbuf != NULL) net_sendbuffer_destroy(dcc->sendbuf, FALSE); } /* Start listening for incoming connections */ static GIOChannel *dcc_listen_port(GIOChannel *iface, IPADDR *ip, int port) { if (net_getsockname(iface, ip, NULL) == -1) return NULL; if (IPADDR_IS_V6(ip)) return net_listen(NULL, &port); else return net_listen(&ip4_any, &port); } /* input function: DCC SERVER received some data.. */ static void dcc_server_input(SERVER_DCC_REC *dcc) { char *str; int ret; g_return_if_fail(IS_DCC_SERVER(dcc)); do { ret = net_sendbuffer_receive_line(dcc->sendbuf, &str, 1); if (ret == -1) { /* connection lost */ dcc_close(DCC(dcc)); break; } if (ret > 0) { dcc->transfd += ret; signal_emit("dcc server message", 2, dcc, str); } if (dcc->connection_established) { /* We set handle to NULL first because the new (chat/get) is using the same */ /* handle and we don't want dcc_close to disconnect it.*/ dcc->handle = NULL; dcc_close(DCC(dcc)); break; } } while (ret > 0); } static void dcc_server_update_flags(SERVER_DCC_REC *dcc, const char *flags) { g_return_if_fail(dcc != NULL); g_return_if_fail(IS_DCC_SERVER(dcc)); if (*flags == '+' || *flags == '-') { const char *ptr = flags + 1; unsigned int value = (*flags == '+') ? 1 : 0; while (*ptr) { if (*ptr == 's' || *ptr == 'S') { dcc->accept_send = value; } else if (*ptr == 'c' || *ptr == 'C') { dcc->accept_chat = value; } else if (*ptr == 'f' || *ptr == 'F') { dcc->accept_fserve = value; } ptr++; } } } /* Initialize DCC record */ static void dcc_init_server_rec(SERVER_DCC_REC *dcc, IRC_SERVER_REC *server, const char *mynick, const char *servertag) { g_return_if_fail(dcc != NULL); g_return_if_fail(IS_DCC_SERVER(dcc)); MODULE_DATA_INIT(dcc); dcc->created = time(NULL); dcc->chat = NULL; dcc->arg = NULL; dcc->nick = NULL; dcc->tagconn = dcc->tagread = dcc->tagwrite = -1; dcc->server = server; dcc->mynick = g_strdup(mynick); dcc->servertag = g_strdup(servertag); dcc_conns = g_slist_append(dcc_conns, dcc); signal_emit("dcc created", 1, dcc); } static SERVER_DCC_REC *dcc_server_create(IRC_SERVER_REC *server, const char *flags) { SERVER_DCC_REC *dcc; dcc = g_new0(SERVER_DCC_REC, 1); dcc->orig_type = dcc->type = DCC_SERVER_TYPE; dcc_server_update_flags(dcc, flags); dcc_init_server_rec(dcc, server, dcc->mynick, dcc->servertag); return dcc; } static SERVER_DCC_REC *dcc_server_clone(SERVER_DCC_REC *dcc) { SERVER_DCC_REC *newdcc; g_return_val_if_fail(IS_DCC_SERVER(dcc), NULL); newdcc = g_new0(SERVER_DCC_REC, 1); newdcc->orig_type = newdcc->type = DCC_SERVER_TYPE; newdcc->accept_send = dcc->accept_send; newdcc->accept_chat = dcc->accept_chat; newdcc->accept_fserve = dcc->accept_fserve; dcc_init_server_rec(newdcc, dcc->server, dcc->mynick, dcc->servertag); return newdcc; } /* input function: DCC SERVER - someone tried to connect to our socket */ static void dcc_server_listen(SERVER_DCC_REC *dcc) { SERVER_DCC_REC *newdcc; IPADDR ip; GIOChannel *handle; int port; g_return_if_fail(IS_DCC_SERVER(dcc)); /* accept connection */ handle = net_accept(dcc->handle, &ip, &port); if (handle == NULL) return; /* Create a new DCC SERVER to handle this connection */ newdcc = dcc_server_clone(dcc); newdcc->starttime = time(NULL); newdcc->handle = handle; newdcc->sendbuf = net_sendbuffer_create(handle, 0); memcpy(&newdcc->addr, &ip, sizeof(IPADDR)); net_ip2host(&newdcc->addr, newdcc->addrstr); newdcc->port = port; newdcc->tagread = g_input_add(handle, G_INPUT_READ, (GInputFunction) dcc_server_input, newdcc); signal_emit("dcc connected", 1, newdcc); } /* DCC SERVER: text received */ static void dcc_server_msg(SERVER_DCC_REC *dcc, const char *msg) { g_return_if_fail(IS_DCC_SERVER(dcc)); g_return_if_fail(msg != NULL); /* Check for CHAT protocol */ if (g_ascii_strncasecmp(msg, "100 ", 4) == 0) { msg += 4; /* Check if this server is accepting chat requests.*/ if (dcc->accept_chat) { /* Connect and start DCC Chat */ char *str; CHAT_DCC_REC *dccchat = dcc_chat_create(dcc->server, NULL, msg, "chat"); dccchat->starttime = time(NULL); dccchat->handle = dcc->handle; dccchat->sendbuf = net_sendbuffer_create(dccchat->handle, 0); memcpy(&dccchat->addr, &dcc->addr, sizeof(IPADDR)); net_ip2host(&dccchat->addr, dccchat->addrstr); dccchat->port = dcc->port; dccchat->tagread = g_input_add(dccchat->handle, G_INPUT_READ, (GInputFunction) dcc_chat_input, dccchat); dcc->connection_established = 1; signal_emit("dcc connected", 1, dccchat); str = g_strdup_printf("101 %s\n", (dccchat->server) ? dccchat->server->nick : "??"); net_sendbuffer_send(dccchat->sendbuf, str, strlen(str)); g_free(str); } } /* Check for FSERVE protocol */ if (g_ascii_strncasecmp(msg, "110 ", 4) == 0) { msg += 4; /* Check if this server is accepting fserve requests.*/ if (dcc->accept_fserve) { /* TODO - Connect and start DCC Fserve */ } } /* Check for SEND protocol */ if (g_ascii_strncasecmp(msg, "120 ", 4) == 0) { msg += 4; /* Check if this server is accepting send requests.*/ if (dcc->accept_send) { /* Connect and start DCC Send */ GET_DCC_REC *dccget; char **params, *fname, *nick; int paramcount, len, quoted = FALSE; uoff_t size; /* 120 clientnickname filesize filename */ params = g_strsplit(msg, " ", -1); paramcount = g_strv_length(params); if (paramcount < 3) { g_strfreev(params); signal_stop(); return; } nick = params[0]; size = str_to_uofft(params[1]); fname = g_strjoinv(" ", ¶ms[2]); len = strlen(fname); if (len > 1 && *fname == '"' && fname[len-1] == '"') { /* "file name" - MIRC sends filenames with spaces like this */ fname[len-1] = '\0'; memmove(fname, fname+1, len); quoted = TRUE; } dccget = dcc_get_create(dcc->server, NULL, nick, fname); dccget->handle = dcc->handle; dccget->target = g_strdup(dcc->server ? dcc->server->nick : "??"); memcpy(&dccget->addr, &dcc->addr, sizeof(dcc->addr)); if (dccget->addr.family == AF_INET) { net_ip2host(&dccget->addr, dccget->addrstr); } else { /* with IPv6, show it to us as it was sent */ memcpy(dccget->addrstr, dcc->addrstr, sizeof(dccget->addrstr)); } dccget->port = dcc->port; dccget->size = size; dccget->file_quoted = quoted; dccget->from_dccserver = 1; dcc->connection_established = 1; signal_emit("dcc request", 2, dccget, dccget->addrstr); g_strfreev(params); g_free(fname); } } signal_stop(); } SERVER_DCC_REC *dcc_server_find_port(const char *port_str) { GSList *tmp; unsigned int port = 0; g_return_val_if_fail(port_str != NULL, NULL); port = atoi(port_str); for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) { SERVER_DCC_REC *dcc = tmp->data; if (IS_DCC_SERVER(dcc) && dcc->port == port) return dcc; } return NULL; } /* SYNTAX: DCC SERVER [+|-scf] [port] */ static void cmd_dcc_server(const char *data, IRC_SERVER_REC *server) { void *free_arg; GIOChannel *handle; SERVER_DCC_REC *dcc; IPADDR own_ip; char *flags, *port; g_return_if_fail(data != NULL); if (!cmd_get_params(data, &free_arg, 2, &flags, &port)) return; dcc = dcc_server_find_port(port); if (dcc != NULL) { /* Server is already running, update it */ dcc_server_update_flags(dcc, flags); cmd_params_free(free_arg); return; } /* start listening */ if (!IS_IRC_SERVER(server) || !server->connected) { cmd_param_error(CMDERR_NOT_CONNECTED); } handle = dcc_listen_port(net_sendbuffer_handle(server->handle), &own_ip, atoi(port)); if (handle == NULL) { cmd_param_error(CMDERR_ERRNO); } dcc = dcc_server_create(server, flags); dcc->handle = handle; dcc->port = atoi(port); dcc->tagconn = g_input_add(dcc->handle, G_INPUT_READ, (GInputFunction) dcc_server_listen, dcc); signal_emit("dcc server started", 1, dcc); cmd_params_free(free_arg); } /* DCC CLOSE SERVER */ static void cmd_dcc_close(char *data, SERVER_REC *server) { GSList *tmp, *next; char *port_str; void *free_arg; int found, port; g_return_if_fail(data != NULL); if (g_ascii_strncasecmp(data, "SERVER ", 7) != 0 || !cmd_get_params(data, &free_arg, 2, NULL, &port_str)) { return; } if (*port_str == '\0') { cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); } port = atoi(port_str); found = FALSE; for (tmp = dcc_conns; tmp != NULL; tmp = next) { SERVER_DCC_REC *dcc = tmp->data; next = tmp->next; if (IS_DCC_SERVER(dcc) && dcc->port == port) { found = TRUE; dcc_close(DCC(dcc)); } } if (found) { signal_stop(); } cmd_params_free(free_arg); } void dcc_server_init(void) { dcc_register_type("SERVER"); command_bind("dcc server", NULL, (SIGNAL_FUNC) cmd_dcc_server); command_bind("dcc close", NULL, (SIGNAL_FUNC) cmd_dcc_close); signal_add("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed); signal_add_first("dcc server message", (SIGNAL_FUNC) dcc_server_msg); } void dcc_server_deinit(void) { dcc_unregister_type("SERVER"); command_unbind("dcc server", (SIGNAL_FUNC) cmd_dcc_server); command_unbind("dcc close", (SIGNAL_FUNC) cmd_dcc_close); signal_remove("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed); signal_remove("dcc server message", (SIGNAL_FUNC) dcc_server_msg); }