summaryrefslogtreecommitdiffstats
path: root/src/irc/dcc/dcc.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/irc/dcc/dcc.c')
-rw-r--r--src/irc/dcc/dcc.c600
1 files changed, 600 insertions, 0 deletions
diff --git a/src/irc/dcc/dcc.c b/src/irc/dcc/dcc.c
new file mode 100644
index 0000000..bb1c6c2
--- /dev/null
+++ b/src/irc/dcc/dcc.c
@@ -0,0 +1,600 @@
+/*
+ dcc.c : irssi
+
+ Copyright (C) 1999-2001 Timo Sirainen
+
+ 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 <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/network.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/ignore.h>
+#include <irssi/src/core/levels.h>
+
+#include <irssi/src/irc/core/irc-servers.h>
+#include <irssi/src/core/servers-setup.h>
+
+#include <irssi/src/irc/dcc/dcc-chat.h>
+#include <irssi/src/irc/dcc/dcc-get.h>
+#include <irssi/src/irc/dcc/dcc-send.h>
+#include <irssi/src/irc/dcc/dcc-server.h>
+
+void dcc_resume_init(void);
+void dcc_resume_deinit(void);
+
+void dcc_autoget_init(void);
+void dcc_autoget_deinit(void);
+
+GSList *dcc_conns;
+
+static GSList *dcc_types;
+static int dcc_timeouttag;
+
+void dcc_register_type(const char *type)
+{
+ dcc_types = g_slist_append(dcc_types, g_strdup(type));
+}
+
+void dcc_unregister_type(const char *type)
+{
+ GSList *pos;
+
+ pos = i_slist_find_string(dcc_types, type);
+ if (pos != NULL) {
+ void *tmp = pos->data;
+ dcc_types = g_slist_remove(dcc_types, pos->data);
+ g_free(tmp);
+ }
+}
+
+int dcc_str2type(const char *str)
+{
+ if (i_slist_find_string(dcc_types, str) == NULL)
+ return -1;
+
+ return module_get_uniq_id_str("DCC", str);
+}
+
+/* Initialize DCC record */
+void dcc_init_rec(DCC_REC *dcc, IRC_SERVER_REC *server, CHAT_DCC_REC *chat,
+ const char *nick, const char *arg)
+{
+ g_return_if_fail(dcc != NULL);
+ g_return_if_fail(nick != NULL);
+ g_return_if_fail(arg != NULL);
+
+ MODULE_DATA_INIT(dcc);
+ dcc->created = time(NULL);
+ dcc->chat = chat;
+ dcc->arg = g_strdup(arg);
+ dcc->nick = g_strdup(nick);
+ dcc->tagconn = dcc->tagread = dcc->tagwrite = -1;
+ dcc->server = server;
+ dcc->mynick = g_strdup(server != NULL ? server->nick :
+ chat != NULL ? chat->nick : "??");
+
+ dcc->servertag = server != NULL ? g_strdup(server->tag) :
+ (chat == NULL ? NULL : g_strdup(chat->servertag));
+
+ dcc->pasv_id = -1; /* Not a passive DCC */
+
+ dcc_conns = g_slist_append(dcc_conns, dcc);
+ signal_emit("dcc created", 1, dcc);
+}
+
+/* Destroy DCC record */
+void dcc_destroy(DCC_REC *dcc)
+{
+ g_return_if_fail(dcc != NULL);
+ if (dcc->destroyed) return;
+
+ dcc_conns = g_slist_remove(dcc_conns, dcc);
+
+ dcc->destroyed = TRUE;
+ signal_emit("dcc destroyed", 1, dcc);
+
+ if (dcc->handle != NULL) net_disconnect(dcc->handle);
+ if (dcc->tagconn != -1) g_source_remove(dcc->tagconn);
+ if (dcc->tagread != -1) g_source_remove(dcc->tagread);
+ if (dcc->tagwrite != -1) g_source_remove(dcc->tagwrite);
+
+ MODULE_DATA_DEINIT(dcc);
+ g_free_not_null(dcc->servertag);
+ g_free_not_null(dcc->target);
+ g_free(dcc->mynick);
+ g_free(dcc->nick);
+ g_free(dcc->arg);
+ g_free(dcc);
+}
+
+DCC_REC *dcc_find_request_latest(int type)
+{
+ DCC_REC *latest;
+ GSList *tmp;
+
+ latest = NULL;
+ for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) {
+ DCC_REC *dcc = tmp->data;
+
+ if (dcc->type == type && dcc_is_waiting_user(dcc))
+ latest = dcc;
+ }
+
+ return latest;
+}
+
+DCC_REC *dcc_find_request(int type, const char *nick, const char *arg)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) {
+ DCC_REC *dcc = tmp->data;
+
+ if (dcc->type == type && !dcc_is_connected(dcc) &&
+ g_ascii_strcasecmp(dcc->nick, nick) == 0 &&
+ (arg == NULL || g_strcmp0(dcc->arg, arg) == 0))
+ return dcc;
+ }
+
+ return NULL;
+}
+
+void dcc_ip2str(IPADDR *ip, char *host)
+{
+ IPADDR temp_ip;
+ guint32 addr;
+
+ if (*settings_get_str("dcc_own_ip") != '\0') {
+ /* overridden IP address */
+ net_host2ip(settings_get_str("dcc_own_ip"), &temp_ip);
+ ip = &temp_ip;
+ }
+
+ if (IPADDR_IS_V6(ip)) {
+ /* IPv6 */
+ net_ip2host(ip, host);
+ } else {
+ memcpy(&addr, &ip->ip, sizeof(addr));
+ g_snprintf(host, MAX_IP_LEN, "%lu",
+ (unsigned long) htonl(addr));
+ }
+}
+
+void dcc_str2ip(const char *str, IPADDR *ip)
+{
+ guint32 addr;
+
+ if (strchr(str, ':') == NULL) {
+ /* normal IPv4 address in 32bit number form */
+ addr = strtoul(str, NULL, 10);
+ ip->family = AF_INET;
+ addr = (guint32) ntohl(addr);
+ memcpy(&ip->ip, &addr, sizeof(addr));
+ } else {
+ /* IPv6 - in standard form */
+ net_host2ip(str, ip);
+ }
+}
+
+/* Start listening for incoming connections */
+GIOChannel *dcc_listen(GIOChannel *iface, IPADDR *ip, int *port)
+{
+ GIOChannel *handle;
+ IPADDR *listen_ip = NULL;
+ const char *dcc_port, *p, *own_ip;
+ int first, last;
+
+ if (net_getsockname(iface, ip, NULL) == -1)
+ return NULL;
+
+ /* figure out if we want to listen in IPv4 address or in "any" address,
+ which may mean IPv4+IPv6 or just IPv6 depending on OS. */
+ own_ip = settings_get_str("dcc_own_ip");
+ if (*own_ip != '\0') {
+ if (is_ipv4_address(own_ip))
+ listen_ip = &ip4_any;
+ } else {
+ if (!IPADDR_IS_V6(ip))
+ listen_ip = &ip4_any;
+ }
+
+ /* get first port */
+ dcc_port = settings_get_str("dcc_port");
+ first = atoi(dcc_port);
+ if (first == 0) {
+ /* random port */
+ *port = 0;
+ return net_listen(listen_ip, port);
+ }
+
+ /* get last port */
+ p = strchr(dcc_port, ' ');
+ if (p == NULL) p = strchr(dcc_port, '-');
+
+ dcc_port = p;
+ if (dcc_port == NULL)
+ last = first;
+ else {
+ last = atoi(dcc_port+1);
+ if (last == 0)
+ last = first;
+ }
+
+ /* use the first available port */
+ for (*port = first; *port <= last; (*port)++) {
+ handle = net_listen(listen_ip, port);
+ if (handle != NULL)
+ return handle;
+ }
+
+ return NULL;
+}
+
+/* Connect to specified IP address using the correct own_ip. */
+GIOChannel *dcc_connect_ip(IPADDR *ip, int port)
+{
+ IPADDR *own_ip, temp_ip;
+ const char *own_ip_str;
+ GIOChannel *handle;
+
+ own_ip_str = settings_get_str("dcc_own_ip");
+ own_ip = NULL;
+ if (*own_ip_str != '\0') {
+ /* use the specified interface for connecting */
+ net_host2ip(own_ip_str, &temp_ip);
+ if (IPADDR_IS_V6(ip) == IPADDR_IS_V6(&temp_ip))
+ own_ip = &temp_ip;
+ }
+
+ if (own_ip == NULL)
+ own_ip = IPADDR_IS_V6(ip) ? source_host_ip6 : source_host_ip4;
+
+ handle = net_connect_ip(ip, port, own_ip);
+ if (handle == NULL && errno == EADDRNOTAVAIL && own_ip != NULL) {
+ /* dcc_own_ip is external address */
+ own_ip = IPADDR_IS_V6(ip) ? source_host_ip6 : source_host_ip4;
+ handle = net_connect_ip(ip, port, own_ip);
+ }
+ return handle;
+}
+
+/* Server connected - update server for DCC records that have
+ the same server tag */
+static void sig_connected(IRC_SERVER_REC *server)
+{
+ GSList *tmp;
+
+ g_return_if_fail(server != NULL);
+
+ for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) {
+ DCC_REC *dcc = tmp->data;
+
+ if (dcc->server == NULL && dcc->servertag != NULL &&
+ g_ascii_strcasecmp(dcc->servertag, server->tag) == 0) {
+ dcc->server = server;
+ g_free(dcc->mynick);
+ dcc->mynick = g_strdup(server->nick);
+ }
+ }
+}
+
+/* Server disconnected, remove it from all DCC records */
+static void sig_server_disconnected(IRC_SERVER_REC *server)
+{
+ GSList *tmp;
+
+ g_return_if_fail(server != NULL);
+
+ for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) {
+ DCC_REC *dcc = tmp->data;
+
+ if (dcc->server == server)
+ dcc->server = NULL;
+ }
+}
+
+/* Your nick changed, change nick in all DCC records */
+static void sig_server_nick_changed(IRC_SERVER_REC *server)
+{
+ GSList *tmp;
+
+ if (!IS_IRC_SERVER(server)) return;
+
+ for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) {
+ DCC_REC *dcc = tmp->data;
+
+ if (dcc->server == server) {
+ g_free(dcc->mynick);
+ dcc->mynick = g_strdup(server->nick);
+ }
+ }
+}
+
+/* handle emitting "ctcp msg dcc" signal - don't use it directly because
+ with /IGNORE * CTCPS we'd be ignored */
+static void ctcp_msg(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr, const char *target)
+{
+ if (g_ascii_strncasecmp(data, "dcc ", 4) != 0)
+ return;
+ data += 4;
+
+ signal_emit("ctcp msg dcc", 5, server, data, nick, addr, target);
+ signal_stop();
+}
+
+/* handle emitting "ctcp reply dcc" signal - don't use it directly because
+ with /IGNORE * CTCPS we'd be ignored */
+static void ctcp_reply(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr, const char *target)
+{
+ if (g_ascii_strncasecmp(data, "dcc ", 4) != 0)
+ return;
+ data += 4;
+
+ signal_emit("ctcp reply dcc", 5, server, data, nick, addr, target);
+ signal_stop();
+}
+
+/* Handle incoming DCC CTCP messages - either from IRC server or DCC chat */
+static void ctcp_msg_dcc(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr,
+ const char *target, DCC_REC *chat)
+{
+ char *args, *str;
+
+ if (ignore_check(SERVER(server), nick, addr, target, data, MSGLEVEL_DCC))
+ return;
+
+ str = g_strconcat("ctcp msg dcc ", data, NULL);
+ args = strchr(str+13, ' ');
+ if (args != NULL) *args++ = '\0'; else args = "";
+
+ ascii_strdown(str+13);
+ if (!signal_emit(str, 6, server, args, nick, addr, target, chat)) {
+ signal_emit("default ctcp msg dcc", 6,
+ server, data, nick, addr, target, chat);
+ }
+ g_free(str);
+}
+
+/* Handle incoming DCC CTCP replies - either from IRC server or DCC chat */
+static void ctcp_reply_dcc(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr,
+ const char *target)
+{
+ char *args, *str;
+
+ if (ignore_check(SERVER(server), nick, addr, target, data, MSGLEVEL_DCC))
+ return;
+
+ str = g_strconcat("ctcp reply dcc ", data, NULL);
+ args = strchr(str+15, ' ');
+ if (args != NULL) *args++ = '\0'; else args = "";
+
+ ascii_strdown(str+15);
+ if (!signal_emit(str, 5, server, args, nick, addr, target)) {
+ signal_emit("default ctcp reply dcc", 5,
+ server, data, nick, addr, target);
+ }
+ g_free(str);
+}
+
+/* CTCP REPLY: REJECT */
+static void ctcp_reply_dcc_reject(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr,
+ DCC_REC *chat)
+{
+ DCC_REC *dcc;
+ char *type, *args;
+
+ type = g_strdup(data);
+ args = strchr(type, ' ');
+ if (args != NULL) *args++ = '\0'; else args = "";
+
+ dcc = dcc_find_request(dcc_str2type(type), nick, args);
+ if (dcc != NULL) dcc_close(dcc);
+
+ g_free(type);
+}
+
+void dcc_close(DCC_REC *dcc)
+{
+ signal_emit("dcc closed", 1, dcc);
+ dcc_destroy(dcc);
+}
+
+/* Reject a DCC request */
+void dcc_reject(DCC_REC *dcc, IRC_SERVER_REC *server)
+{
+ g_return_if_fail(dcc != NULL);
+
+ signal_emit("dcc rejected", 1, dcc);
+
+ if (dcc->server != NULL)
+ server = dcc->server;
+
+ if (server != NULL && !dcc_is_connected(dcc)) {
+ irc_send_cmdv(server, "NOTICE %s :\001DCC REJECT %s %s\001",
+ dcc->nick, dcc_type2str(dcc->orig_type),
+ dcc->arg);
+ }
+
+ dcc_close(dcc);
+}
+
+static int dcc_timeout_func(void)
+{
+ GSList *tmp, *next;
+ time_t now;
+
+ now = time(NULL)-settings_get_time("dcc_timeout")/1000;
+ for (tmp = dcc_conns; tmp != NULL; tmp = next) {
+ DCC_REC *dcc = tmp->data;
+
+ next = tmp->next;
+ if (dcc->tagread == -1 && now > dcc->created && !IS_DCC_SERVER(dcc)) {
+ /* Timed out - don't send DCC REJECT CTCP so CTCP
+ flooders won't affect us and it really doesn't
+ matter that much anyway if the other side doen't
+ get it..
+
+ We don't want dcc servers to time out. */
+ dcc_close(dcc);
+ }
+ }
+
+ return 1;
+}
+
+static void event_no_such_nick(IRC_SERVER_REC *server, char *data)
+{
+ char *params, *nick;
+ GSList *tmp, *next;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &nick);
+
+ /* check if we've send any dcc requests to this nick.. */
+ for (tmp = dcc_conns; tmp != NULL; tmp = next) {
+ DCC_REC *dcc = tmp->data;
+
+ next = tmp->next;
+ if (!dcc_is_connected(dcc) && dcc->server == server &&
+ dcc->nick != NULL && g_ascii_strcasecmp(dcc->nick, nick) == 0)
+ dcc_close(dcc);
+ }
+
+ g_free(params);
+}
+
+/* SYNTAX: DCC CLOSE <type> <nick> [<file>] */
+static void cmd_dcc_close(char *data, IRC_SERVER_REC *server)
+{
+ GSList *tmp, *next;
+ char *typestr, *nick, *arg, *fname;
+ void *free_arg;
+ int found, type;
+
+ g_return_if_fail(data != NULL);
+
+ if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST,
+ &typestr, &nick, &arg))
+ return;
+
+ if (*nick == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ ascii_strup(typestr);
+ type = dcc_str2type(typestr);
+ if (type == -1) {
+ signal_emit("dcc error unknown type", 1, typestr);
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ fname = cmd_get_quoted_param(&arg);
+
+ found = FALSE;
+ for (tmp = dcc_conns; tmp != NULL; tmp = next) {
+ DCC_REC *dcc = tmp->data;
+
+ next = tmp->next;
+ if (dcc->type == type && g_ascii_strcasecmp(dcc->nick, nick) == 0 &&
+ (*fname == '\0' || g_strcmp0(dcc->arg, fname) == 0)) {
+ dcc_reject(dcc, server);
+ found = TRUE;
+ }
+ }
+
+ if (!found) {
+ signal_emit("dcc error close not found", 3,
+ typestr, nick, arg);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+static void cmd_dcc(const char *data, IRC_SERVER_REC *server, void *item)
+{
+ command_runsub("dcc", data, server, item);
+}
+
+void irc_dcc_init(void)
+{
+ dcc_conns = NULL;
+ dcc_timeouttag = g_timeout_add(1000, (GSourceFunc) dcc_timeout_func, NULL);
+
+ settings_add_str("dcc", "dcc_port", "0");
+ settings_add_time("dcc", "dcc_timeout", "5min");
+ settings_add_str("dcc", "dcc_own_ip", "");
+
+ signal_add("event connected", (SIGNAL_FUNC) sig_connected);
+ signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+ signal_add("server nick changed", (SIGNAL_FUNC) sig_server_nick_changed);
+ signal_add("ctcp msg", (SIGNAL_FUNC) ctcp_msg);
+ signal_add("ctcp reply", (SIGNAL_FUNC) ctcp_reply);
+ signal_add("ctcp msg dcc", (SIGNAL_FUNC) ctcp_msg_dcc);
+ signal_add("ctcp reply dcc", (SIGNAL_FUNC) ctcp_reply_dcc);
+ signal_add("ctcp reply dcc reject", (SIGNAL_FUNC) ctcp_reply_dcc_reject);
+ signal_add("event 401", (SIGNAL_FUNC) event_no_such_nick);
+ command_bind("dcc", NULL, (SIGNAL_FUNC) cmd_dcc);
+ command_bind("dcc close", NULL, (SIGNAL_FUNC) cmd_dcc_close);
+
+ dcc_chat_init();
+ dcc_get_init();
+ dcc_send_init();
+ dcc_resume_init();
+ dcc_autoget_init();
+ dcc_server_init();
+
+ settings_check();
+ module_register("dcc", "irc");
+}
+
+void irc_dcc_deinit(void)
+{
+ while (dcc_conns != NULL)
+ dcc_destroy(dcc_conns->data);
+
+ dcc_chat_deinit();
+ dcc_get_deinit();
+ dcc_send_deinit();
+ dcc_resume_deinit();
+ dcc_autoget_deinit();
+ dcc_server_deinit();
+
+ signal_remove("event connected", (SIGNAL_FUNC) sig_connected);
+ signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+ signal_remove("server nick changed", (SIGNAL_FUNC) sig_server_nick_changed);
+ signal_remove("ctcp msg", (SIGNAL_FUNC) ctcp_msg);
+ signal_remove("ctcp reply", (SIGNAL_FUNC) ctcp_reply);
+ signal_remove("ctcp msg dcc", (SIGNAL_FUNC) ctcp_msg_dcc);
+ signal_remove("ctcp reply dcc", (SIGNAL_FUNC) ctcp_reply_dcc);
+ signal_remove("ctcp reply dcc reject", (SIGNAL_FUNC) ctcp_reply_dcc_reject);
+ signal_remove("event 401", (SIGNAL_FUNC) event_no_such_nick);
+ command_unbind("dcc", (SIGNAL_FUNC) cmd_dcc);
+ command_unbind("dcc close", (SIGNAL_FUNC) cmd_dcc_close);
+
+ g_source_remove(dcc_timeouttag);
+}
+
+MODULE_ABICHECK(irc_dcc)