diff options
Diffstat (limited to 'src/irc')
45 files changed, 1562 insertions, 454 deletions
diff --git a/src/irc/core/Makefile.am b/src/irc/core/Makefile.am index 3db5cf0e..20caaeb1 100644 --- a/src/irc/core/Makefile.am +++ b/src/irc/core/Makefile.am @@ -26,6 +26,8 @@ libirc_core_a_SOURCES = \ irc-servers-reconnect.c \ irc-servers-setup.c \ irc-session.c \ + irc-cap.c \ + sasl.c \ lag.c \ massjoin.c \ modes.c \ @@ -48,6 +50,8 @@ pkginc_irc_core_HEADERS = \ irc-queries.h \ irc-servers.h \ irc-servers-setup.h \ + irc-cap.h \ + sasl.h \ modes.h \ mode-lists.h \ module.h \ diff --git a/src/irc/core/bans.c b/src/irc/core/bans.c index d8d5d448..198fdc4a 100644 --- a/src/irc/core/bans.c +++ b/src/irc/core/bans.c @@ -88,7 +88,7 @@ char *ban_get_masks(IRC_CHANNEL_REC *channel, const char *nicks, int ban_type) str = g_string_new(NULL); banlist = g_strsplit(nicks, " ", -1); for (ban = banlist; *ban != NULL; ban++) { - if (strchr(*ban, '!') != NULL) { + if (**ban == '$' || strchr(*ban, '!') != NULL) { /* explicit ban */ g_string_append_printf(str, "%s ", *ban); continue; @@ -184,11 +184,11 @@ static void command_set_ban(const char *data, IRC_SERVER_REC *server, if (server == NULL || !server->connected || !IS_IRC_SERVER(server)) cmd_return_error(CMDERR_NOT_CONNECTED); - if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTCHAN | PARAM_FLAG_GETREST, - item, &channel, &nicks)) return; - if (!ischannel(*channel)) cmd_param_error(CMDERR_NOT_JOINED); + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTCHAN | PARAM_FLAG_GETREST | + PARAM_FLAG_STRIP_TRAILING_WS, item, &channel, &nicks)) return; + if (!server_ischannel(SERVER(server), channel)) cmd_param_error(CMDERR_NOT_JOINED); if (*nicks == '\0') { - if (strcmp(data, "*") != 0) + if (g_strcmp0(data, "*") != 0) cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); /* /BAN * or /UNBAN * - ban/unban everyone */ nicks = (char *) data; @@ -262,8 +262,8 @@ static void cmd_ban(const char *data, IRC_SERVER_REC *server, void *item) CMD_IRC_SERVER(server); - if (!cmd_get_params(data, &free_arg, 1 | - PARAM_FLAG_OPTIONS | PARAM_FLAG_GETREST, + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | + PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS, "ban", &optlist, &ban)) return; @@ -297,8 +297,8 @@ static void cmd_unban(const char *data, IRC_SERVER_REC *server, void *item) CMD_IRC_SERVER(server); - if (!cmd_get_params(data, &free_arg, 1 | - PARAM_FLAG_OPTIONS | PARAM_FLAG_GETREST, + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | + PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS, "unban", &optlist, &ban)) return; @@ -318,7 +318,7 @@ static void cmd_unban(const char *data, IRC_SERVER_REC *server, void *item) static void read_settings(void) { if (default_ban_type_str != NULL && - strcmp(default_ban_type_str, settings_get_str("ban_type")) == 0) + g_strcmp0(default_ban_type_str, settings_get_str("ban_type")) == 0) return; g_free_not_null(default_ban_type_str); diff --git a/src/irc/core/channel-events.c b/src/irc/core/channel-events.c index 6fdfeef3..46bbd5fa 100644 --- a/src/irc/core/channel-events.c +++ b/src/irc/core/channel-events.c @@ -37,7 +37,7 @@ static void check_join_failure(IRC_SERVER_REC *server, const char *channel) channel++; /* server didn't understand !channels */ chanrec = channel_find(SERVER(server), channel); - if (chanrec == NULL && channel[0] == '!') { + if (chanrec == NULL && channel[0] == '!' && strlen(channel) > 6) { /* it probably replied with the full !channel name, find the channel with the short name.. */ chan2 = g_strdup_printf("!%s", channel+6); @@ -138,7 +138,13 @@ static void channel_change_topic(IRC_SERVER_REC *server, const char *channel, g_free_not_null(chanrec->topic_by); chanrec->topic_by = g_strdup(setby); - chanrec->topic_time = settime; + if (chanrec->topic_by == NULL) { + /* ensure invariant topic_time > 0 <=> topic_by != NULL. + this could be triggered by a topic command without sender */ + chanrec->topic_time = 0; + } else { + chanrec->topic_time = settime; + } signal_emit("channel topic changed", 1, chanrec); } @@ -270,7 +276,7 @@ static void event_join(IRC_SERVER_REC *server, const char *data, const char *nic } chanrec->joined = TRUE; - if (strcmp(chanrec->name, channel) != 0) { + if (g_strcmp0(chanrec->name, channel) != 0) { g_free(chanrec->name); chanrec->name = g_strdup(channel); } diff --git a/src/irc/core/channel-rejoin.c b/src/irc/core/channel-rejoin.c index 68a1dee1..154035ae 100644 --- a/src/irc/core/channel-rejoin.c +++ b/src/irc/core/channel-rejoin.c @@ -149,7 +149,7 @@ static void event_target_unavailable(IRC_SERVER_REC *server, const char *data) g_return_if_fail(data != NULL); params = event_get_params(data, 2, NULL, &channel); - if (ischannel(*channel)) { + if (server_ischannel(SERVER(server), channel)) { chanrec = irc_channel_find(server, channel); if (chanrec != NULL && chanrec->joined) { /* dalnet event - can't change nick while diff --git a/src/irc/core/channels-query.c b/src/irc/core/channels-query.c index 857ebaf0..d7dadf04 100644 --- a/src/irc/core/channels-query.c +++ b/src/irc/core/channels-query.c @@ -119,21 +119,22 @@ static void query_remove_all(IRC_CHANNEL_REC *channel) int n; rec = channel->server->chanqueries; + if (rec == NULL) return; /* remove channel from query lists */ for (n = 0; n < CHANNEL_QUERIES; n++) rec->queries[n] = g_slist_remove(rec->queries[n], channel); rec->current_queries = g_slist_remove(rec->current_queries, channel); - query_check(channel->server); + if (!channel->server->disconnected) + query_check(channel->server); } static void sig_channel_destroyed(IRC_CHANNEL_REC *channel) { g_return_if_fail(channel != NULL); - if (IS_IRC_CHANNEL(channel) && !channel->server->disconnected && - !channel->synced) + if (IS_IRC_CHANNEL(channel)) query_remove_all(channel); } diff --git a/src/irc/core/irc-cap.c b/src/irc/core/irc-cap.c new file mode 100644 index 00000000..5464e493 --- /dev/null +++ b/src/irc/core/irc-cap.c @@ -0,0 +1,192 @@ +/* irc-cap.c : irssi + + Copyright (C) 2015 The Lemon Man + + 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 "signals.h" +#include "misc.h" + +#include "irc-cap.h" +#include "irc-servers.h" + +int cap_toggle (IRC_SERVER_REC *server, char *cap, int enable) +{ + if (cap == NULL || *cap == '\0') + return FALSE; + + /* If the negotiation hasn't been completed yet just queue the requests */ + if (!server->cap_complete) { + if (enable && !gslist_find_string(server->cap_queue, cap)) { + server->cap_queue = g_slist_prepend(server->cap_queue, g_strdup(cap)); + return TRUE; + } + else if (!enable && gslist_find_string(server->cap_queue, cap)) { + server->cap_queue = gslist_remove_string(server->cap_queue, cap); + return TRUE; + } + + return FALSE; + } + + if (enable && !gslist_find_string(server->cap_active, cap)) { + /* Make sure the required cap is supported by the server */ + if (!gslist_find_string(server->cap_supported, cap)) + return FALSE; + + irc_send_cmdv(server, "CAP REQ %s", cap); + return TRUE; + } + else if (!enable && gslist_find_string(server->cap_active, cap)) { + irc_send_cmdv(server, "CAP REQ -%s", cap); + return TRUE; + } + + return FALSE; +} + +void cap_finish_negotiation (IRC_SERVER_REC *server) +{ + if (server->cap_complete) + return; + + server->cap_complete = TRUE; + irc_send_cmd_now(server, "CAP END"); + + signal_emit("server cap end", 1, server); +} + +static void cap_emit_signal (IRC_SERVER_REC *server, char *cmd, char *args) +{ + char *signal_name; + + signal_name = g_strdup_printf("server cap %s %s", cmd, args? args: ""); + signal_emit(signal_name, 1, server); + g_free(signal_name); +} + +static void event_cap (IRC_SERVER_REC *server, char *args, char *nick, char *address) +{ + GSList *tmp; + GString *cmd; + char *params, *evt, *list, **caps; + int i, caps_length, disable, avail_caps; + + params = event_get_params(args, 3, NULL, &evt, &list); + if (params == NULL) + return; + + /* Strip the trailing whitespaces before splitting the string, some servers send responses with + * superfluous whitespaces that g_strsplit the interprets as tokens */ + caps = g_strsplit(g_strchomp(list), " ", -1); + caps_length = g_strv_length(caps); + + if (!g_strcmp0(evt, "LS")) { + /* Create a list of the supported caps */ + for (i = 0; i < caps_length; i++) + server->cap_supported = g_slist_prepend(server->cap_supported, g_strdup(caps[i])); + + /* Request the required caps, if any */ + if (server->cap_queue == NULL) { + cap_finish_negotiation(server); + } + else { + cmd = g_string_new("CAP REQ :"); + + avail_caps = 0; + + /* Check whether the cap is supported by the server */ + for (tmp = server->cap_queue; tmp != NULL; tmp = tmp->next) { + if (gslist_find_string(server->cap_supported, tmp->data)) { + if (avail_caps > 0) + g_string_append_c(cmd, ' '); + g_string_append(cmd, tmp->data); + + avail_caps++; + } + } + + /* Clear the queue here */ + gslist_free_full(server->cap_queue, (GDestroyNotify) g_free); + server->cap_queue = NULL; + + /* If the server doesn't support any cap we requested close the negotiation here */ + if (avail_caps > 0) + irc_send_cmd_now(server, cmd->str); + else + cap_finish_negotiation(server); + + g_string_free(cmd, TRUE); + } + } + else if (!g_strcmp0(evt, "ACK")) { + int got_sasl = FALSE; + + /* Emit a signal for every ack'd cap */ + for (i = 0; i < caps_length; i++) { + disable = (*caps[i] == '-'); + + if (disable) + server->cap_active = gslist_remove_string(server->cap_active, caps[i] + 1); + else + server->cap_active = g_slist_prepend(server->cap_active, g_strdup(caps[i])); + + if (!g_strcmp0(caps[i], "sasl")) + got_sasl = TRUE; + + cap_emit_signal(server, "ack", caps[i]); + } + + /* Hopefully the server has ack'd all the caps requested and we're ready to terminate the + * negotiation, unless sasl was requested. In this case we must not terminate the negotiation + * until the sasl handshake is over. */ + if (got_sasl == FALSE) + cap_finish_negotiation(server); + } + else if (!g_strcmp0(evt, "NAK")) { + g_warning("The server answered with a NAK to our CAP request, this should not happen"); + + /* A NAK'd request means that a required cap can't be enabled or disabled, don't update the + * list of active caps and notify the listeners. */ + for (i = 0; i < caps_length; i++) + cap_emit_signal(server, "nak", caps[i]); + } + + g_strfreev(caps); + g_free(params); +} + +static void event_invalid_cap (IRC_SERVER_REC *server, const char *data, const char *from) +{ + /* The server didn't understand one (or more) requested caps, terminate the negotiation. + * This could be handled in a graceful way but since it shouldn't really ever happen this seems a + * good way to deal with 410 errors. */ + server->cap_complete = FALSE; + irc_send_cmd_now(server, "CAP END"); +} + +void cap_init (void) +{ + signal_add_first("event cap", (SIGNAL_FUNC) event_cap); + signal_add_first("event 410", (SIGNAL_FUNC) event_invalid_cap); +} + +void cap_deinit (void) +{ + signal_remove("event cap", (SIGNAL_FUNC) event_cap); + signal_remove("event 410", (SIGNAL_FUNC) event_invalid_cap); +} diff --git a/src/irc/core/irc-cap.h b/src/irc/core/irc-cap.h new file mode 100644 index 00000000..df957cd2 --- /dev/null +++ b/src/irc/core/irc-cap.h @@ -0,0 +1,9 @@ +#ifndef __IRC_CAP_H +#define __IRC_CAP_H + +void cap_init(void); +void cap_deinit(void); +int cap_toggle (IRC_SERVER_REC *server, char *cap, int enable); +void cap_finish_negotiation (IRC_SERVER_REC *server); + +#endif diff --git a/src/irc/core/irc-channels-setup.c b/src/irc/core/irc-channels-setup.c index 2320352d..bbbc095c 100644 --- a/src/irc/core/irc-channels-setup.c +++ b/src/irc/core/irc-channels-setup.c @@ -24,10 +24,12 @@ void irc_channels_setup_init(void) { - signal_add("channel wholist", (SIGNAL_FUNC) channel_send_autocommands); + signal_add("channel wholist", (SIGNAL_FUNC) channel_send_botcommands); + signal_add("channel joined", (SIGNAL_FUNC) channel_send_autocommands); } void irc_channels_setup_deinit(void) { - signal_remove("channel wholist", (SIGNAL_FUNC) channel_send_autocommands); + signal_remove("channel wholist", (SIGNAL_FUNC) channel_send_botcommands); + signal_remove("channel joined", (SIGNAL_FUNC) channel_send_autocommands); } diff --git a/src/irc/core/irc-channels.c b/src/irc/core/irc-channels.c index e38cb98b..e775f530 100644 --- a/src/irc/core/irc-channels.c +++ b/src/irc/core/irc-channels.c @@ -99,7 +99,7 @@ static void irc_channels_join(IRC_SERVER_REC *server, const char *data, tmp = chanlist; for (;; tmp++) { if (*tmp != NULL) { - channel = ischannel(**tmp) ? g_strdup(*tmp) : + channel = server_ischannel(SERVER(server), *tmp) ? g_strdup(*tmp) : g_strdup_printf("#%s", *tmp); chanrec = irc_channel_find(server, channel); @@ -134,7 +134,7 @@ static void irc_channels_join(IRC_SERVER_REC *server, const char *data, if (use_keys) cmdlen += outkeys->len; if (*tmpstr != NULL) - cmdlen += ischannel(**tmpstr) ? strlen(*tmpstr) : + cmdlen += server_ischannel(SERVER(server), *tmpstr) ? strlen(*tmpstr) : strlen(*tmpstr)+1; if (*tmpkey != NULL) cmdlen += strlen(*tmpkey); @@ -146,11 +146,13 @@ static void irc_channels_join(IRC_SERVER_REC *server, const char *data, continue; } if (outchans->len > 0) { - g_string_truncate(outchans, outchans->len-1); - g_string_truncate(outkeys, outkeys->len-1); - irc_send_cmdv(IRC_SERVER(server), - use_keys ? "JOIN %s %s" : "JOIN %s", - outchans->str, outkeys->str); + g_string_truncate(outchans, outchans->len - 1); + g_string_truncate(outkeys, outkeys->len - 1); + + if (use_keys) + irc_send_cmdv(IRC_SERVER(server), "JOIN %s %s", outchans->str, outkeys->str); + else + irc_send_cmdv(IRC_SERVER(server), "JOIN %s", outchans->str); } cmdlen = 0; g_string_truncate(outchans,0); @@ -172,6 +174,13 @@ static CHANNEL_REC *irc_channel_find_server(SERVER_REC *server, const char *channel) { GSList *tmp; + char *fmt_channel; + + /* if 'channel' has no leading # this lookup is going to fail, add a + * octothorpe in front of it to handle this case. */ + fmt_channel = server_ischannel(SERVER(server), channel) ? + g_strdup(channel) : + g_strdup_printf("#%s", channel); for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { CHANNEL_REC *rec = tmp->data; @@ -180,13 +189,19 @@ static CHANNEL_REC *irc_channel_find_server(SERVER_REC *server, continue; /* check both !ABCDEchannel and !channel */ - if (IRC_SERVER(server)->nick_comp_func(channel, rec->name) == 0) + if (IRC_SERVER(server)->nick_comp_func(fmt_channel, rec->name) == 0) { + g_free(fmt_channel); return rec; + } - if (IRC_SERVER(server)->nick_comp_func(channel, rec->visible_name) == 0) + if (IRC_SERVER(server)->nick_comp_func(fmt_channel, rec->visible_name) == 0) { + g_free(fmt_channel); return rec; + } } + g_free(fmt_channel); + return NULL; } diff --git a/src/irc/core/irc-chatnets.c b/src/irc/core/irc-chatnets.c index d757bf8d..98ce89c3 100644 --- a/src/irc/core/irc-chatnets.c +++ b/src/irc/core/irc-chatnets.c @@ -35,10 +35,16 @@ void ircnet_create(IRC_CHATNET_REC *rec) static void sig_chatnet_read(IRC_CHATNET_REC *rec, CONFIG_NODE *node) { + char *value; + if (!IS_IRC_CHATNET(rec)) return; - rec->usermode = g_strdup(config_node_get_str(node, "usermode", NULL)); + value = config_node_get_str(node, "usermode", NULL); + rec->usermode = (value != NULL && *value != '\0') ? g_strdup(value) : NULL; + + value = config_node_get_str(node, "alternate_nick", NULL); + rec->alternate_nick = (value != NULL && *value != '\0') ? g_strdup(value) : NULL; rec->max_cmds_at_once = config_node_get_int(node, "cmdmax", 0); rec->cmd_queue_speed = config_node_get_int(node, "cmdspeed", 0); @@ -48,6 +54,10 @@ static void sig_chatnet_read(IRC_CHATNET_REC *rec, CONFIG_NODE *node) rec->max_msgs = config_node_get_int(node, "max_msgs", 0); rec->max_modes = config_node_get_int(node, "max_modes", 0); rec->max_whois = config_node_get_int(node, "max_whois", 0); + + rec->sasl_mechanism = g_strdup(config_node_get_str(node, "sasl_mechanism", NULL)); + rec->sasl_username = g_strdup(config_node_get_str(node, "sasl_username", NULL)); + rec->sasl_password = g_strdup(config_node_get_str(node, "sasl_password", NULL)); } static void sig_chatnet_saved(IRC_CHATNET_REC *rec, CONFIG_NODE *node) @@ -56,7 +66,10 @@ static void sig_chatnet_saved(IRC_CHATNET_REC *rec, CONFIG_NODE *node) return; if (rec->usermode != NULL) - iconfig_node_set_str(node, "usermode", rec->usermode); + iconfig_node_set_str(node, "usermode", rec->usermode); + + if (rec->alternate_nick != NULL) + iconfig_node_set_str(node, "alternate_nick", rec->alternate_nick); if (rec->max_cmds_at_once > 0) iconfig_node_set_int(node, "cmdmax", rec->max_cmds_at_once); @@ -73,12 +86,24 @@ static void sig_chatnet_saved(IRC_CHATNET_REC *rec, CONFIG_NODE *node) iconfig_node_set_int(node, "max_modes", rec->max_modes); if (rec->max_whois > 0) iconfig_node_set_int(node, "max_whois", rec->max_whois); + + if (rec->sasl_mechanism != NULL) + iconfig_node_set_str(node, "sasl_mechanism", rec->sasl_mechanism); + if (rec->sasl_username != NULL) + iconfig_node_set_str(node, "sasl_username", rec->sasl_username); + if (rec->sasl_password != NULL) + iconfig_node_set_str(node, "sasl_password", rec->sasl_password); } static void sig_chatnet_destroyed(IRC_CHATNET_REC *rec) { - if (IS_IRC_CHATNET(rec)) - g_free(rec->usermode); + if (IS_IRC_CHATNET(rec)) { + g_free(rec->usermode); + g_free(rec->alternate_nick); + g_free(rec->sasl_mechanism); + g_free(rec->sasl_username); + g_free(rec->sasl_password); + } } diff --git a/src/irc/core/irc-chatnets.h b/src/irc/core/irc-chatnets.h index 22da90c5..3fd44472 100644 --- a/src/irc/core/irc-chatnets.h +++ b/src/irc/core/irc-chatnets.h @@ -17,12 +17,16 @@ struct _IRC_CHATNET_REC { #include "chatnet-rec.h" - char *usermode; + char *usermode; + char *alternate_nick; + + char *sasl_mechanism; + char *sasl_username; + char *sasl_password; int max_cmds_at_once; int cmd_queue_speed; - int max_query_chans; /* when syncing, max. number of channels to - put in one MODE/WHO command */ + int max_query_chans; /* when syncing, max. number of channels to put in one MODE/WHO command */ /* max. number of kicks/msgs/mode/whois per command */ int max_kicks, max_msgs, max_modes, max_whois; diff --git a/src/irc/core/irc-commands.c b/src/irc/core/irc-commands.c index 7c3d3f5f..32ee8845 100644 --- a/src/irc/core/irc-commands.c +++ b/src/irc/core/irc-commands.c @@ -61,18 +61,18 @@ static int knockout_tag; /* SYNTAX: NOTICE <targets> <message> */ static void cmd_notice(const char *data, IRC_SERVER_REC *server, - WI_ITEM_REC *item) + WI_ITEM_REC *item) { const char *target, *msg; char *recoded; void *free_arg; - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, - &target, &msg)) + &target, &msg)) return; - if (strcmp(target, "*") == 0) + if (g_strcmp0(target, "*") == 0) target = item == NULL ? NULL : window_item_get_target(item); if (target == NULL || *target == '\0' || *msg == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); @@ -88,18 +88,18 @@ static void cmd_notice(const char *data, IRC_SERVER_REC *server, /* SYNTAX: CTCP <targets> <ctcp command> [<ctcp data>] */ static void cmd_ctcp(const char *data, IRC_SERVER_REC *server, - WI_ITEM_REC *item) + WI_ITEM_REC *item) { const char *target; char *ctcpcmd, *ctcpdata; void *free_arg; - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST, - &target, &ctcpcmd, &ctcpdata)) + &target, &ctcpcmd, &ctcpdata)) return; - if (strcmp(target, "*") == 0) + if (g_strcmp0(target, "*") == 0) target = item == NULL ? NULL : window_item_get_target(item); if (target == NULL || *target == '\0' || *ctcpcmd == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); @@ -122,18 +122,18 @@ static void cmd_ctcp(const char *data, IRC_SERVER_REC *server, /* SYNTAX: NCTCP <targets> <ctcp command> [<ctcp data>] */ static void cmd_nctcp(const char *data, IRC_SERVER_REC *server, - WI_ITEM_REC *item) + WI_ITEM_REC *item) { const char *target; char *ctcpcmd, *ctcpdata, *recoded; void *free_arg; - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST, - &target, &ctcpcmd, &ctcpdata)) + &target, &ctcpcmd, &ctcpdata)) return; - if (strcmp(target, "*") == 0) + if (g_strcmp0(target, "*") == 0) target = item == NULL ? NULL : window_item_get_target(item); if (target == NULL || *target == '\0' || *ctcpcmd == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); @@ -150,28 +150,31 @@ static void cmd_nctcp(const char *data, IRC_SERVER_REC *server, /* SYNTAX: PART [<channels>] [<message>] */ static void cmd_part(const char *data, IRC_SERVER_REC *server, - WI_ITEM_REC *item) + WI_ITEM_REC *item) { char *channame, *msg; char *recoded = NULL; void *free_arg; - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | - PARAM_FLAG_OPTCHAN, item, &channame, &msg)) + PARAM_FLAG_OPTCHAN, item, &channame, &msg)) return; if (*channame == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); if (*msg == '\0') msg = (char *) settings_get_str("part_message"); - if (server->cmdcount > MAX_COMMANDS_ON_PART_UNTIL_PURGE) + if (server->cmdcount > MAX_COMMANDS_ON_PART_UNTIL_PURGE) irc_server_purge_output(server, channame); if (*msg != '\0') recoded = recode_out(SERVER(server), msg, channame); - irc_send_cmdv(server, ! recoded ? "PART %s" : "PART %s :%s", - channame, recoded); + + if (recoded == NULL) + irc_send_cmdv(server, "PART %s", channame); + else + irc_send_cmdv(server, "PART %s :%s", channame, recoded); g_free(recoded); cmd_params_free(free_arg); @@ -183,15 +186,15 @@ static void cmd_kick(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item char *channame, *nicks, *reason, *recoded; void *free_arg; - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST | - PARAM_FLAG_OPTCHAN, item, - &channame, &nicks, &reason)) + PARAM_FLAG_OPTCHAN, item, + &channame, &nicks, &reason)) return; if (*channame == '\0' || *nicks == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); - if (!ischannel(*channame)) cmd_param_error(CMDERR_NOT_JOINED); + if (!server_ischannel(SERVER(server), channame)) cmd_param_error(CMDERR_NOT_JOINED); recoded = recode_out(SERVER(server), reason, channame); g_string_printf(tmpstr, "KICK %s %s :%s", channame, nicks, recoded); @@ -210,17 +213,21 @@ static void cmd_topic(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *ite char *recoded = NULL; void *free_arg; - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTCHAN | - PARAM_FLAG_OPTIONS | PARAM_FLAG_GETREST, - item, "topic", &optlist, &channame, &topic)) + PARAM_FLAG_OPTIONS | PARAM_FLAG_GETREST, + item, "topic", &optlist, &channame, &topic)) return; if (*topic != '\0' || g_hash_table_lookup(optlist, "delete") != NULL) recoded = recode_out(SERVER(server), topic, channame); - irc_send_cmdv(server, recoded == NULL ? "TOPIC %s" : "TOPIC %s :%s", - channame, recoded); + + if (recoded == NULL) + irc_send_cmdv(server, "TOPIC %s", channame); + else + irc_send_cmdv(server, "TOPIC %s :%s", channame, recoded); + g_free(recoded); cmd_params_free(free_arg); @@ -232,13 +239,13 @@ static void cmd_invite(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *it char *nick, *channame; void *free_arg; - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 2, &nick, &channame)) return; if (*nick == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); - if (*channame == '\0' || strcmp(channame, "*") == 0) { + if (*channame == '\0' || g_strcmp0(channame, "*") == 0) { if (!IS_IRC_CHANNEL(item)) cmd_param_error(CMDERR_NOT_JOINED); @@ -251,16 +258,17 @@ static void cmd_invite(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *it /* SYNTAX: LIST [-yes] [<channel>] */ static void cmd_list(const char *data, IRC_SERVER_REC *server, - WI_ITEM_REC *item) + WI_ITEM_REC *item) { GHashTable *optlist; char *str; void *free_arg; - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | - PARAM_FLAG_GETREST, "list", &optlist, &str)) + PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS, + "list", &optlist, &str)) return; if (*str == '\0' && g_hash_table_lookup(optlist, "yes") == NULL && @@ -274,55 +282,60 @@ static void cmd_list(const char *data, IRC_SERVER_REC *server, /* SYNTAX: WHO [<nicks> | <channels> | **] */ static void cmd_who(const char *data, IRC_SERVER_REC *server, - WI_ITEM_REC *item) + WI_ITEM_REC *item) { char *channel, *rest; void *free_arg; - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); - if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &channel, &rest)) + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | + PARAM_FLAG_STRIP_TRAILING_WS, &channel, &rest)) return; - if (strcmp(channel, "*") == 0 || *channel == '\0') { + if (g_strcmp0(channel, "*") == 0 || *channel == '\0') { if (!IS_IRC_CHANNEL(item)) - cmd_param_error(CMDERR_NOT_JOINED); + cmd_param_error(CMDERR_NOT_JOINED); channel = IRC_CHANNEL(item)->name; } - if (strcmp(channel, "**") == 0) { + if (g_strcmp0(channel, "**") == 0) { /* ** displays all nicks.. */ *channel = '\0'; } - irc_send_cmdv(server, *rest == '\0' ? "WHO %s" : "WHO %s %s", - channel, rest); + if (rest[0] == '\0') + irc_send_cmdv(server, "WHO %s", channel); + else + irc_send_cmdv(server, "WHO %s %s", channel, rest); + cmd_params_free(free_arg); } static void cmd_names(const char *data, IRC_SERVER_REC *server, - WI_ITEM_REC *item) + WI_ITEM_REC *item) { - GHashTable *optlist; + GHashTable *optlist; char *channel; void *free_arg; - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | - PARAM_FLAG_GETREST, "names", &optlist, &channel)) + PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS, + "names", &optlist, &channel)) return; - if (strcmp(channel, "*") == 0 || *channel == '\0') { + if (g_strcmp0(channel, "*") == 0 || *channel == '\0') { if (!IS_IRC_CHANNEL(item)) - cmd_param_error(CMDERR_NOT_JOINED); + cmd_param_error(CMDERR_NOT_JOINED); channel = IRC_CHANNEL(item)->name; } - if (strcmp(channel, "**") == 0) { + if (g_strcmp0(channel, "**") == 0) { /* ** displays all nicks.. */ - irc_send_cmd(server, "NAMES"); + irc_send_cmd(server, "NAMES"); } else { irc_send_cmdv(server, "NAMES %s", channel); } @@ -333,12 +346,12 @@ static void cmd_names(const char *data, IRC_SERVER_REC *server, /* SYNTAX: NICK <new nick> */ static void cmd_nick(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) { - char *nick; + char *nick; void *free_arg; g_return_if_fail(data != NULL); - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 1, &nick)) return; @@ -375,23 +388,23 @@ static char *get_redirect_nicklist(const char *nicks, int *free) /* SYNTAX: WHOIS [-<server tag>] [<server>] [<nicks>] */ static void cmd_whois(const char *data, IRC_SERVER_REC *server, - WI_ITEM_REC *item) + WI_ITEM_REC *item) { GHashTable *optlist; char *qserver, *query, *event_402, *str; void *free_arg; int free_nick; - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS | - PARAM_FLAG_UNKNOWN_OPTIONS, - "whois", &optlist, &qserver, &query)) + PARAM_FLAG_UNKNOWN_OPTIONS, + "whois", &optlist, &qserver, &query)) return; /* -<server tag> */ server = IRC_SERVER(cmd_options_get_server("whois", optlist, - SERVER(server))); + SERVER(server))); if (server == NULL) { cmd_params_free(free_arg); return; @@ -409,7 +422,7 @@ static void cmd_whois(const char *data, IRC_SERVER_REC *server, query = qserver = queryitem->name; } - if (strcmp(query, "*") == 0 && + if (g_strcmp0(query, "*") == 0 && g_hash_table_lookup(optlist, "yes") == NULL) cmd_param_error(CMDERR_NOT_GOOD_IDEA); @@ -426,15 +439,15 @@ static void cmd_whois(const char *data, IRC_SERVER_REC *server, str = g_strconcat(qserver, " ", query, NULL); server_redirect_event(server, "whois", 1, str, TRUE, - NULL, - "event 318", "whois end", - "event 402", event_402, - "event 301", "whois away", /* 301 can come as a reply to /MSG, /WHOIS or /WHOWAS */ - "event 313", "whois oper", - "event 401", (settings_get_bool("auto_whowas") ? "whois try whowas" : "whois event not found"), - "event 311", "whois event", - "", "whois default event", NULL); - g_free(str); + NULL, + "event 318", "whois end", + "event 402", event_402, + "event 301", "whois away", /* 301 can come as a reply to /MSG, /WHOIS or /WHOWAS */ + "event 313", "whois oper", + "event 401", (settings_get_bool("auto_whowas") ? "whois try whowas" : "whois event not found"), + "event 311", "whois event", + "", "whois default event", NULL); + g_free(str); server->whois_found = FALSE; irc_send_cmd_split(server, tmpstr->str, 2, server->max_whois_in_cmd); @@ -444,7 +457,7 @@ static void cmd_whois(const char *data, IRC_SERVER_REC *server, } static void event_whois(IRC_SERVER_REC *server, const char *data, - const char *nick, const char *addr) + const char *nick, const char *addr) { server->whois_found = TRUE; signal_emit("event 311", 4, server, data, nick, addr); @@ -460,23 +473,23 @@ static void sig_whois_try_whowas(IRC_SERVER_REC *server, const char *data) server->whowas_found = FALSE; server_redirect_event(server, "whowas", 1, nick, -1, NULL, - "event 314", "whowas event", - "event 369", "whowas event end", - "event 406", "event empty", NULL); + "event 314", "whowas event", + "event 369", "whowas event end", + "event 406", "event empty", NULL); irc_send_cmdv(server, "WHOWAS %s 1", nick); g_free(params); } static void event_end_of_whois(IRC_SERVER_REC *server, const char *data, - const char *nick, const char *addr) + const char *nick, const char *addr) { signal_emit("event 318", 4, server, data, nick, addr); server->whois_found = FALSE; } static void event_whowas(IRC_SERVER_REC *server, const char *data, - const char *nick, const char *addr) + const char *nick, const char *addr) { server->whowas_found = TRUE; signal_emit("event 314", 4, server, data, nick, addr); @@ -489,21 +502,25 @@ static void cmd_whowas(const char *data, IRC_SERVER_REC *server) void *free_arg; int free_nick; - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); - if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &nicks, &rest)) + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS, + &nicks, &rest)) return; if (*nicks == '\0') nicks = server->nick; nicks_redir = get_redirect_nicklist(nicks, &free_nick); server_redirect_event(server, "whowas", 1, nicks_redir, -1, NULL, - "event 301", "whowas away", /* 301 can come as a reply to /MSG, /WHOIS or /WHOWAS */ - "event 314", "whowas event", NULL); + "event 301", "whowas away", /* 301 can come as a reply to /MSG, /WHOIS or /WHOWAS */ + "event 314", "whowas event", NULL); if (free_nick) g_free(nicks_redir); server->whowas_found = FALSE; - irc_send_cmdv(server, *rest == '\0' ? "WHOWAS %s" : - "WHOWAS %s %s", nicks, rest); + + if (rest[0] == '\0') + irc_send_cmdv(server, "WHOWAS %s", nicks); + else + irc_send_cmdv(server, "WHOWAS %s %s", nicks, rest); cmd_params_free(free_arg); } @@ -512,9 +529,9 @@ static void cmd_whowas(const char *data, IRC_SERVER_REC *server) static void cmd_ping(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) { GTimeVal tv; - char *str; + char *str; - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); if (*data == '\0') { if (!IS_QUERY(item)) @@ -537,7 +554,7 @@ static void cmd_away(const char *data, IRC_SERVER_REC *server) void *free_arg; if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | - PARAM_FLAG_GETREST, "away", &optlist, &reason)) return; + PARAM_FLAG_GETREST, "away", &optlist, &reason)) return; if (g_hash_table_lookup(optlist, "one") != NULL) irc_server_send_away(server, reason); @@ -550,7 +567,7 @@ static void cmd_away(const char *data, IRC_SERVER_REC *server) /* SYNTAX: SCONNECT <new server> [[<port>] <existing server>] */ static void cmd_sconnect(const char *data, IRC_SERVER_REC *server) { - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); irc_send_cmdv(server, "CONNECT %s", data); @@ -583,11 +600,11 @@ static void cmd_wait(const char *data, IRC_SERVER_REC *server) void *free_arg; int n; - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | - PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST, - NULL, &optlist, &msecs)) + PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST, + NULL, &optlist, &msecs)) return; if (*msecs == '\0') @@ -595,7 +612,7 @@ static void cmd_wait(const char *data, IRC_SERVER_REC *server) /* -<server tag> */ server = IRC_SERVER(cmd_options_get_server(NULL, optlist, - SERVER(server))); + SERVER(server))); n = atoi(msecs); if (server != NULL && n > 0) { @@ -618,10 +635,10 @@ static void cmd_wall(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item IRC_CHANNEL_REC *chanrec; GSList *tmp, *nicks; - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTCHAN | - PARAM_FLAG_GETREST, item, &channame, &msg)) + PARAM_FLAG_GETREST, item, &channame, &msg)) return; if (*msg == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); @@ -637,11 +654,11 @@ static void cmd_wall(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item /* Fall back to manually noticing each op */ nicks = NULL; g_hash_table_foreach(chanrec->nicks, - (GHFunc) cmd_wall_hash, &nicks); + (GHFunc) cmd_wall_hash, &nicks); args = g_strconcat(chanrec->name, " ", recoded, NULL); msg = parse_special_string(settings_get_str("wall_format"), - SERVER(server), item, args, NULL, 0); + SERVER(server), item, args, NULL, 0); g_free(args); for (tmp = nicks; tmp != NULL; tmp = tmp->next) { @@ -649,7 +666,7 @@ static void cmd_wall(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item if (rec != chanrec->ownnick) { irc_send_cmdv(server, "NOTICE %s :%s", - rec->nick, msg); + rec->nick, msg); } } @@ -663,17 +680,17 @@ static void cmd_wall(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item /* SYNTAX: KICKBAN [<channel>] <nicks> <reason> */ static void cmd_kickban(const char *data, IRC_SERVER_REC *server, - WI_ITEM_REC *item) + WI_ITEM_REC *item) { - IRC_CHANNEL_REC *chanrec; + IRC_CHANNEL_REC *chanrec; char *channel, *nicks, *reason, *kickcmd, *bancmd, *recoded; - char **nicklist, *spacenicks; + char **nicklist, *spacenicks; void *free_arg; - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_OPTCHAN | PARAM_FLAG_GETREST, - item, &channel, &nicks, &reason)) + item, &channel, &nicks, &reason)) return; if (*channel == '\0' || *nicks == '\0') @@ -684,7 +701,7 @@ static void cmd_kickban(const char *data, IRC_SERVER_REC *server, cmd_param_error(CMDERR_CHAN_NOT_FOUND); nicklist = g_strsplit(nicks, ",", -1); - spacenicks = g_strjoinv(" ", nicklist); + spacenicks = g_strjoinv(" ", nicklist); g_strfreev(nicklist); recoded = recode_out(SERVER(server), reason, channel); @@ -692,9 +709,9 @@ static void cmd_kickban(const char *data, IRC_SERVER_REC *server, g_free(recoded); bancmd = g_strdup_printf("%s %s", chanrec->name, spacenicks); - g_free(spacenicks); + g_free(spacenicks); - if (settings_get_bool("kick_first_on_kickban")) { + if (settings_get_bool("kick_first_on_kickban")) { signal_emit("command kick", 3, kickcmd, server, chanrec); signal_emit("command ban", 3, bancmd, server, chanrec); } else { @@ -725,14 +742,14 @@ static void knockout_timeout_server(IRC_SERVER_REC *server) if (!IS_IRC_SERVER(server)) return; - now = time(NULL); + now = time(NULL); for (tmp = server->knockoutlist; tmp != NULL; tmp = next) { KNOCKOUT_REC *rec = tmp->data; next = tmp->next; if (rec->unban_time <= now) { /* timeout, unban. */ - ban_remove(rec->channel, rec->ban); + signal_emit("command unban", 3, rec->ban, server, rec->channel); knockout_destroy(server, rec); } } @@ -746,16 +763,16 @@ static int knockout_timeout(void) /* SYNTAX: KNOCKOUT [<time>] <nicks> <reason> */ static void cmd_knockout(const char *data, IRC_SERVER_REC *server, - IRC_CHANNEL_REC *channel) + IRC_CHANNEL_REC *channel) { KNOCKOUT_REC *rec; char *nicks, *reason, *timeoutstr, *kickcmd, *bancmd, *recoded; - char **nicklist, *spacenicks, *banmasks; + char **nicklist, *spacenicks, *banmasks; void *free_arg; int timeleft; GSList *ptr; - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); if (!IS_IRC_CHANNEL(channel)) cmd_return_error(CMDERR_NOT_JOINED); @@ -763,14 +780,14 @@ static void cmd_knockout(const char *data, IRC_SERVER_REC *server, if (i_isdigit(*data)) { /* first argument is the timeout */ if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST, - &timeoutstr, &nicks, &reason)) + &timeoutstr, &nicks, &reason)) return; if (!parse_time_interval(timeoutstr, &timeleft)) cmd_param_error(CMDERR_INVALID_TIME); } else { if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, - &nicks, &reason)) + &nicks, &reason)) return; timeleft = settings_get_time("knockout_time"); } @@ -778,7 +795,7 @@ static void cmd_knockout(const char *data, IRC_SERVER_REC *server, if (*nicks == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); nicklist = g_strsplit(nicks, ",", -1); - spacenicks = g_strjoinv(" ", nicklist); + spacenicks = g_strjoinv(" ", nicklist); g_strfreev(nicklist); banmasks = ban_get_masks(channel, spacenicks, 0); @@ -791,7 +808,7 @@ static void cmd_knockout(const char *data, IRC_SERVER_REC *server, bancmd = *banmasks == '\0'? NULL : g_strdup_printf("%s %s", channel->name, banmasks); - if (settings_get_bool("kick_first_on_kickban")) { + if (settings_get_bool("kick_first_on_kickban")) { signal_emit("command kick", 3, kickcmd, server, channel); if (bancmd != NULL) signal_emit("command ban", 3, bancmd, server, channel); @@ -810,7 +827,7 @@ static void cmd_knockout(const char *data, IRC_SERVER_REC *server, for (ptr = server->knockoutlist; ptr != NULL; ptr = ptr->next) { rec = ptr->data; if (channel == rec->channel && - !strcmp(rec->ban, banmasks)) + !g_strcmp0(rec->ban, banmasks)) break; } if (ptr == NULL) { @@ -828,10 +845,10 @@ static void cmd_knockout(const char *data, IRC_SERVER_REC *server, /* SYNTAX: SERVER PURGE [<target>] */ static void cmd_server_purge(const char *data, IRC_SERVER_REC *server) { - char *target; + char *target; void *free_arg; - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 1, &target)) return; @@ -878,12 +895,12 @@ static void cmd_oper(const char *data, IRC_SERVER_REC *server) char *nick, *password; void *free_arg; - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); - /* asking for password is handled by fe-common */ + /* asking for password is handled by fe-common */ if (!cmd_get_params(data, &free_arg, 2, &nick, &password)) return; - if (*password == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + if (*password == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); irc_send_cmdv(server, "OPER %s %s", nick, password); cmd_params_free(free_arg); @@ -892,7 +909,7 @@ static void cmd_oper(const char *data, IRC_SERVER_REC *server) /* SYNTAX: ACCEPT [[-]nick,...] */ static void cmd_accept(const char *data, IRC_SERVER_REC *server) { - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); if (*data == '\0') irc_send_cmd(server, "ACCEPT *"); @@ -903,7 +920,7 @@ static void cmd_accept(const char *data, IRC_SERVER_REC *server) /* SYNTAX: UNSILENCE <nick!user@host> */ static void cmd_unsilence(const char *data, IRC_SERVER_REC *server) { - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); @@ -913,9 +930,12 @@ static void cmd_unsilence(const char *data, IRC_SERVER_REC *server) static void command_self(const char *data, IRC_SERVER_REC *server) { - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); - irc_send_cmdv(server, *data == '\0' ? "%s" : "%s %s", current_command, data); + if (data[0] == '\0') + irc_send_cmdv(server, "%s", current_command); + else + irc_send_cmdv(server, "%s %s", current_command, data); } static void command_1self(const char *data, IRC_SERVER_REC *server) @@ -933,7 +953,7 @@ static void command_2self(const char *data, IRC_SERVER_REC *server) char *target, *text; void *free_arg; - CMD_IRC_SERVER(server); + CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &target, &text)) return; @@ -978,8 +998,8 @@ void irc_commands_init(void) command_bind_irc("admin", NULL, (SIGNAL_FUNC) command_self); /* SYNTAX: INFO [<server>] */ command_bind_irc("info", NULL, (SIGNAL_FUNC) command_self); - /* SYNTAX: KNOCK <channel> */ - command_bind_irc("knock", NULL, (SIGNAL_FUNC) command_self); + /* SYNTAX: KNOCK <channel> */ + command_bind_irc("knock", NULL, (SIGNAL_FUNC) command_self); /* SYNTAX: LINKS [[<server>] <mask>] */ command_bind_irc("links", NULL, (SIGNAL_FUNC) command_self); /* SYNTAX: LUSERS [<server mask> [<remote server>]] */ @@ -998,11 +1018,15 @@ void irc_commands_init(void) command_bind_irc("trace", NULL, (SIGNAL_FUNC) command_self); /* SYNTAX: VERSION [<server>|<nick>] */ command_bind_irc("version", NULL, (SIGNAL_FUNC) command_self); + /* SYNTAX: SERVLIST [<mask> [<type>]] */ + command_bind_irc("servlist", NULL, (SIGNAL_FUNC) command_self); /* SYNTAX: SILENCE [[+|-]<nick!user@host>] SILENCE [<nick>] */ command_bind_irc("silence", NULL, (SIGNAL_FUNC) command_self); command_bind_irc("unsilence", NULL, (SIGNAL_FUNC) cmd_unsilence); command_bind_irc("sconnect", NULL, (SIGNAL_FUNC) cmd_sconnect); + /* SYNTAX: SQUERY <service> [<message>] */ + command_bind_irc("squery", NULL, (SIGNAL_FUNC) command_2self); /* SYNTAX: DIE */ command_bind_irc("die", NULL, (SIGNAL_FUNC) command_self); /* SYNTAX: HASH */ @@ -1061,7 +1085,7 @@ void irc_commands_deinit(void) command_unbind("accept", (SIGNAL_FUNC) cmd_accept); command_unbind("admin", (SIGNAL_FUNC) command_self); command_unbind("info", (SIGNAL_FUNC) command_self); - command_unbind("knock", (SIGNAL_FUNC) command_self); + command_unbind("knock", (SIGNAL_FUNC) command_self); command_unbind("links", (SIGNAL_FUNC) command_self); command_unbind("lusers", (SIGNAL_FUNC) command_self); command_unbind("map", (SIGNAL_FUNC) command_self); @@ -1071,9 +1095,11 @@ void irc_commands_deinit(void) command_unbind("time", (SIGNAL_FUNC) command_self); command_unbind("trace", (SIGNAL_FUNC) command_self); command_unbind("version", (SIGNAL_FUNC) command_self); + command_unbind("servlist", (SIGNAL_FUNC) command_self); command_unbind("silence", (SIGNAL_FUNC) command_self); command_unbind("unsilence", (SIGNAL_FUNC) cmd_unsilence); command_unbind("sconnect", (SIGNAL_FUNC) cmd_sconnect); + command_unbind("squery", (SIGNAL_FUNC) command_2self); command_unbind("die", (SIGNAL_FUNC) command_self); command_unbind("hash", (SIGNAL_FUNC) command_self); command_unbind("oper", (SIGNAL_FUNC) cmd_oper); diff --git a/src/irc/core/irc-core.c b/src/irc/core/irc-core.c index bf7386ad..a9221e02 100644 --- a/src/irc/core/irc-core.c +++ b/src/irc/core/irc-core.c @@ -26,6 +26,8 @@ #include "irc-chatnets.h" #include "irc-channels.h" #include "irc-queries.h" +#include "irc-cap.h" +#include "sasl.h" #include "irc-servers-setup.h" #include "channels-setup.h" @@ -117,6 +119,8 @@ void irc_core_init(void) lag_init(); netsplit_init(); irc_expandos_init(); + cap_init(); + sasl_init(); settings_check(); module_register("core", "irc"); @@ -126,6 +130,8 @@ void irc_core_deinit(void) { signal_emit("chat protocol deinit", 1, chat_protocol_find("IRC")); + sasl_deinit(); + cap_deinit(); irc_expandos_deinit(); netsplit_deinit(); lag_deinit(); @@ -137,7 +143,7 @@ void irc_core_deinit(void) irc_irc_deinit(); irc_servers_deinit(); irc_chatnets_deinit(); - irc_session_deinit(); + irc_session_deinit(); chat_protocol_unregister("IRC"); } diff --git a/src/irc/core/irc-expandos.c b/src/irc/core/irc-expandos.c index 5d2de503..62ef577a 100644 --- a/src/irc/core/irc-expandos.c +++ b/src/irc/core/irc-expandos.c @@ -27,6 +27,10 @@ #include "irc-channels.h" #include "nicklist.h" +#ifndef HOST_NAME_MAX +#define HOST_NAME_MAX 255 +#endif + static char *last_join; /* last person to join a channel you are on */ @@ -56,7 +60,7 @@ static char *expando_userhost(SERVER_REC *server, void *item, int *free_ret) { IRC_SERVER_REC *ircserver; const char *username; - char hostname[100]; + char hostname[HOST_NAME_MAX + 1]; ircserver = IRC_SERVER(server); @@ -72,10 +76,36 @@ static char *expando_userhost(SERVER_REC *server, void *item, int *free_ret) username = ircserver->connrec->username; if (gethostname(hostname, sizeof(hostname)) != 0 || *hostname == '\0') - strcpy(hostname, "??"); + strcpy(hostname, "(none)"); return g_strconcat(username, "@", hostname, NULL);; } +/* your hostname address (host) */ +static char *expando_hostname(SERVER_REC *server, void *item, int *free_ret) +{ + IRC_SERVER_REC *ircserver; + char hostname[HOST_NAME_MAX + 1]; + char **list; + char *hostname_split; + + ircserver = IRC_SERVER(server); + + *free_ret = TRUE; + + /* prefer the _real_ /userhost reply */ + if (ircserver != NULL && ircserver->userhost != NULL) { + list = g_strsplit(ircserver->userhost, "@", -1); + hostname_split = g_strdup(list[1]); + g_strfreev(list); + return hostname_split; + } + + /* haven't received userhost reply yet. guess something */ + if (gethostname(hostname, sizeof(hostname)) != 0 || *hostname == '\0') + strcpy(hostname, "(none)"); + return g_strdup(hostname); +} + /* user mode in active server */ static char *expando_usermode(SERVER_REC *server, void *item, int *free_ret) { @@ -136,6 +166,9 @@ void irc_expandos_init(void) expando_create("X", expando_userhost, "window changed", EXPANDO_ARG_NONE, "window server changed", EXPANDO_ARG_WINDOW, NULL); + expando_create("x", expando_hostname, + "window changed", EXPANDO_ARG_NONE, + "window server changed", EXPANDO_ARG_WINDOW, NULL); expando_create("usermode", expando_usermode, "window changed", EXPANDO_ARG_NONE, "window server changed", EXPANDO_ARG_WINDOW, @@ -164,6 +197,7 @@ void irc_expandos_deinit(void) expando_destroy("H", expando_server_numeric); expando_destroy("S", expando_servername); expando_destroy("X", expando_userhost); + expando_destroy("x", expando_hostname); expando_destroy("usermode", expando_usermode); expando_destroy("cumode", expando_cumode); diff --git a/src/irc/core/irc-nicklist.c b/src/irc/core/irc-nicklist.c index 1f8c1fc9..3e16db80 100644 --- a/src/irc/core/irc-nicklist.c +++ b/src/irc/core/irc-nicklist.c @@ -48,37 +48,13 @@ NICK_REC *irc_nicklist_insert(IRC_CHANNEL_REC *channel, const char *nick, rec->send_massjoin = send_massjoin; if (prefixes != NULL) { - strocpy(rec->prefixes, prefixes, sizeof(rec->prefixes)); + g_strlcpy(rec->prefixes, prefixes, sizeof(rec->prefixes)); } nicklist_insert(CHANNEL(channel), rec); return rec; } -#define isnickchar(a) \ - (i_isalnum(a) || (a) == '`' || (a) == '-' || (a) == '_' || \ - (a) == '[' || (a) == ']' || (a) == '{' || (a) == '}' || \ - (a) == '|' || (a) == '\\' || (a) == '^') - -/* Remove all "extra" characters from `nick'. Like _nick_ -> nick */ -char *irc_nick_strip(const char *nick) -{ - char *stripped, *spos; - - g_return_val_if_fail(nick != NULL, NULL); - - spos = stripped = g_strdup(nick); - while (isnickchar(*nick)) { - if (i_isalnum(*nick)) - *spos++ = *nick; - nick++; - } - if ((unsigned char) *nick >= 128) - *spos++ = *nick; /* just add it so that nicks won't match.. */ - *spos = '\0'; - return stripped; -} - int irc_nickcmp_rfc1459(const char *m, const char *n) { while (*m != '\0' && *n != '\0') { @@ -338,7 +314,11 @@ static void event_whois_ircop(SERVER_REC *server, const char *data) static void event_nick_invalid(IRC_SERVER_REC *server, const char *data) { if (!server->connected) - server_disconnect((SERVER_REC *) server); + /* we used to call server_disconnect but that crashes + irssi because of undefined memory access. instead, + indicate that the connection should be dropped and + let the irc method to the clean-up. */ + server->connection_lost = server->no_reconnect = TRUE; } static void event_nick_in_use(IRC_SERVER_REC *server, const char *data) @@ -405,7 +385,7 @@ static void event_target_unavailable(IRC_SERVER_REC *server, const char *data) g_return_if_fail(data != NULL); params = event_get_params(data, 2, NULL, &channel); - if (!ischannel(*channel)) { + if (!server_ischannel(SERVER(server), channel)) { /* nick is unavailable. */ event_nick_in_use(server, data); } diff --git a/src/irc/core/irc-nicklist.h b/src/irc/core/irc-nicklist.h index 7302556b..2ae17d2c 100644 --- a/src/irc/core/irc-nicklist.h +++ b/src/irc/core/irc-nicklist.h @@ -8,9 +8,6 @@ NICK_REC *irc_nicklist_insert(IRC_CHANNEL_REC *channel, const char *nick, int op, int halfop, int voice, int send_massjoin, const char *prefixes); -/* Remove all "extra" characters from `nick'. Like _nick_ -> nick */ -char *irc_nick_strip(const char *nick); - int irc_nickcmp_rfc1459(const char *, const char *); int irc_nickcmp_ascii(const char *, const char *); diff --git a/src/irc/core/irc-queries.c b/src/irc/core/irc-queries.c index ac1a72a1..64995ead 100644 --- a/src/irc/core/irc-queries.c +++ b/src/irc/core/irc-queries.c @@ -45,6 +45,8 @@ QUERY_REC *irc_query_find(IRC_SERVER_REC *server, const char *nick) { GSList *tmp; + g_return_val_if_fail(nick != NULL, NULL); + for (tmp = server->queries; tmp != NULL; tmp = tmp->next) { QUERY_REC *rec = tmp->data; @@ -60,39 +62,25 @@ static void check_query_changes(IRC_SERVER_REC *server, const char *nick, { QUERY_REC *query; - if (ischannel(*target)) - return; + if (server_ischannel(SERVER(server), target)) + return; query = irc_query_find(server, nick); if (query == NULL) return; - if (strcmp(query->name, nick) != 0) { + if (g_strcmp0(query->name, nick) != 0) { /* upper/lowercase chars in nick changed */ query_change_nick(query, nick); } if (address != NULL && (query->address == NULL || - strcmp(query->address, address) != 0)) { + g_strcmp0(query->address, address) != 0)) { /* host changed */ query_change_address(query, address); } } -static void event_privmsg(IRC_SERVER_REC *server, const char *data, - const char *nick, const char *address) -{ - char *params, *target, *msg; - - g_return_if_fail(data != NULL); - if (nick == NULL) - return; - - params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg); - check_query_changes(server, nick, address, target); - g_free(params); -} - static void ctcp_action(IRC_SERVER_REC *server, const char *msg, const char *nick, const char *address, const char *target) @@ -109,7 +97,7 @@ static void event_nick(SERVER_REC *server, const char *data, query = query_find(server, orignick); if (query != NULL) { params = event_get_params(data, 1, &nick); - if (strcmp(query->name, nick) != 0) + if (g_strcmp0(query->name, nick) != 0) query_change_nick(query, nick); g_free(params); } @@ -117,14 +105,12 @@ static void event_nick(SERVER_REC *server, const char *data, void irc_queries_init(void) { - signal_add_last("event privmsg", (SIGNAL_FUNC) event_privmsg); signal_add_last("ctcp action", (SIGNAL_FUNC) ctcp_action); signal_add("event nick", (SIGNAL_FUNC) event_nick); } void irc_queries_deinit(void) { - signal_remove("event privmsg", (SIGNAL_FUNC) event_privmsg); signal_remove("ctcp action", (SIGNAL_FUNC) ctcp_action); signal_remove("event nick", (SIGNAL_FUNC) event_nick); } diff --git a/src/irc/core/irc-servers-reconnect.c b/src/irc/core/irc-servers-reconnect.c index b0aad26f..ca61492d 100644 --- a/src/irc/core/irc-servers-reconnect.c +++ b/src/irc/core/irc-servers-reconnect.c @@ -48,6 +48,9 @@ static void sig_server_connect_copy(SERVER_CONNECT_REC **dest, rec->max_whois = src->max_whois; rec->usermode = g_strdup(src->usermode); rec->alternate_nick = g_strdup(src->alternate_nick); + rec->sasl_mechanism = src->sasl_mechanism; + rec->sasl_username = src->sasl_username; + rec->sasl_password = src->sasl_password; *dest = (SERVER_CONNECT_REC *) rec; } diff --git a/src/irc/core/irc-servers-setup.c b/src/irc/core/irc-servers-setup.c index 5659991b..e79557ab 100644 --- a/src/irc/core/irc-servers-setup.c +++ b/src/irc/core/irc-servers-setup.c @@ -28,6 +28,7 @@ #include "irc-chatnets.h" #include "irc-servers-setup.h" #include "irc-servers.h" +#include "sasl.h" /* Fill information to connection from server setup record */ static void sig_server_setup_fill_reconn(IRC_SERVER_CONNECT_REC *conn, @@ -47,12 +48,18 @@ static void sig_server_setup_fill_reconn(IRC_SERVER_CONNECT_REC *conn, static void sig_server_setup_fill_connect(IRC_SERVER_CONNECT_REC *conn) { + const char *value; + if (!IS_IRC_SERVER_CONNECT(conn)) return; - conn->alternate_nick = *settings_get_str("alternate_nick") != '\0' ? - g_strdup(settings_get_str("alternate_nick")) : NULL; - conn->usermode = g_strdup(settings_get_str("usermode")); + value = settings_get_str("alternate_nick"); + conn->alternate_nick = (value != NULL && *value != '\0') ? + g_strdup(value) : NULL; + + value = settings_get_str("usermode"); + conn->usermode = (value != NULL && *value != '\0') ? + g_strdup(value) : NULL; } static void sig_server_setup_fill_chatnet(IRC_SERVER_CONNECT_REC *conn, @@ -62,7 +69,10 @@ static void sig_server_setup_fill_chatnet(IRC_SERVER_CONNECT_REC *conn, return; g_return_if_fail(IS_IRCNET(ircnet)); - if (ircnet->nick != NULL) g_free_and_null(conn->alternate_nick); + if (ircnet->alternate_nick != NULL) { + g_free_and_null(conn->alternate_nick); + conn->alternate_nick = g_strdup(ircnet->alternate_nick); + } if (ircnet->usermode != NULL) { g_free_and_null(conn->usermode); conn->usermode = g_strdup(ircnet->usermode); @@ -79,18 +89,44 @@ static void sig_server_setup_fill_chatnet(IRC_SERVER_CONNECT_REC *conn, conn->cmd_queue_speed = ircnet->cmd_queue_speed; if (ircnet->max_query_chans > 0) conn->max_query_chans = ircnet->max_query_chans; + + /* Validate the SASL parameters filled by sig_chatnet_read() or cmd_network_add */ + conn->sasl_mechanism = SASL_MECHANISM_NONE; + conn->sasl_username = NULL; + conn->sasl_password = NULL; + + if (ircnet->sasl_mechanism != NULL) { + if (!g_ascii_strcasecmp(ircnet->sasl_mechanism, "plain")) { + /* The PLAIN method needs both the username and the password */ + if (ircnet->sasl_username != NULL && *ircnet->sasl_username && + ircnet->sasl_password != NULL && *ircnet->sasl_password) { + conn->sasl_mechanism = SASL_MECHANISM_PLAIN; + conn->sasl_username = ircnet->sasl_username; + conn->sasl_password = ircnet->sasl_password; + } else + g_warning("The fields sasl_username and sasl_password are either missing or empty"); + } + else if (!g_ascii_strcasecmp(ircnet->sasl_mechanism, "external")) { + conn->sasl_mechanism = SASL_MECHANISM_EXTERNAL; + } + else + g_warning("Unsupported SASL mechanism \"%s\" selected", ircnet->sasl_mechanism); + } } static void init_userinfo(void) { + unsigned int changed; const char *set, *nick, *user_name, *str; + changed = 0; /* check if nick/username/realname wasn't read from setup.. */ set = settings_get_str("real_name"); if (set == NULL || *set == '\0') { str = g_getenv("IRCNAME"); settings_set_str("real_name", str != NULL ? str : g_get_real_name()); + changed |= USER_SETTINGS_REAL_NAME; } /* username */ @@ -101,6 +137,7 @@ static void init_userinfo(void) str != NULL ? str : g_get_user_name()); user_name = settings_get_str("user_name"); + changed |= USER_SETTINGS_USER_NAME; } /* nick */ @@ -110,15 +147,20 @@ static void init_userinfo(void) settings_set_str("nick", str != NULL ? str : user_name); nick = settings_get_str("nick"); + changed |= USER_SETTINGS_NICK; } /* host name */ set = settings_get_str("hostname"); if (set == NULL || *set == '\0') { str = g_getenv("IRCHOST"); - if (str != NULL) + if (str != NULL) { settings_set_str("hostname", str); + changed |= USER_SETTINGS_HOSTNAME; + } } + + signal_emit("irssi init userinfo changed", 1, GUINT_TO_POINTER(changed)); } static void sig_server_setup_read(IRC_SERVER_SETUP_REC *rec, CONFIG_NODE *node) diff --git a/src/irc/core/irc-servers.c b/src/irc/core/irc-servers.c index baf8a0d2..4eaab712 100644 --- a/src/irc/core/irc-servers.c +++ b/src/irc/core/irc-servers.c @@ -32,6 +32,10 @@ #include "irc-queries.h" #include "irc-servers-setup.h" #include "irc-servers.h" +#include "irc-cap.h" +#include "sasl.h" + +#include "channels-setup.h" #include "channel-rejoin.h" #include "servers-idle.h" #include "servers-reconnect.h" @@ -71,13 +75,28 @@ static int isnickflag_func(SERVER_REC *server, char flag) static int ischannel_func(SERVER_REC *server, const char *data) { - if (*data == '@') { - /* @#channel, @+#channel */ - data++; - if (*data == '+' && ischannel(data[1])) - return 1; - } - return ischannel(*data); + IRC_SERVER_REC *irc_server = (IRC_SERVER_REC *) server; + char *chantypes, *statusmsg; + + g_return_val_if_fail(data != NULL, FALSE); + + /* empty string is no channel */ + if (*data == '\0') + return FALSE; + + chantypes = g_hash_table_lookup(irc_server->isupport, "chantypes"); + if (chantypes == NULL) + chantypes = "#&!+"; /* normal, local, secure, modeless */ + + statusmsg = g_hash_table_lookup(irc_server->isupport, "statusmsg"); + if (statusmsg == NULL) + statusmsg = "@"; + + data += strspn(data, statusmsg); + + /* strchr(3) considers the trailing NUL as part of the string, make sure + * we didn't advance too much. */ + return *data != '\0' && strchr(chantypes, *data) != NULL; } static char **split_line(const SERVER_REC *server, const char *line, @@ -97,11 +116,14 @@ static char **split_line(const SERVER_REC *server, const char *line, * the code much simpler. It's worth it. */ len -= strlen(recoded_start) + strlen(recoded_end); + g_warn_if_fail(len > 0); if (len <= 0) { /* There is no room for anything. */ g_free(recoded_start); g_free(recoded_end); - return NULL; + lines = g_new(char *, 1); + lines[0] = NULL; + return lines; } lines = recode_split(server, line, target, len, onspace); @@ -195,7 +217,6 @@ static void server_init(IRC_SERVER_REC *server) { IRC_SERVER_CONNECT_REC *conn; char *address, *ptr, *username, *cmd; - GTimeVal now; g_return_if_fail(server != NULL); @@ -214,6 +235,13 @@ static void server_init(IRC_SERVER_REC *server) g_free(cmd); } + if (conn->sasl_mechanism != SASL_MECHANISM_NONE) + cap_toggle(server, "sasl", TRUE); + + cap_toggle(server, "multi-prefix", TRUE); + + irc_send_cmd_now(server, "CAP LS"); + if (conn->password != NULL && *conn->password != '\0') { /* send password */ cmd = g_strdup_printf("PASS %s", conn->password); @@ -263,9 +291,8 @@ static void server_init(IRC_SERVER_REC *server) /* prevent the queue from sending too early, we have a max cut off of 120 secs */ /* this will reset to 1 sec after we get the 001 event */ - g_get_current_time(&now); - memcpy(&((IRC_SERVER_REC *)server)->wait_cmd, &now, sizeof(GTimeVal)); - ((IRC_SERVER_REC *)server)->wait_cmd.tv_sec += 120; + g_get_current_time(&server->wait_cmd); + g_time_val_add(&server->wait_cmd, 120 * G_USEC_PER_SEC); } SERVER_REC *irc_server_init_connect(SERVER_CONNECT_REC *conn) @@ -286,7 +313,7 @@ SERVER_REC *irc_server_init_connect(SERVER_CONNECT_REC *conn) if (server->connrec->port <= 0) { server->connrec->port = - server->connrec->use_ssl ? 6697 : 6667; + server->connrec->use_tls ? 6697 : 6667; } server->cmd_queue_speed = ircconn->cmd_queue_speed > 0 ? @@ -304,7 +331,7 @@ SERVER_REC *irc_server_init_connect(SERVER_CONNECT_REC *conn) ircconn->max_whois : DEFAULT_MAX_WHOIS; server->max_msgs_in_cmd = ircconn->max_msgs > 0 ? ircconn->max_msgs : DEFAULT_MAX_MSGS; - server->connrec->use_ssl = conn->use_ssl; + server->connrec->use_tls = conn->use_tls; modes_server_init(server); @@ -314,6 +341,8 @@ SERVER_REC *irc_server_init_connect(SERVER_CONNECT_REC *conn) void irc_server_connect(SERVER_REC *server) { + g_return_if_fail(server != NULL); + if (!server_start_connect(server)) { server_connect_unref(server->connrec); g_free(server); @@ -409,7 +438,18 @@ static void sig_disconnected(IRC_SERVER_REC *server) server_redirect_destroy(tmp->next->data); } g_slist_free(server->cmdqueue); - server->cmdqueue = NULL; + server->cmdqueue = NULL; + + gslist_free_full(server->cap_active, (GDestroyNotify) g_free); + server->cap_active = NULL; + + gslist_free_full(server->cap_supported, (GDestroyNotify) g_free); + server->cap_supported = NULL; + + gslist_free_full(server->cap_queue, (GDestroyNotify) g_free); + server->cap_queue = NULL; + + g_free_and_null(server->sasl_buffer); /* these are dynamically allocated only if isupport was sent */ g_hash_table_foreach(server->isupport, @@ -500,7 +540,7 @@ void irc_server_send_data(IRC_SERVER_REC *server, const char *data, int len) server->wait_cmd.tv_sec = 0; else { memcpy(&server->wait_cmd, &server->last_cmd, sizeof(GTimeVal)); - server->wait_cmd.tv_sec += 2 + len/100; + g_time_val_add(&server->wait_cmd, (2 + len/100) * G_USEC_PER_SEC); } } @@ -588,9 +628,16 @@ char *irc_server_get_channels(IRC_SERVER_REC *server) GString *chans, *keys; char *ret; int use_keys; + int rejoin_channels_mode; g_return_val_if_fail(server != NULL, FALSE); + rejoin_channels_mode = settings_get_choice("rejoin_channels_on_reconnect"); + + /* do we want to rejoin channels in the first place? */ + if(rejoin_channels_mode == 0) + return g_strdup(""); + chans = g_string_new(NULL); keys = g_string_new(NULL); use_keys = FALSE; @@ -598,22 +645,27 @@ char *irc_server_get_channels(IRC_SERVER_REC *server) /* get currently joined channels */ for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { CHANNEL_REC *channel = tmp->data; - - g_string_append_printf(chans, "%s,", channel->name); - g_string_append_printf(keys, "%s,", channel->key == NULL ? "x" : - channel->key); - if (channel->key != NULL) - use_keys = TRUE; + CHANNEL_SETUP_REC *setup = channel_setup_find(channel->name, channel->server->connrec->chatnet); + if ((setup != NULL && setup->autojoin && rejoin_channels_mode == 2) || rejoin_channels_mode == 1) { + g_string_append_printf(chans, "%s,", channel->name); + g_string_append_printf(keys, "%s,", channel->key == NULL ? "x" : channel->key); + if (channel->key != NULL) + use_keys = TRUE; + } } /* get also the channels that are in rejoin list */ for (tmp = server->rejoin_channels; tmp != NULL; tmp = tmp->next) { REJOIN_REC *rec = tmp->data; + CHANNEL_SETUP_REC *setup = channel_setup_find(rec->channel, server->tag); + + if ((setup != NULL && setup->autojoin && rejoin_channels_mode == 2) || rejoin_channels_mode == 1) { + g_string_append_printf(chans, "%s,", rec->channel); + g_string_append_printf(keys, "%s,", rec->key == NULL ? "x" : + rec->key); - g_string_append_printf(chans, "%s,", rec->channel); - g_string_append_printf(keys, "%s,", rec->key == NULL ? "x" : - rec->key); - if (rec->key != NULL) use_keys = TRUE; + if (rec->key != NULL) use_keys = TRUE; + } } if (chans->len > 0) { @@ -632,13 +684,12 @@ char *irc_server_get_channels(IRC_SERVER_REC *server) static void event_connected(IRC_SERVER_REC *server, const char *data, const char *from) { char *params, *nick; - GTimeVal now; g_return_if_fail(server != NULL); params = event_get_params(data, 1, &nick); - if (strcmp(server->nick, nick) != 0) { + if (g_strcmp0(server->nick, nick) != 0) { /* nick changed unexpectedly .. connected via proxy, etc. */ g_free(server->nick); server->nick = g_strdup(nick); @@ -655,8 +706,7 @@ static void event_connected(IRC_SERVER_REC *server, const char *data, const char server->real_connect_time = time(NULL); /* let the queue send now that we are identified */ - g_get_current_time(&now); - memcpy(&server->wait_cmd, &now, sizeof(GTimeVal)); + g_get_current_time(&server->wait_cmd); if (server->connrec->usermode != NULL) { /* Send the user mode, before the autosendcmd. @@ -970,6 +1020,7 @@ void irc_server_init_isupport(IRC_SERVER_REC *server) void irc_servers_init(void) { + settings_add_choice("servers", "rejoin_channels_on_reconnect", 1, "off;on;auto"); settings_add_str("misc", "usermode", DEFAULT_USER_MODE); settings_add_str("misc", "split_line_start", ""); settings_add_str("misc", "split_line_end", ""); diff --git a/src/irc/core/irc-servers.h b/src/irc/core/irc-servers.h index 7e4eeabf..09f3f81d 100644 --- a/src/irc/core/irc-servers.h +++ b/src/irc/core/irc-servers.h @@ -27,6 +27,10 @@ struct _IRC_SERVER_CONNECT_REC { char *usermode; char *alternate_nick; + int sasl_mechanism; + char *sasl_username; + char *sasl_password; + int max_cmds_at_once; int cmd_queue_speed; int max_query_chans; @@ -63,12 +67,21 @@ struct _IRC_SERVER_REC { unsigned int nick_collision:1; /* We're just now being killed because of nick collision */ unsigned int motd_got:1; /* We've received MOTD */ unsigned int isupport_sent:1; /* Server has sent us an isupport reply */ + unsigned int cap_complete:1; /* We've done the initial CAP negotiation */ + unsigned int sasl_success:1; /* Did we authenticate successfully ? */ int max_kicks_in_cmd; /* max. number of people to kick with one /KICK command */ int max_modes_in_cmd; /* max. number of mode changes in one /MODE command */ int max_whois_in_cmd; /* max. number of nicks in one /WHOIS command */ int max_msgs_in_cmd; /* max. number of targets in one /MSG */ + GSList *cap_supported; /* A list of caps supported by the server */ + GSList *cap_active; /* A list of caps active for this session */ + GSList *cap_queue; /* A list of caps to request on connection */ + + GString *sasl_buffer; /* Buffer used to reassemble a fragmented SASL payload */ + guint sasl_timeout; /* Holds the source id of the running timeout */ + /* Command sending queue */ int cmdcount; /* number of commands in `cmdqueue'. Can be more than there actually is, to make flood control remember diff --git a/src/irc/core/irc-session.c b/src/irc/core/irc-session.c index 42d82734..18e8e5c7 100644 --- a/src/irc/core/irc-session.c +++ b/src/irc/core/irc-session.c @@ -28,6 +28,8 @@ #include "irc-channels.h" #include "irc-nicklist.h" +#include "sasl.h" + struct _isupport_data { CONFIG_REC *config; CONFIG_NODE *node; }; static void session_isupport_foreach(char *key, char *value, struct _isupport_data *data) @@ -65,8 +67,12 @@ static void sig_session_save_server(IRC_SERVER_REC *server, CONFIG_REC *config, config_node_set_str(config, node, "away_reason", server->away_reason); config_node_set_bool(config, node, "emode_known", server->emode_known); + config_node_set_int(config, node, "sasl_mechanism", server->connrec->sasl_mechanism); + config_node_set_str(config, node, "sasl_username", server->connrec->sasl_username); + config_node_set_str(config, node, "sasl_password", server->connrec->sasl_password); + config_node_set_bool(config, node, "isupport_sent", server->isupport_sent); - isupport = config_node_section(node, "isupport", NODE_TYPE_BLOCK); + isupport = config_node_section(config, node, "isupport", NODE_TYPE_BLOCK); isupport_data.config = config; isupport_data.node = isupport; @@ -90,12 +96,21 @@ static void sig_session_restore_server(IRC_SERVER_REC *server, server->emode_known = config_node_get_bool(node, "emode_known", FALSE); server->isupport_sent = config_node_get_bool(node, "isupport_sent", FALSE); + server->connrec->sasl_mechanism = config_node_get_int(node, "sasl_mechanism", SASL_MECHANISM_NONE); + /* The fields below might have been filled when loading the chatnet + * description from the config and we favor the content that's been saved + * in the session file over that. */ + g_free(server->connrec->sasl_username); + server->connrec->sasl_username = g_strdup(config_node_get_str(node, "sasl_username", NULL)); + g_free(server->connrec->sasl_password); + server->connrec->sasl_password = g_strdup(config_node_get_str(node, "sasl_password", NULL)); + if (server->isupport == NULL) { server->isupport = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal); } - node = config_node_section(node, "isupport", -1); + node = config_node_section(NULL, node, "isupport", -1); tmp = node == NULL ? NULL : config_node_first(node->value); for (; tmp != NULL; tmp = config_node_next(tmp)) { diff --git a/src/irc/core/irc.c b/src/irc/core/irc.c index 509418b7..a740b0da 100644 --- a/src/irc/core/irc.c +++ b/src/irc/core/irc.c @@ -40,6 +40,8 @@ static int signal_server_incoming; # define MAX_SOCKET_READS 5 #endif +static void strip_params_colon(char *const); + /* The core of the irc_send_cmd* functions. If `raw' is TRUE, the `cmd' won't be checked at all if it's 512 bytes or not, or if it contains line feeds or not. Use with extreme caution! */ @@ -212,8 +214,12 @@ void irc_send_cmd_split(IRC_SERVER_REC *server, const char *cmd, count = 0; if (nickstr->len > 0) g_string_truncate(nickstr, nickstr->len-1); - irc_send_cmdv(server, post == NULL ? "%s %s" : "%s %s %s", - pre, nickstr->str, post); + + if (post == NULL) + irc_send_cmdv(server, "%s %s", pre, nickstr->str); + else + irc_send_cmdv(server, "%s %s %s", pre, nickstr->str, post); + g_string_truncate(nickstr, 0); if (*tmp == NULL || tmp[1] == NULL) @@ -265,8 +271,9 @@ char *event_get_params(const char *data, int count, ...) while (count-- > 0) { str = (char **) va_arg(args, char **); if (count == 0 && rest) { - /* put the rest to last parameter */ - tmp = *datad == ':' ? datad+1 : datad; + /* Put the rest into the last parameter. */ + strip_params_colon(datad); + tmp = datad; } else { tmp = event_get_param(&datad); } @@ -277,6 +284,33 @@ char *event_get_params(const char *data, int count, ...) return duprec; } +/* Given a string containing <params>, strip any colon prefixing <trailing>. */ +static void strip_params_colon(char *const params) +{ + char *s; + + if (params == NULL) { + return; + } + + s = params; + while (*s != '\0') { + if (*s == ':') { + memmove(s, s+1, strlen(s+1)+1); + return; + } + + s = strchr(s, ' '); + if (s == NULL) { + return; + } + + while (*s == ' ') { + s++; + } + } +} + static void irc_server_event(IRC_SERVER_REC *server, const char *line, const char *nick, const char *address) { diff --git a/src/irc/core/irc.h b/src/irc/core/irc.h index de19e084..b5bd833a 100644 --- a/src/irc/core/irc.h +++ b/src/irc/core/irc.h @@ -22,12 +22,6 @@ typedef struct _REDIRECT_REC REDIRECT_REC; #define isnickflag(server, a) \ (server->prefix[(int)(unsigned char) a] != '\0') -#define ischannel(a) \ - ((a) == '#' || /* normal */ \ - (a) == '&' || /* local */ \ - (a) == '!' || /* secure */ \ - (a) == '+') /* modeless */ - #define IS_IRC_ITEM(rec) (IS_IRC_CHANNEL(rec) || IS_IRC_QUERY(rec)) #define IRC_PROTOCOL (chat_protocol_lookup("IRC")) diff --git a/src/irc/core/modes.c b/src/irc/core/modes.c index ebaf4b8f..cc3d0faf 100644 --- a/src/irc/core/modes.c +++ b/src/irc/core/modes.c @@ -398,7 +398,7 @@ void parse_channel_modes(IRC_CHANNEL_REC *channel, const char *setby, old_key = NULL; } - if (strcmp(newmode->str, channel->mode) != 0) { + if (g_strcmp0(newmode->str, channel->mode) != 0) { g_free(channel->mode); channel->mode = g_strdup(newmode->str); @@ -488,7 +488,7 @@ static void event_mode(IRC_SERVER_REC *server, const char *data, params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &channel, &mode); - if (!ischannel(*channel)) { + if (!server_ischannel(SERVER(server), channel)) { /* user mode change */ parse_user_mode(server, mode); } else { @@ -536,7 +536,7 @@ static void sig_req_usermode_change(IRC_SERVER_REC *server, const char *data, params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &mode); - if (!ischannel(*target)) { + if (!server_ischannel(SERVER(server), target)) { /* we requested a user mode change, save this */ mode = modes_join(NULL, server->wanted_usermode, mode, FALSE); g_free_not_null(server->wanted_usermode); @@ -743,6 +743,7 @@ static char *get_nicks(IRC_SERVER_REC *server, WI_ITEM_REC *item, g_hash_table_lookup(optlist, "yes") == NULL) { /* too many matches */ g_string_free(str, TRUE); + g_strfreev(matches); cmd_params_free(free_arg); signal_emit("error command", 1, @@ -756,7 +757,7 @@ static char *get_nicks(IRC_SERVER_REC *server, WI_ITEM_REC *item, if (str->len > 0) g_string_truncate(str, str->len-1); ret = str->str; g_string_free(str, FALSE); - + g_strfreev(matches); cmd_params_free(free_arg); *ret_channel = channel; @@ -835,14 +836,14 @@ static void cmd_mode(const char *data, IRC_SERVER_REC *server, if (*data == '+' || *data == '-') { target = "*"; - if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_GETREST, &mode)) + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS, &mode)) return; } else { - if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &target, &mode)) + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS, &target, &mode)) return; } - if (strcmp(target, "*") == 0) { + if (g_strcmp0(target, "*") == 0) { if (!IS_IRC_CHANNEL(channel)) cmd_param_error(CMDERR_NOT_JOINED); @@ -856,7 +857,7 @@ static void cmd_mode(const char *data, IRC_SERVER_REC *server, target = chanrec->name; irc_send_cmdv(server, "MODE %s", target); - } else if (ischannel(*target)) + } else if (server_ischannel(SERVER(server), target)) channel_set_mode(server, target, mode); else { if (g_ascii_strcasecmp(target, server->nick) == 0) { diff --git a/src/irc/core/sasl.c b/src/irc/core/sasl.c new file mode 100644 index 00000000..2b589579 --- /dev/null +++ b/src/irc/core/sasl.c @@ -0,0 +1,335 @@ +/* + fe-sasl.c : irssi + + Copyright (C) 2015 The Lemon Man + + 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 "misc.h" +#include "settings.h" + +#include "irc-cap.h" +#include "irc-servers.h" +#include "sasl.h" + +/* + * Based on IRCv3 SASL Extension Specification: + * http://ircv3.net/specs/extensions/sasl-3.1.html + */ +#define AUTHENTICATE_CHUNK_SIZE 400 /* bytes */ + +/* + * Maximum size to allow the buffer to grow to before the next fragment comes in. Note that + * due to the way fragmentation works, the maximum message size will actually be: + * floor(AUTHENTICATE_MAX_SIZE / AUTHENTICATE_CHUNK_SIZE) + AUTHENTICATE_CHUNK_SIZE - 1 + */ +#define AUTHENTICATE_MAX_SIZE 8192 /* bytes */ + +#define SASL_TIMEOUT (20 * 1000) /* ms */ + +static gboolean sasl_timeout(IRC_SERVER_REC *server) +{ + /* The authentication timed out, we can't do much beside terminating it */ + irc_send_cmd_now(server, "AUTHENTICATE *"); + cap_finish_negotiation(server); + + server->sasl_timeout = 0; + server->sasl_success = FALSE; + + signal_emit("server sasl failure", 2, server, "The authentication timed out"); + + return FALSE; +} + +static void sasl_start(IRC_SERVER_REC *server, const char *data, const char *from) +{ + IRC_SERVER_CONNECT_REC *conn; + + conn = server->connrec; + + switch (conn->sasl_mechanism) { + case SASL_MECHANISM_PLAIN: + irc_send_cmd_now(server, "AUTHENTICATE PLAIN"); + break; + + case SASL_MECHANISM_EXTERNAL: + irc_send_cmd_now(server, "AUTHENTICATE EXTERNAL"); + break; + } + server->sasl_timeout = g_timeout_add(SASL_TIMEOUT, (GSourceFunc) sasl_timeout, server); +} + +static void sasl_fail(IRC_SERVER_REC *server, const char *data, const char *from) +{ + char *params, *error; + + /* Stop any pending timeout, if any */ + if (server->sasl_timeout != 0) { + g_source_remove(server->sasl_timeout); + server->sasl_timeout = 0; + } + + params = event_get_params(data, 2, NULL, &error); + + server->sasl_success = FALSE; + + signal_emit("server sasl failure", 2, server, error); + + /* Terminate the negotiation */ + cap_finish_negotiation(server); + + g_free(params); +} + +static void sasl_already(IRC_SERVER_REC *server, const char *data, const char *from) +{ + if (server->sasl_timeout != 0) { + g_source_remove(server->sasl_timeout); + server->sasl_timeout = 0; + } + + server->sasl_success = TRUE; + + signal_emit("server sasl success", 1, server); + + /* We're already authenticated, do nothing */ + cap_finish_negotiation(server); +} + +static void sasl_success(IRC_SERVER_REC *server, const char *data, const char *from) +{ + if (server->sasl_timeout != 0) { + g_source_remove(server->sasl_timeout); + server->sasl_timeout = 0; + } + + server->sasl_success = TRUE; + + signal_emit("server sasl success", 1, server); + + /* The authentication succeeded, time to finish the CAP negotiation */ + cap_finish_negotiation(server); +} + +/* + * Responsible for reassembling incoming SASL requests. SASL requests must be split + * into 400 byte requests to stay below the IRC command length limit of 512 bytes. + * The spec says that if there is 400 bytes, then there is expected to be a + * continuation in the next chunk. If a message is exactly a multiple of 400 bytes, + * there must be a blank message of "AUTHENTICATE +" to indicate the end. + * + * This function returns the fully reassembled and decoded AUTHENTICATION message if + * completed or NULL if there are more messages expected. + */ +static gboolean sasl_reassemble_incoming(IRC_SERVER_REC *server, const char *fragment, GString **decoded) +{ + GString *enc_req; + gsize fragment_len; + + fragment_len = strlen(fragment); + + /* Check if there is an existing fragment to prepend. */ + if (server->sasl_buffer != NULL) { + if (g_strcmp0("+", fragment) == 0) { + enc_req = server->sasl_buffer; + } else { + enc_req = g_string_append_len(server->sasl_buffer, fragment, fragment_len); + } + server->sasl_buffer = NULL; + } else { + enc_req = g_string_new_len(fragment, fragment_len); + } + + /* + * Fail authentication with this server. They have sent too much data. + */ + if (enc_req->len > AUTHENTICATE_MAX_SIZE) { + return FALSE; + } + + /* + * If the the request is exactly the chunk size, this is a fragment + * and more data is expected. + */ + if (fragment_len == AUTHENTICATE_CHUNK_SIZE) { + server->sasl_buffer = enc_req; + return TRUE; + } + + if (enc_req->len == 1 && *enc_req->str == '+') { + *decoded = g_string_new_len("", 0); + } else { + gsize dec_len; + gint state = 0; + guint save = 0; + + /* Since we're not going to use the enc_req GString anymore we + * can perform the decoding in place. */ + dec_len = g_base64_decode_step(enc_req->str, enc_req->len, + (guchar *)enc_req->str, + &state, &save); + /* A copy of the data is made when the GString is created. */ + *decoded = g_string_new_len(enc_req->str, dec_len); + } + + g_string_free(enc_req, TRUE); + return TRUE; +} + +/* + * Splits the response into appropriately sized chunks for the AUTHENTICATION + * command to be sent to the IRC server. If |response| is NULL, then the empty + * response is sent to the server. + */ +void sasl_send_response(IRC_SERVER_REC *server, GString *response) +{ + char *enc; + size_t offset, enc_len, chunk_len; + + if (response == NULL) { + irc_send_cmdv(server, "AUTHENTICATE +"); + return; + } + + enc = g_base64_encode((guchar *) response->str, response->len); + enc_len = strlen(enc); + + for (offset = 0; offset < enc_len; offset += AUTHENTICATE_CHUNK_SIZE) { + chunk_len = enc_len - offset; + if (chunk_len > AUTHENTICATE_CHUNK_SIZE) + chunk_len = AUTHENTICATE_CHUNK_SIZE; + + irc_send_cmdv(server, "AUTHENTICATE %.*s", (int) chunk_len, enc + offset); + } + + if (offset == enc_len) { + irc_send_cmdv(server, "AUTHENTICATE +"); + } + g_free(enc); +} + +/* + * Called when the incoming SASL request is completely received. + */ +static void sasl_step_complete(IRC_SERVER_REC *server, GString *data) +{ + IRC_SERVER_CONNECT_REC *conn; + GString *resp; + + conn = server->connrec; + + switch (conn->sasl_mechanism) { + case SASL_MECHANISM_PLAIN: + /* At this point we assume that conn->sasl_{username, password} are non-NULL. + * The PLAIN mechanism expects a NULL-separated string composed by the authorization identity, the + * authentication identity and the password. + * The authorization identity field is explicitly set to the user provided username. + */ + + resp = g_string_new(NULL); + + g_string_append(resp, conn->sasl_username); + g_string_append_c(resp, '\0'); + g_string_append(resp, conn->sasl_username); + g_string_append_c(resp, '\0'); + g_string_append(resp, conn->sasl_password); + + sasl_send_response(server, resp); + g_string_free(resp, TRUE); + + break; + + case SASL_MECHANISM_EXTERNAL: + /* Empty response */ + sasl_send_response(server, NULL); + break; + } +} + +static void sasl_step_fail(IRC_SERVER_REC *server) +{ + irc_send_cmd_now(server, "AUTHENTICATE *"); + cap_finish_negotiation(server); + + server->sasl_timeout = 0; + + signal_emit("server sasl failure", 2, server, "The server sent an invalid payload"); +} + +static void sasl_step(IRC_SERVER_REC *server, const char *data, const char *from) +{ + GString *req = NULL; + + /* Stop the timer */ + if (server->sasl_timeout != 0) { + g_source_remove(server->sasl_timeout); + server->sasl_timeout = 0; + } + + if (!sasl_reassemble_incoming(server, data, &req)) { + sasl_step_fail(server); + return; + } + + if (req != NULL) { + sasl_step_complete(server, req); + g_string_free(req, TRUE); + } + + /* We expect a response within a reasonable time */ + server->sasl_timeout = g_timeout_add(SASL_TIMEOUT, (GSourceFunc) sasl_timeout, server); +} + +static void sasl_disconnected(IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + if (!IS_IRC_SERVER(server)) { + return; + } + + if (server->sasl_timeout != 0) { + g_source_remove(server->sasl_timeout); + server->sasl_timeout = 0; + } +} + +void sasl_init(void) +{ + signal_add_first("server cap ack sasl", (SIGNAL_FUNC) sasl_start); + signal_add_first("event authenticate", (SIGNAL_FUNC) sasl_step); + signal_add_first("event 903", (SIGNAL_FUNC) sasl_success); + signal_add_first("event 902", (SIGNAL_FUNC) sasl_fail); + signal_add_first("event 904", (SIGNAL_FUNC) sasl_fail); + signal_add_first("event 905", (SIGNAL_FUNC) sasl_fail); + signal_add_first("event 906", (SIGNAL_FUNC) sasl_fail); + signal_add_first("event 907", (SIGNAL_FUNC) sasl_already); + signal_add_first("server disconnected", (SIGNAL_FUNC) sasl_disconnected); +} + +void sasl_deinit(void) +{ + signal_remove("server cap ack sasl", (SIGNAL_FUNC) sasl_start); + signal_remove("event authenticate", (SIGNAL_FUNC) sasl_step); + signal_remove("event 903", (SIGNAL_FUNC) sasl_success); + signal_remove("event 902", (SIGNAL_FUNC) sasl_fail); + signal_remove("event 904", (SIGNAL_FUNC) sasl_fail); + signal_remove("event 905", (SIGNAL_FUNC) sasl_fail); + signal_remove("event 906", (SIGNAL_FUNC) sasl_fail); + signal_remove("event 907", (SIGNAL_FUNC) sasl_already); + signal_remove("server disconnected", (SIGNAL_FUNC) sasl_disconnected); +} diff --git a/src/irc/core/sasl.h b/src/irc/core/sasl.h new file mode 100644 index 00000000..0693989d --- /dev/null +++ b/src/irc/core/sasl.h @@ -0,0 +1,34 @@ +/* + fe-sasl.c : irssi + + Copyright (C) 2015 The Lemon Man + + 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. +*/ + +#ifndef __SASL_H +#define __SASL_H + +enum { + SASL_MECHANISM_NONE = 0, + SASL_MECHANISM_PLAIN, + SASL_MECHANISM_EXTERNAL, + SASL_MECHANISM_MAX +}; + +void sasl_init(void); +void sasl_deinit(void); + +#endif diff --git a/src/irc/core/servers-redirect.c b/src/irc/core/servers-redirect.c index 518248cb..aeb9c010 100644 --- a/src/irc/core/servers-redirect.c +++ b/src/irc/core/servers-redirect.c @@ -339,7 +339,7 @@ static GSList *redirect_cmd_list_find(GSList *list, const char *event) while (list != NULL) { const char *str = list->data; - if (strcmp(str, event) == 0) + if (g_strcmp0(str, event) == 0) break; list = list->next->next; } @@ -365,7 +365,7 @@ static const char *redirect_match(REDIRECT_REC *redirect, const char *event, use the default signal */ signal = NULL; for (tmp = redirect->signals; tmp != NULL; tmp = tmp->next->next) { - if (strcmp(tmp->data, event) == 0) { + if (g_strcmp0(tmp->data, event) == 0) { signal = tmp->next->data; break; } @@ -433,9 +433,11 @@ static void redirect_abort(IRC_SERVER_REC *server, REDIRECT_REC *rec) if (rec->aborted || !rec->destroyed) { /* emit the failure signal */ - str = g_strdup_printf(rec->failure_signal != NULL ? - "FAILED %s: %s" : "FAILED %s", - rec->cmd->name, rec->failure_signal); + if (rec->failure_signal != NULL) + str = g_strdup_printf("FAILED %s: %s", rec->cmd->name, rec->failure_signal); + else + str = g_strdup_printf("FAILED %s", rec->cmd->name); + rawlog_redirect(server->rawlog, str); g_free(str); @@ -527,7 +529,7 @@ server_redirect_get(IRC_SERVER_REC *server, const char *prefix, next = ptr->next; r = ptr->data; if (prefix != NULL && r->prefix != NULL && - strcmp(prefix, r->prefix)) { + g_strcmp0(prefix, r->prefix)) { /* not from this server */ continue; } diff --git a/src/irc/dcc/dcc-autoget.c b/src/irc/dcc/dcc-autoget.c index 4768641b..dd12f438 100644 --- a/src/irc/dcc/dcc-autoget.c +++ b/src/irc/dcc/dcc-autoget.c @@ -23,6 +23,7 @@ #include "masks.h" #include "settings.h" #include "servers.h" +#include "misc.h" #include "dcc-get.h" @@ -30,7 +31,7 @@ static void sig_dcc_request(GET_DCC_REC *dcc, const char *nickaddr) { struct stat statbuf; const char *masks; - char *str, *file; + char *str, *file, *esc_arg; int max_size; if (!IS_DCC_GET(dcc)) return; @@ -51,13 +52,13 @@ static void sig_dcc_request(GET_DCC_REC *dcc, const char *nickaddr) /* Unless specifically said in dcc_autoget_masks, don't do autogets sent to channels. */ - if (*masks == '\0' && dcc->target != NULL && ischannel(*dcc->target)) + if (*masks == '\0' && dcc->target != NULL && server_ischannel(SERVER(dcc->server), dcc->target)) return; /* don't autoget files beginning with a dot, if download dir is our home dir (stupid kludge for stupid people) */ if (*dcc->arg == '.' && - strcmp(settings_get_str("dcc_download_path"), "~") == 0) + g_strcmp0(settings_get_str("dcc_download_path"), "~") == 0) return; /* check file size limit, NOTE: it's still possible to send a @@ -68,11 +69,13 @@ static void sig_dcc_request(GET_DCC_REC *dcc, const char *nickaddr) /* ok. but do we want/need to resume? */ file = dcc_get_download_path(dcc->arg); + esc_arg = escape_string(dcc->arg); str = g_strdup_printf(settings_get_bool("dcc_autoresume") && stat(file, &statbuf) == 0 ? - "RESUME %s %s" : "GET %s %s", - dcc->nick, dcc->arg); + "RESUME %s \"%s\"" : "GET %s \"%s\"", + dcc->nick, esc_arg); signal_emit("command dcc", 2, str, dcc->server); + g_free(esc_arg); g_free(file); g_free(str); } diff --git a/src/irc/dcc/dcc-chat.c b/src/irc/dcc/dcc-chat.c index 8ee4decd..88c577f7 100644 --- a/src/irc/dcc/dcc-chat.c +++ b/src/irc/dcc/dcc-chat.c @@ -66,6 +66,13 @@ CHAT_DCC_REC *dcc_chat_create(IRC_SERVER_REC *server, dcc->id = dcc_chat_get_new_id(nick); dcc_init_rec(DCC(dcc), server, chat, nick, arg); + if (dcc->module_data == NULL) { + /* failed to successfully init; TODO: change init_rec API */ + g_free(dcc->id); + g_free(dcc); + return NULL; + } + return dcc; } @@ -191,7 +198,7 @@ static void cmd_msg(const char *data, SERVER_REC *server, WI_ITEM_REC *item) return; /* handle only DCC messages */ - if (strcmp(target, "*") == 0) + if (g_strcmp0(target, "*") == 0) dcc = item_get_dcc(item); else if (*target == '=') dcc = dcc_chat_find_id(target+1); @@ -471,6 +478,7 @@ static void cmd_dcc_chat(const char *data, IRC_SERVER_REC *server) /* We are accepting a passive DCC CHAT. */ dcc_chat_passive(dcc); } + cmd_params_free(free_arg); return; } @@ -485,6 +493,11 @@ static void cmd_dcc_chat(const char *data, IRC_SERVER_REC *server) cmd_param_error(CMDERR_NOT_CONNECTED); dcc = dcc_chat_create(server, NULL, nick, "chat"); + if (dcc == NULL) { + cmd_params_free(free_arg); + g_warn_if_reached(); + return; + } if (g_hash_table_lookup(optlist, "passive") == NULL) { /* Standard DCC CHAT... let's listen for incoming connections */ @@ -619,13 +632,16 @@ static void ctcp_msg_dcc_chat(IRC_SERVER_REC *server, const char *data, /* CHAT <unused> <address> <port> */ /* CHAT <unused> <address> 0 <id> (DCC CHAT passive protocol) */ params = g_strsplit(data, " ", -1); - paramcount = strarray_length(params); + paramcount = g_strv_length(params); if (paramcount < 3) { g_strfreev(params); return; } - passive = paramcount == 4 && strcmp(params[2], "0") == 0; + passive = paramcount == 4 && g_strcmp0(params[2], "0") == 0; + + if (nick == NULL) + nick = ""; dcc = DCC_CHAT(dcc_find_request(DCC_CHAT_TYPE, nick, NULL)); if (dcc != NULL) { @@ -658,6 +674,11 @@ static void ctcp_msg_dcc_chat(IRC_SERVER_REC *server, const char *data, } dcc = dcc_chat_create(server, chat, nick, params[0]); + if (dcc == NULL) { + g_strfreev(params); + g_warn_if_reached(); + return; + } dcc->target = g_strdup(target); dcc->port = atoi(params[2]); diff --git a/src/irc/dcc/dcc-get.c b/src/irc/dcc/dcc-get.c index 69fdc746..cecbb076 100644 --- a/src/irc/dcc/dcc-get.c +++ b/src/irc/dcc/dcc-get.c @@ -30,6 +30,8 @@ #include "dcc-get.h" #include "dcc-send.h" +static char *dcc_get_recv_buffer; + GET_DCC_REC *dcc_get_create(IRC_SERVER_REC *server, CHAT_DCC_REC *chat, const char *nick, const char *arg) { @@ -41,6 +43,12 @@ GET_DCC_REC *dcc_get_create(IRC_SERVER_REC *server, CHAT_DCC_REC *chat, dcc->fhandle = -1; dcc_init_rec(DCC(dcc), server, chat, nick, arg); + if (dcc->module_data == NULL) { + /* failed to successfully init; TODO: change API */ + g_free(dcc); + return NULL; + } + return dcc; } @@ -139,14 +147,20 @@ static void sig_dccget_send(GET_DCC_REC *dcc) dcc_get_send_received(dcc); } +#define DCC_GET_RECV_BUFFER_SIZE 32768 + /* input function: DCC GET received data */ static void sig_dccget_receive(GET_DCC_REC *dcc) { - char buffer[512]; int ret; + if (dcc_get_recv_buffer == NULL) { + dcc_get_recv_buffer = g_malloc(DCC_GET_RECV_BUFFER_SIZE); + } + for (;;) { - ret = net_receive(dcc->handle, buffer, sizeof(buffer)); + ret = net_receive(dcc->handle, dcc_get_recv_buffer, + DCC_GET_RECV_BUFFER_SIZE); if (ret == 0) break; if (ret < 0) { @@ -156,7 +170,7 @@ static void sig_dccget_receive(GET_DCC_REC *dcc) return; } - if (write(dcc->fhandle, buffer, ret) != ret) { + if (write(dcc->fhandle, dcc_get_recv_buffer, ret) != ret) { /* most probably out of disk space */ signal_emit("dcc error write", 2, dcc, g_strerror(errno)); @@ -226,6 +240,8 @@ void sig_dccget_connected(GET_DCC_REC *dcc) else ret = fchmod(temphandle, dcc_file_create_mode); + close(temphandle); + if (ret != -1) { ret = link(tempfname, dcc->file); @@ -249,7 +265,6 @@ void sig_dccget_connected(GET_DCC_REC *dcc) /* close/remove the temp file */ ret_errno = errno; - close(temphandle); unlink(tempfname); g_free(tempfname); @@ -373,6 +388,8 @@ int get_file_params_count(char **params, int paramcount) if (*params[0] == '"') { /* quoted file name? */ for (pos = 0; pos < paramcount-3; pos++) { + if (strlen(params[pos]) == 0) + continue; if (params[pos][strlen(params[pos])-1] == '"' && get_params_match(params, pos+1)) return pos+1; @@ -419,10 +436,15 @@ static void ctcp_msg_dcc_send(IRC_SERVER_REC *server, const char *data, int p_id = -1; int passive = FALSE; + if (addr == NULL) + addr = ""; + if (nick == NULL) + nick = ""; + /* SEND <file name> <address> <port> <size> [...] */ /* SEND <file name> <address> 0 <size> <id> (DCC SEND passive protocol) */ params = g_strsplit(data, " ", -1); - paramcount = strarray_length(params); + paramcount = g_strv_length(params); if (paramcount < 4) { signal_emit("dcc error ctcp", 5, "SEND", data, @@ -472,8 +494,8 @@ static void ctcp_msg_dcc_send(IRC_SERVER_REC *server, const char *data, net_ip2host(&temp_dcc->addr, temp_dcc->addrstr); else { /* with IPv6, show it to us as it was sent */ - strocpy(temp_dcc->addrstr, address, - sizeof(temp_dcc->addrstr)); + g_strlcpy(temp_dcc->addrstr, address, + sizeof(temp_dcc->addrstr)); } /* This new signal is added to let us invoke @@ -497,6 +519,12 @@ static void ctcp_msg_dcc_send(IRC_SERVER_REC *server, const char *data, dcc_destroy(DCC(dcc)); /* remove the old DCC */ dcc = dcc_get_create(server, chat, nick, fname); + if (dcc == NULL) { + g_free(address); + g_free(fname); + g_warn_if_reached(); + return; + } dcc->target = g_strdup(target); if (passive && port == 0) @@ -507,7 +535,7 @@ static void ctcp_msg_dcc_send(IRC_SERVER_REC *server, const char *data, net_ip2host(&dcc->addr, dcc->addrstr); else { /* with IPv6, show it to us as it was sent */ - strocpy(dcc->addrstr, address, sizeof(dcc->addrstr)); + g_strlcpy(dcc->addrstr, address, sizeof(dcc->addrstr)); } dcc->port = port; dcc->size = size; @@ -525,14 +553,14 @@ void cmd_dcc_receive(const char *data, DCC_GET_FUNC accept_func, { GET_DCC_REC *dcc; GSList *tmp, *next; - char *nick, *fname; + char *nick, *arg, *fname; void *free_arg; int found; g_return_if_fail(data != NULL); - if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, - &nick, &fname)) + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | + PARAM_FLAG_STRIP_TRAILING_WS, &nick, &arg)) return; if (*nick == '\0') { @@ -547,6 +575,8 @@ void cmd_dcc_receive(const char *data, DCC_GET_FUNC accept_func, return; } + fname = cmd_get_quoted_param(&arg); + found = FALSE; for (tmp = dcc_conns; tmp != NULL; tmp = next) { GET_DCC_REC *dcc = tmp->data; @@ -554,7 +584,7 @@ void cmd_dcc_receive(const char *data, DCC_GET_FUNC accept_func, next = tmp->next; if (IS_DCC_GET(dcc) && g_ascii_strcasecmp(dcc->nick, nick) == 0 && (dcc_is_waiting_user(dcc) || dcc->from_dccserver) && - (*fname == '\0' || strcmp(dcc->arg, fname) == 0)) { + (*fname == '\0' || g_strcmp0(dcc->arg, fname) == 0)) { found = TRUE; if (!dcc_is_passive(dcc)) accept_func(dcc); @@ -593,4 +623,5 @@ void dcc_get_deinit(void) signal_remove("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed); signal_remove("ctcp msg dcc send", (SIGNAL_FUNC) ctcp_msg_dcc_send); command_unbind("dcc get", (SIGNAL_FUNC) cmd_dcc_get); + g_free_and_null(dcc_get_recv_buffer); } diff --git a/src/irc/dcc/dcc-resume.c b/src/irc/dcc/dcc-resume.c index 11b28aef..ce0ac925 100644 --- a/src/irc/dcc/dcc-resume.c +++ b/src/irc/dcc/dcc-resume.c @@ -62,6 +62,8 @@ int get_file_params_count_resume(char **params, int paramcount) if (*params[0] == '"') { /* quoted file name? */ for (pos = 0; pos < paramcount-2; pos++) { + if (strlen(params[pos]) == 0) + continue; if (params[pos][strlen(params[pos])-1] == '"' && get_params_match_resume(params, pos+1)) return pos+1; @@ -88,7 +90,7 @@ static int dcc_ctcp_resume_parse(int type, const char *data, const char *nick, /* RESUME|ACCEPT <file name> <port> <size> */ /* RESUME|ACCEPT <file name> 0 <size> <id> (passive protocol) */ params = g_strsplit(data, " ", -1); - paramcount = strarray_length(params); + paramcount = g_strv_length(params); if (paramcount < 3) return 0; diff --git a/src/irc/dcc/dcc-send.c b/src/irc/dcc/dcc-send.c index 2ce84f18..912129b7 100644 --- a/src/irc/dcc/dcc-send.c +++ b/src/irc/dcc/dcc-send.c @@ -174,8 +174,8 @@ static void cmd_dcc_send(const char *data, IRC_SERVER_REC *server, int queue, mode, passive; if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS | - PARAM_FLAG_GETREST, "dcc send", - &optlist, &nick, &fileargs)) + PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS, + "dcc send", &optlist, &nick, &fileargs)) return; chat = item_get_dcc(item); @@ -237,6 +237,12 @@ static SEND_DCC_REC *dcc_send_create(IRC_SERVER_REC *server, dcc->queue = -1; dcc_init_rec(DCC(dcc), server, chat, nick, arg); + if (dcc->module_data == NULL) { + /* failed to successfully init; TODO: change API */ + g_free(dcc); + return NULL; + } + return dcc; } @@ -417,6 +423,10 @@ static int dcc_send_one_file(int queue, const char *target, const char *fname, dcc = dcc_send_create(server, chat, target, str); g_free(str); + if (dcc == NULL) { + g_warn_if_reached(); + return FALSE; + } dcc->handle = handle; dcc->port = port; diff --git a/src/irc/dcc/dcc-server.c b/src/irc/dcc/dcc-server.c index 30224ff9..7ae572cd 100644 --- a/src/irc/dcc/dcc-server.c +++ b/src/irc/dcc/dcc-server.c @@ -245,7 +245,7 @@ static void dcc_server_msg(SERVER_DCC_REC *dcc, const char *msg) /* 120 clientnickname filesize filename */ params = g_strsplit(msg, " ", -1); - paramcount = strarray_length(params); + paramcount = g_strv_length(params); if (paramcount < 3) { g_strfreev(params); diff --git a/src/irc/dcc/dcc.c b/src/irc/dcc/dcc.c index 6f0d5c81..c51f7c7a 100644 --- a/src/irc/dcc/dcc.c +++ b/src/irc/dcc/dcc.c @@ -149,7 +149,7 @@ DCC_REC *dcc_find_request(int type, const char *nick, const char *arg) if (dcc->type == type && !dcc_is_connected(dcc) && g_ascii_strcasecmp(dcc->nick, nick) == 0 && - (arg == NULL || strcmp(dcc->arg, arg) == 0)) + (arg == NULL || g_strcmp0(dcc->arg, arg) == 0)) return dcc; } @@ -490,7 +490,7 @@ static void event_no_such_nick(IRC_SERVER_REC *server, char *data) static void cmd_dcc_close(char *data, IRC_SERVER_REC *server) { GSList *tmp, *next; - char *typestr, *nick, *arg; + char *typestr, *nick, *arg, *fname; void *free_arg; int found, type; @@ -510,13 +510,15 @@ static void cmd_dcc_close(char *data, IRC_SERVER_REC *server) 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 && - (*arg == '\0' || strcmp(dcc->arg, arg) == 0)) { + (*fname == '\0' || g_strcmp0(dcc->arg, fname) == 0)) { dcc_reject(dcc, server); found = TRUE; } diff --git a/src/irc/flood/autoignore.c b/src/irc/flood/autoignore.c index 250a1fe8..86ff3ec5 100644 --- a/src/irc/flood/autoignore.c +++ b/src/irc/flood/autoignore.c @@ -66,7 +66,7 @@ static void sig_flood(IRC_SERVER_REC *server, const char *nick, const char *host mask = g_strdup_printf("%s!%s", nick, host); if (level & check_level) { - rec = ignore_find(server->tag, mask, NULL); + rec = ignore_find_full(server->tag, mask, NULL, NULL, 0); if (rec == NULL) autoignore_add(server, mask, level); else diff --git a/src/irc/flood/flood.c b/src/irc/flood/flood.c index 29502ca6..0944a6eb 100644 --- a/src/irc/flood/flood.c +++ b/src/irc/flood/flood.c @@ -250,7 +250,7 @@ static void flood_privmsg(IRC_SERVER_REC *server, const char *data, params = event_get_params(data, 2, &target, &text); - level = ischannel(*target) ? MSGLEVEL_PUBLIC : MSGLEVEL_MSGS; + level = server_ischannel(SERVER(server), target) ? MSGLEVEL_PUBLIC : MSGLEVEL_MSGS; if (addr != NULL && !ignore_check(SERVER(server), nick, addr, target, text, level)) flood_newmsg(server, level, nick, addr, target); @@ -287,7 +287,7 @@ static void flood_ctcp(IRC_SERVER_REC *server, const char *data, return; level = g_ascii_strncasecmp(data, "ACTION ", 7) != 0 ? MSGLEVEL_CTCPS : - (ischannel(*target) ? MSGLEVEL_PUBLIC : MSGLEVEL_MSGS); + (server_ischannel(SERVER(server), target) ? MSGLEVEL_PUBLIC : MSGLEVEL_MSGS); if (!ignore_check(SERVER(server), nick, addr, target, data, level)) flood_newmsg(server, level, nick, addr, target); } diff --git a/src/irc/notifylist/notify-commands.c b/src/irc/notifylist/notify-commands.c index 67076106..0d4fd4f2 100644 --- a/src/irc/notifylist/notify-commands.c +++ b/src/irc/notifylist/notify-commands.c @@ -36,7 +36,8 @@ static void cmd_notify(gchar *data) g_return_if_fail(data != NULL); - if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS | PARAM_FLAG_GETREST, + if (!cmd_get_params(data, &free_arg, + 2 | PARAM_FLAG_OPTIONS | PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS, "notify", &optlist, &mask, &ircnets)) return; if (*mask == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); diff --git a/src/irc/notifylist/notify-ison.c b/src/irc/notifylist/notify-ison.c index f9ca7b37..8a8865cc 100644 --- a/src/irc/notifylist/notify-ison.c +++ b/src/irc/notifylist/notify-ison.c @@ -80,6 +80,10 @@ static void ison_send(IRC_SERVER_REC *server, GString *cmd) { MODULE_SERVER_REC *mserver; + if (!server->connected) { + return; + } + mserver = MODULE_DATA(server); mserver->ison_count++; diff --git a/src/irc/notifylist/notify-setup.c b/src/irc/notifylist/notify-setup.c index d6f91361..9ea481ca 100644 --- a/src/irc/notifylist/notify-setup.c +++ b/src/irc/notifylist/notify-setup.c @@ -30,7 +30,7 @@ void notifylist_add_config(NOTIFYLIST_REC *rec) CONFIG_NODE *node; node = iconfig_node_traverse("notifies", TRUE); - node = config_node_section(node, rec->mask, NODE_TYPE_BLOCK); + node = iconfig_node_section(node, rec->mask, NODE_TYPE_BLOCK); if (rec->away_check) iconfig_node_set_bool(node, "away_check", TRUE); @@ -39,7 +39,7 @@ void notifylist_add_config(NOTIFYLIST_REC *rec) iconfig_node_set_str(node, "ircnets", NULL); if (rec->ircnets != NULL && *rec->ircnets != NULL) { - node = config_node_section(node, "ircnets", NODE_TYPE_LIST); + node = iconfig_node_section(node, "ircnets", NODE_TYPE_LIST); iconfig_node_add_list(node, rec->ircnets); } } @@ -73,7 +73,7 @@ void notifylist_read_config(void) rec->mask = g_strdup(node->key); rec->away_check = config_node_get_bool(node, "away_check", FALSE); - node = config_node_section(node, "ircnets", -1); + node = iconfig_node_section(node, "ircnets", -1); if (node != NULL) rec->ircnets = config_node_get_list(node); } } diff --git a/src/irc/notifylist/notifylist.c b/src/irc/notifylist/notifylist.c index b0b7ee1c..573f7a7f 100644 --- a/src/irc/notifylist/notifylist.c +++ b/src/irc/notifylist/notifylist.c @@ -91,7 +91,7 @@ int notifylist_ircnets_match(NOTIFYLIST_REC *rec, const char *ircnet) if (rec->ircnets == NULL) return TRUE; if (ircnet == NULL) return FALSE; - if (strcmp(ircnet, "*") == 0) return TRUE; + if (g_strcmp0(ircnet, "*") == 0) return TRUE; for (tmp = rec->ircnets; *tmp != NULL; tmp++) { if (g_ascii_strcasecmp(*tmp, ircnet) == 0) diff --git a/src/irc/proxy/dump.c b/src/irc/proxy/dump.c index 80fd7c2e..e39c21a6 100644 --- a/src/irc/proxy/dump.c +++ b/src/irc/proxy/dump.c @@ -83,7 +83,7 @@ void proxy_outserver(CLIENT_REC *client, const char *data, ...) va_start(args, data); str = g_strdup_vprintf(data, args); - proxy_outdata(client, ":%s!%s@proxy %s\n", client->nick, + proxy_outdata(client, ":%s!%s@proxy %s\r\n", client->nick, settings_get_str("user_name"), str); g_free(str); @@ -106,7 +106,7 @@ void proxy_outserver_all(IRC_SERVER_REC *server, const char *data, ...) CLIENT_REC *rec = tmp->data; if (rec->connected && rec->server == server) { - proxy_outdata(rec, ":%s!%s@proxy %s\n", rec->nick, + proxy_outdata(rec, ":%s!%s@proxy %s\r\n", rec->nick, settings_get_str("user_name"), str); } } @@ -132,7 +132,7 @@ void proxy_outserver_all_except(CLIENT_REC *client, const char *data, ...) if (rec->connected && rec != client && rec->server == client->server) { - proxy_outdata(rec, ":%s!%s@proxy %s\n", rec->nick, + proxy_outdata(rec, ":%s!%s@proxy %s\r\n", rec->nick, settings_get_str("user_name"), str); } } @@ -169,7 +169,7 @@ static void dump_join(IRC_CHANNEL_REC *channel, CLIENT_REC *client) NICK_REC *nick = tmp->data; if (str->len >= 500) { - g_string_append_c(str, '\n'); + g_string_append(str, "\r\n"); proxy_outdata(client, "%s", str->str); create_names_start(str, channel, client); first = TRUE; @@ -186,21 +186,21 @@ static void dump_join(IRC_CHANNEL_REC *channel, CLIENT_REC *client) } g_slist_free(nicks); - g_string_append_c(str, '\n'); + g_string_append(str, "\r\n"); proxy_outdata(client, "%s", str->str); g_string_free(str, TRUE); - proxy_outdata(client, ":%s 366 %s %s :End of /NAMES list.\n", + proxy_outdata(client, ":%s 366 %s %s :End of /NAMES list.\r\n", client->proxy_address, client->nick, channel->name); if (channel->topic != NULL) { /* this is needed because the topic may be encoded into other charsets internaly */ recoded = recode_out(SERVER(client->server), channel->topic, channel->name); - proxy_outdata(client, ":%s 332 %s %s :%s\n", + proxy_outdata(client, ":%s 332 %s %s :%s\r\n", client->proxy_address, client->nick, channel->name, recoded); g_free(recoded); if (channel->topic_time > 0) - proxy_outdata(client, ":%s 333 %s %s %s %d\n", + proxy_outdata(client, ":%s 333 %s %s %s %d\r\n", client->proxy_address, client->nick, channel->name, channel->topic_by, channel->topic_time); } @@ -209,10 +209,10 @@ static void dump_join(IRC_CHANNEL_REC *channel, CLIENT_REC *client) void proxy_client_reset_nick(CLIENT_REC *client) { if (client->server == NULL || - strcmp(client->nick, client->server->nick) == 0) + g_strcmp0(client->nick, client->server->nick) == 0) return; - proxy_outdata(client, ":%s!proxy NICK :%s\n", + proxy_outdata(client, ":%s!proxy NICK :%s\r\n", client->nick, client->server->nick); g_free(client->nick); @@ -236,13 +236,13 @@ void proxy_dump_data(CLIENT_REC *client) proxy_client_reset_nick(client); /* welcome info */ - proxy_outdata(client, ":%s 001 %s :Welcome to the Internet Relay Network %s!%s@proxy\n", client->proxy_address, client->nick, client->nick, settings_get_str("user_name")); - proxy_outdata(client, ":%s 002 %s :Your host is irssi-proxy, running version %s\n", client->proxy_address, client->nick, PACKAGE_VERSION); - proxy_outdata(client, ":%s 003 %s :This server was created ...\n", client->proxy_address, client->nick); + proxy_outdata(client, ":%s 001 %s :Welcome to the Internet Relay Network %s!%s@proxy\r\n", client->proxy_address, client->nick, client->nick, settings_get_str("user_name")); + proxy_outdata(client, ":%s 002 %s :Your host is irssi-proxy, running version %s\r\n", client->proxy_address, client->nick, PACKAGE_VERSION); + proxy_outdata(client, ":%s 003 %s :This server was created ...\r\n", client->proxy_address, client->nick); if (client->server == NULL || !client->server->emode_known) - proxy_outdata(client, ":%s 004 %s %s %s oirw abiklmnopqstv\n", client->proxy_address, client->nick, client->proxy_address, PACKAGE_VERSION); + proxy_outdata(client, ":%s 004 %s %s %s oirw abiklmnopqstv\r\n", client->proxy_address, client->nick, client->proxy_address, PACKAGE_VERSION); else - proxy_outdata(client, ":%s 004 %s %s %s oirw abeIiklmnopqstv\n", client->proxy_address, client->nick, client->proxy_address, PACKAGE_VERSION); + proxy_outdata(client, ":%s 004 %s %s %s oirw abeIiklmnopqstv\r\n", client->proxy_address, client->nick, client->proxy_address, PACKAGE_VERSION); if (client->server != NULL && client->server->isupport_sent) { isupport_out = g_string_new(NULL); @@ -267,7 +267,7 @@ void proxy_dump_data(CLIENT_REC *client) count = 0; if (paramstr->len > 0) g_string_truncate(paramstr, paramstr->len-1); - g_string_append_printf(paramstr, " :are supported by this server\n"); + g_string_append_printf(paramstr, " :are supported by this server\r\n"); proxy_outdata(client, "%s", paramstr->str); g_string_truncate(paramstr, 0); g_string_printf(paramstr, ":%s 005 %s ", client->proxy_address, client->nick); @@ -281,9 +281,9 @@ void proxy_dump_data(CLIENT_REC *client) g_strfreev(paramlist); } - proxy_outdata(client, ":%s 251 %s :There are 0 users and 0 invisible on 1 servers\n", client->proxy_address, client->nick); - proxy_outdata(client, ":%s 255 %s :I have 0 clients, 0 services and 0 servers\n", client->proxy_address, client->nick); - proxy_outdata(client, ":%s 422 %s :MOTD File is missing\n", client->proxy_address, client->nick); + proxy_outdata(client, ":%s 251 %s :There are 0 users and 0 invisible on 1 servers\r\n", client->proxy_address, client->nick); + proxy_outdata(client, ":%s 255 %s :I have 0 clients, 0 services and 0 servers\r\n", client->proxy_address, client->nick); + proxy_outdata(client, ":%s 422 %s :MOTD File is missing\r\n", client->proxy_address, client->nick); /* user mode / away status */ if (client->server != NULL) { @@ -293,7 +293,7 @@ void proxy_dump_data(CLIENT_REC *client) client->server->usermode); } if (client->server->usermode_away) { - proxy_outdata(client, ":%s 306 %s :You have been marked as being away\n", + proxy_outdata(client, ":%s 306 %s :You have been marked as being away\r\n", client->proxy_address, client->nick); } diff --git a/src/irc/proxy/listen.c b/src/irc/proxy/listen.c index 33392285..cde4c0be 100644 --- a/src/irc/proxy/listen.c +++ b/src/irc/proxy/listen.c @@ -31,12 +31,76 @@ #include "fe-common/core/printtext.h" /* FIXME: evil. need to do fe-proxy */ +#include <sys/un.h> + GSList *proxy_listens; GSList *proxy_clients; static GString *next_line; static int ignore_next; +static int enabled = FALSE; + +static int is_all_digits(const char *s) +{ + return strspn(s, "0123456789") == strlen(s); +} + +static GIOChannel *net_listen_unix(const char *path) +{ + struct sockaddr_un sa; + int saved_errno, handle; + + g_return_val_if_fail(path != NULL, NULL); + + handle = socket(AF_UNIX, SOCK_STREAM, 0); + if (handle == -1) { + return NULL; + } + + fcntl(handle, F_SETFL, O_NONBLOCK); + + memset(&sa, '\0', sizeof sa); + sa.sun_family = AF_UNIX; + strncpy(sa.sun_path, path, sizeof sa.sun_path - 1); + if (bind(handle, (struct sockaddr *)&sa, sizeof sa) == -1) { + saved_errno = errno; + goto error_close; + } + + if (listen(handle, 1) == -1) { + saved_errno = errno; + goto error_unlink; + } + + return g_io_channel_new(handle); + +error_unlink: + unlink(sa.sun_path); +error_close: + close(handle); + errno = saved_errno; + return NULL; +} + +static GIOChannel *net_accept_unix(GIOChannel *handle) +{ + struct sockaddr_un sa; + int ret; + socklen_t addrlen; + + g_return_val_if_fail(handle != NULL, NULL); + + addrlen = sizeof sa; + ret = accept(g_io_channel_unix_get_fd(handle), (struct sockaddr *)&sa, &addrlen); + + if (ret < 0) + return NULL; + + fcntl(ret, F_SETFL, O_NONBLOCK); + return g_io_channel_new(ret); +} + static void remove_client(CLIENT_REC *rec) { g_return_if_fail(rec != NULL); @@ -45,19 +109,19 @@ static void remove_client(CLIENT_REC *rec) rec->listen->clients = g_slist_remove(rec->listen->clients, rec); signal_emit("proxy client disconnected", 1, rec); - printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE, - "Proxy: Client disconnected from %s", rec->host); + printtext(rec->server, NULL, MSGLEVEL_CLIENTNOTICE, + "Proxy: Client %s disconnected", rec->addr); g_free(rec->proxy_address); net_sendbuffer_destroy(rec->handle, TRUE); g_source_remove(rec->recv_tag); g_free_not_null(rec->nick); - g_free_not_null(rec->host); + g_free_not_null(rec->addr); g_free(rec); } static void proxy_redirect_event(CLIENT_REC *client, const char *command, - int count, const char *arg, int remote) + int count, const char *arg, int remote) { char *str; @@ -65,7 +129,7 @@ static void proxy_redirect_event(CLIENT_REC *client, const char *command, str = g_strdup_printf("proxy %p", client); server_redirect_event(client->server, command, count, - arg, remote, NULL, "", str, NULL); + arg, remote, NULL, "", str, NULL); g_free(str); } @@ -82,7 +146,7 @@ static void grab_who(CLIENT_REC *client, const char *channel) arg = g_string_new(channel); for (tmp = list, count = 0; *tmp != NULL; tmp++, count++) { - if (strcmp(*tmp, "0") == 0) { + if (g_strcmp0(*tmp, "0") == 0) { /* /who 0 displays everyone */ **tmp = '*'; } @@ -92,40 +156,74 @@ static void grab_who(CLIENT_REC *client, const char *channel) } proxy_redirect_event(client, "who", - client->server->one_endofwho ? 1 : count, - arg->str, -1); + client->server->one_endofwho ? 1 : count, + arg->str, -1); g_strfreev(list); g_string_free(arg, TRUE); } static void handle_client_connect_cmd(CLIENT_REC *client, - const char *cmd, const char *args) + const char *cmd, const char *args) { const char *password; password = settings_get_str("irssiproxy_password"); - if (password != NULL && strcmp(cmd, "PASS") == 0) { - if (strcmp(password, args) == 0) - client->pass_sent = TRUE; - else { + if (g_strcmp0(cmd, "PASS") == 0) { + const char *args_pass; + + if (!client->multiplex) { + args_pass = args; + } else { + IRC_SERVER_REC *server; + char *tag; + const char *tag_end; + + if ((tag_end = strchr(args, ':')) != NULL) { + args_pass = tag_end + 1; + } else { + tag_end = args + strlen(args); + args_pass = ""; + } + + tag = g_strndup(args, tag_end - args); + server = IRC_SERVER(server_find_chatnet(tag)); + g_free(tag); + + if (!server) { + /* an invalid network was specified */ + remove_client(client); + return; + } + + client->server = server; + g_free(client->proxy_address); + client->proxy_address = g_strdup_printf("%.*s.proxy", (int)(tag_end - args), args); + } + + if (g_strcmp0(password, args_pass) != 0) { /* wrong password! */ remove_client(client); - return; + return; } - } else if (strcmp(cmd, "NICK") == 0) { + client->pass_sent = TRUE; + } else if (g_strcmp0(cmd, "NICK") == 0) { g_free_not_null(client->nick); client->nick = g_strdup(args); - } else if (strcmp(cmd, "USER") == 0) { + } else if (g_strcmp0(cmd, "USER") == 0) { client->user_sent = TRUE; } if (client->nick != NULL && client->user_sent) { - if (*password != '\0' && !client->pass_sent) { + if ((*password != '\0' || client->multiplex) && !client->pass_sent) { /* client didn't send us PASS, kill it */ remove_client(client); } else { + signal_emit("proxy client connected", 1, client); + printtext(client->server, NULL, MSGLEVEL_CLIENTNOTICE, + "Proxy: Client %s connected", + client->addr); client->connected = TRUE; proxy_dump_data(client); } @@ -133,7 +231,7 @@ static void handle_client_connect_cmd(CLIENT_REC *client, } static void handle_client_cmd(CLIENT_REC *client, char *cmd, char *args, - const char *data) + const char *data) { GSList *tmp; if (!client->connected) { @@ -141,12 +239,12 @@ static void handle_client_cmd(CLIENT_REC *client, char *cmd, char *args, return; } - if (strcmp(cmd, "QUIT") == 0) { + if (g_strcmp0(cmd, "QUIT") == 0) { remove_client(client); return; } - if (strcmp(cmd, "PING") == 0) { + if (g_strcmp0(cmd, "PING") == 0) { /* Reply to PING, if the target parameter is either proxy_adress, our own nick or empty. */ char *params, *origin, *target; @@ -155,38 +253,38 @@ static void handle_client_cmd(CLIENT_REC *client, char *cmd, char *args, if (*target == '\0' || g_ascii_strcasecmp(target, client->proxy_address) == 0 || g_ascii_strcasecmp(target, client->nick) == 0) { - proxy_outdata(client, ":%s PONG %s :%s\n", - client->proxy_address, - client->proxy_address, origin); + proxy_outdata(client, ":%s PONG %s :%s\r\n", + client->proxy_address, + client->proxy_address, origin); g_free(params); return; } g_free(params); } - if (strcmp(cmd, "PROXY") == 0) { + if (g_strcmp0(cmd, "PROXY") == 0) { if (g_ascii_strcasecmp(args, "CTCP ON") == 0) { - /* client wants all ctcps */ + /* client wants all ctcps */ client->want_ctcp = 1; - for (tmp = proxy_clients; tmp != NULL; tmp = tmp->next) { + for (tmp = proxy_clients; tmp != NULL; tmp = tmp->next) { CLIENT_REC *rec = tmp->data; - if ((g_ascii_strcasecmp(client->listen->ircnet,rec->listen->ircnet) == 0) && - /* kludgy way to check if the clients aren't the same */ - (client->recv_tag != rec->recv_tag)) { - if (rec->want_ctcp == 1) - proxy_outdata(rec, ":%s NOTICE %s :Another client is now receiving CTCPs sent to %s\n", - rec->proxy_address, rec->nick, rec->listen->ircnet); - rec->want_ctcp = 0; - } + if (g_ascii_strcasecmp(client->listen->ircnet, rec->listen->ircnet) == 0 && + /* kludgy way to check if the clients aren't the same */ + client->recv_tag != rec->recv_tag) { + if (rec->want_ctcp == 1) + proxy_outdata(rec, ":%s NOTICE %s :Another client is now receiving CTCPs sent to %s\r\n", + rec->proxy_address, rec->nick, rec->listen->ircnet); + rec->want_ctcp = 0; + } } - proxy_outdata(client, ":%s NOTICE %s :You're now receiving CTCPs sent to %s\n", - client->proxy_address, client->nick,client->listen->ircnet); + proxy_outdata(client, ":%s NOTICE %s :You're now receiving CTCPs sent to %s\r\n", + client->proxy_address, client->nick, client->listen->ircnet); } else if (g_ascii_strcasecmp(args, "CTCP OFF") == 0) { - /* client wants proxy to handle all ctcps */ + /* client wants proxy to handle all ctcps */ client->want_ctcp = 0; - proxy_outdata(client, ":%s NOTICE %s :Proxy is now handling itself CTCPs sent to %s\n", - client->proxy_address, client->nick, client->listen->ircnet); + proxy_outdata(client, ":%s NOTICE %s :Proxy is now handling itself CTCPs sent to %s\r\n", + client->proxy_address, client->nick, client->listen->ircnet); } else { signal_emit("proxy client command", 3, client, args, data); } @@ -194,17 +292,17 @@ static void handle_client_cmd(CLIENT_REC *client, char *cmd, char *args, } if (client->server == NULL || !client->server->connected) { - proxy_outdata(client, ":%s NOTICE %s :Not connected to server\n", - client->proxy_address, client->nick); - return; + proxy_outdata(client, ":%s NOTICE %s :Not connected to server\r\n", + client->proxy_address, client->nick); + return; } - /* check if the command could be redirected */ - if (strcmp(cmd, "WHO") == 0) + /* check if the command could be redirected */ + if (g_strcmp0(cmd, "WHO") == 0) grab_who(client, args); - else if (strcmp(cmd, "WHOWAS") == 0) + else if (g_strcmp0(cmd, "WHOWAS") == 0) proxy_redirect_event(client, "whowas", 1, args, -1); - else if (strcmp(cmd, "WHOIS") == 0) { + else if (g_strcmp0(cmd, "WHOIS") == 0) { char *p; /* convert dots to spaces */ @@ -212,11 +310,11 @@ static void handle_client_cmd(CLIENT_REC *client, char *cmd, char *args, if (*p == ',') *p = ' '; proxy_redirect_event(client, "whois", 1, args, TRUE); - } else if (strcmp(cmd, "ISON") == 0) + } else if (g_strcmp0(cmd, "ISON") == 0) proxy_redirect_event(client, "ison", 1, args, -1); - else if (strcmp(cmd, "USERHOST") == 0) + else if (g_strcmp0(cmd, "USERHOST") == 0) proxy_redirect_event(client, "userhost", 1, args, -1); - else if (strcmp(cmd, "MODE") == 0) { + else if (g_strcmp0(cmd, "MODE") == 0) { /* convert dots to spaces */ char *slist, *str, mode, *p; int argc; @@ -252,40 +350,40 @@ static void handle_client_cmd(CLIENT_REC *client, char *cmd, char *args, } g_free(str); g_free(slist); - } else if (strcmp(cmd, "PRIVMSG") == 0) { + } else if (g_strcmp0(cmd, "PRIVMSG") == 0) { /* send the message to other clients as well */ char *params, *target, *msg; params = event_get_params(args, 2 | PARAM_FLAG_GETREST, - &target, &msg); + &target, &msg); proxy_outserver_all_except(client, "PRIVMSG %s", args); ignore_next = TRUE; if (*msg != '\001' || msg[strlen(msg)-1] != '\001') { - signal_emit(ischannel(*target) ? - "message own_public" : "message own_private", 4, - client->server, msg, target, target); + signal_emit(server_ischannel(SERVER(client->server), target) ? + "message own_public" : "message own_private", 4, + client->server, msg, target, target); } else if (strncmp(msg+1, "ACTION ", 7) == 0) { /* action */ - msg[strlen(msg)-1] = '\0'; + msg[strlen(msg)-1] = '\0'; signal_emit("message irc own_action", 3, - client->server, msg+8, target); + client->server, msg+8, target); } else { - /* CTCP */ + /* CTCP */ char *p; msg[strlen(msg)-1] = '\0'; p = strchr(msg, ' '); - if (p != NULL) *p++ = '\0'; else p = ""; + if (p != NULL) *p++ = '\0'; else p = ""; signal_emit("message irc own_ctcp", 4, - client->server, msg+1, p, target); + client->server, msg+1, p, target); } ignore_next = FALSE; g_free(params); - } else if (strcmp(cmd, "PING") == 0) { + } else if (g_strcmp0(cmd, "PING") == 0) { proxy_redirect_event(client, "ping", 1, NULL, TRUE); - } else if (strcmp(cmd, "AWAY") == 0) { + } else if (g_strcmp0(cmd, "AWAY") == 0) { /* set the away reason */ if (args != NULL) { g_free(client->server->away_reason); @@ -331,23 +429,38 @@ static void sig_listen(LISTEN_REC *listen) CLIENT_REC *rec; IPADDR ip; NET_SENDBUF_REC *sendbuf; - GIOChannel *handle; + GIOChannel *handle; char host[MAX_IP_LEN]; int port; + char *addr; g_return_if_fail(listen != NULL); /* accept connection */ - handle = net_accept(listen->handle, &ip, &port); - if (handle == NULL) - return; - net_ip2host(&ip, host); + if (listen->port) { + handle = net_accept(listen->handle, &ip, &port); + if (handle == NULL) + return; + net_ip2host(&ip, host); + addr = g_strdup_printf("%s:%d", host, port); + } else { + /* no port => this is a unix socket */ + handle = net_accept_unix(listen->handle); + if (handle == NULL) + return; + addr = g_strdup("(local)"); + } + sendbuf = net_sendbuffer_create(handle, 0); rec = g_new0(CLIENT_REC, 1); rec->listen = listen; rec->handle = sendbuf; - rec->host = g_strdup(host); - if (strcmp(listen->ircnet, "*") == 0) { + rec->addr = addr; + if (g_strcmp0(listen->ircnet, "?") == 0) { + rec->multiplex = TRUE; + rec->proxy_address = g_strdup("multiplex.proxy"); + rec->server = NULL; + } else if (g_strcmp0(listen->ircnet, "*") == 0) { rec->proxy_address = g_strdup("irc.proxy"); rec->server = servers == NULL ? NULL : IRC_SERVER(servers->data); } else { @@ -356,14 +469,15 @@ static void sig_listen(LISTEN_REC *listen) IRC_SERVER(server_find_chatnet(listen->ircnet)); } rec->recv_tag = g_input_add(handle, G_INPUT_READ, - (GInputFunction) sig_listen_client, rec); + (GInputFunction) sig_listen_client, rec); proxy_clients = g_slist_prepend(proxy_clients, rec); - rec->listen->clients = g_slist_prepend(rec->listen->clients, rec); + listen->clients = g_slist_prepend(listen->clients, rec); - signal_emit("proxy client connected", 1, rec); - printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE, - "Proxy: Client connected from %s", rec->host); + signal_emit("proxy client connecting", 1, rec); + printtext(rec->server, NULL, MSGLEVEL_CLIENTNOTICE, + "Proxy: New client %s on port %s (%s)", + rec->addr, listen->port_or_path, listen->ircnet); } static void sig_incoming(IRC_SERVER_REC *server, const char *line) @@ -371,7 +485,7 @@ static void sig_incoming(IRC_SERVER_REC *server, const char *line) g_return_if_fail(line != NULL); /* send server event to all clients */ - g_string_printf(next_line, "%s\n", line); + g_string_printf(next_line, "%s\r\n", line); } static void sig_server_event(IRC_SERVER_REC *server, const char *line, @@ -415,7 +529,7 @@ static void sig_server_event(IRC_SERVER_REC *server, const char *line, } } - if (strcmp(event, "event privmsg") == 0 && + if (g_strcmp0(event, "event privmsg") == 0 && strstr(args, " :\001") != NULL && strstr(args, " :\001ACTION") == NULL) { /* CTCP - either answer ourself or forward it to one client */ @@ -435,8 +549,8 @@ static void sig_server_event(IRC_SERVER_REC *server, const char *line, return; } - if (strcmp(event, "event ping") == 0 || - strcmp(event, "event pong") == 0) { + if (g_strcmp0(event, "event ping") == 0 || + g_strcmp0(event, "event pong") == 0) { /* We want to answer ourself to PINGs and CTCPs. Also hide PONGs from clients. */ g_free(event); @@ -452,21 +566,21 @@ static void sig_server_event(IRC_SERVER_REC *server, const char *line, static void event_connected(IRC_SERVER_REC *server) { GSList *tmp; - const char *chatnet; + const char *chatnet; if (!IS_IRC_SERVER(server)) return; - chatnet = server->connrec->chatnet; + chatnet = server->connrec->chatnet; for (tmp = proxy_clients; tmp != NULL; tmp = tmp->next) { CLIENT_REC *rec = tmp->data; if (rec->connected && rec->server == NULL && - (strcmp(rec->listen->ircnet, "*") == 0 || + (g_strcmp0(rec->listen->ircnet, "*") == 0 || (chatnet != NULL && g_ascii_strcasecmp(chatnet, rec->listen->ircnet) == 0))) { - proxy_outdata(rec, ":%s NOTICE %s :Connected to server\n", - rec->proxy_address, rec->nick); + proxy_outdata(rec, ":%s NOTICE %s :Connected to server\r\n", + rec->proxy_address, rec->nick); rec->server = server; proxy_client_reset_nick(rec); } @@ -474,11 +588,11 @@ static void event_connected(IRC_SERVER_REC *server) } static void proxy_server_disconnected(CLIENT_REC *client, - IRC_SERVER_REC *server) + IRC_SERVER_REC *server) { GSList *tmp; - proxy_outdata(client, ":%s NOTICE %s :Connection lost to server %s\n", + proxy_outdata(client, ":%s NOTICE %s :Connection lost to server %s\r\n", client->proxy_address, client->nick, server->connrec->address); @@ -559,14 +673,19 @@ static void sig_message_own_action(IRC_SERVER_REC *server, const char *msg, proxy_outserver_all(server, "PRIVMSG %s :\001ACTION %s\001", target, msg); } -static LISTEN_REC *find_listen(const char *ircnet, int port) +static LISTEN_REC *find_listen(const char *ircnet, int port, const char *port_or_path) { GSList *tmp; for (tmp = proxy_listens; tmp != NULL; tmp = tmp->next) { LISTEN_REC *rec = tmp->data; - if (rec->port == port && + if ((port + ? /* a tcp port */ + rec->port == port + : /* a unix socket path */ + g_strcmp0(rec->port_or_path, port_or_path) == 0 + ) && g_ascii_strcasecmp(rec->ircnet, ircnet) == 0) return rec; } @@ -574,48 +693,53 @@ static LISTEN_REC *find_listen(const char *ircnet, int port) return NULL; } -static void add_listen(const char *ircnet, int port) +static void add_listen(const char *ircnet, int port, const char *port_or_path) { LISTEN_REC *rec; IPADDR ip4, ip6, *my_ip; + GIOChannel *handle; - if (port <= 0 || *ircnet == '\0') + if (*port_or_path == '\0' || port < 0 || *ircnet == '\0') return; - /* bind to specific host/ip? */ - my_ip = NULL; - if (*settings_get_str("irssiproxy_bind") != '\0') { - if (net_gethostbyname(settings_get_str("irssiproxy_bind"), - &ip4, &ip6) != 0) { - printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, - "Proxy: can not resolve '%s' - aborting", - settings_get_str("irssiproxy_bind")); - return; + if (port == 0) { + /* listening on a unix socket */ + handle = net_listen_unix(port_or_path); + } else { + /* bind to specific host/ip? */ + my_ip = NULL; + if (*settings_get_str("irssiproxy_bind") != '\0') { + if (net_gethostbyname(settings_get_str("irssiproxy_bind"), + &ip4, &ip6) != 0) { + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, + "Proxy: can not resolve '%s' - aborting", + settings_get_str("irssiproxy_bind")); + return; + } + + my_ip = ip6.family == 0 ? &ip4 : ip4.family == 0 || + settings_get_bool("resolve_prefer_ipv6") ? &ip6 : &ip4; } + handle = net_listen(my_ip, &port); + } - my_ip = ip6.family == 0 ? &ip4 : ip4.family == 0 || - settings_get_bool("resolve_prefer_ipv6") ? &ip6 : &ip4; + if (handle == NULL) { + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, + "Proxy: Listen in port %s failed: %s", + port_or_path, g_strerror(errno)); + return; } rec = g_new0(LISTEN_REC, 1); + rec->handle = handle; rec->ircnet = g_strdup(ircnet); rec->port = port; - - rec->handle = net_listen(my_ip, &rec->port); - - if (rec->handle == NULL) { - printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, - "Proxy: Listen in port %d failed: %s", - rec->port, g_strerror(errno)); - g_free(rec->ircnet); - g_free(rec); - return; - } + rec->port_or_path = g_strdup(port_or_path); rec->tag = g_input_add(rec->handle, G_INPUT_READ, - (GInputFunction) sig_listen, rec); + (GInputFunction) sig_listen, rec); - proxy_listens = g_slist_append(proxy_listens, rec); + proxy_listens = g_slist_append(proxy_listens, rec); } static void remove_listen(LISTEN_REC *rec) @@ -625,8 +749,13 @@ static void remove_listen(LISTEN_REC *rec) while (rec->clients != NULL) remove_client(rec->clients->data); + /* remove unix socket because bind wants to (re)create it */ + if (rec->port == 0) + unlink(rec->port_or_path); + net_disconnect(rec->handle); g_source_remove(rec->tag); + g_free(rec->port_or_path); g_free(rec->ircnet); g_free(rec); } @@ -634,8 +763,9 @@ static void remove_listen(LISTEN_REC *rec) static void read_settings(void) { LISTEN_REC *rec; - GSList *remove_listens; - char **ports, **tmp, *ircnet, *port; + GSList *remove_listens = NULL; + GSList *add_listens = NULL; + char **ports, **tmp, *ircnet, *port_or_path; int portnum; remove_listens = g_slist_copy(proxy_listens); @@ -643,27 +773,45 @@ static void read_settings(void) ports = g_strsplit(settings_get_str("irssiproxy_ports"), " ", -1); for (tmp = ports; *tmp != NULL; tmp++) { ircnet = *tmp; - port = strchr(ircnet, '='); - if (port == NULL) + port_or_path = strchr(ircnet, '='); + if (port_or_path == NULL) continue; - *port++ = '\0'; - portnum = atoi(port); - if (portnum <= 0) - continue; + *port_or_path++ = '\0'; + if (is_all_digits(port_or_path)) { + portnum = atoi(port_or_path); + if (portnum <= 0) + continue; + } else { + portnum = 0; + } - rec = find_listen(ircnet, portnum); - if (rec == NULL) - add_listen(ircnet, portnum); - else + rec = find_listen(ircnet, portnum, port_or_path); + if (rec == NULL) { + rec = g_new0(LISTEN_REC, 1); + rec->ircnet = ircnet; /* borrow */ + rec->port = portnum; + rec->port_or_path = port_or_path; /* borrow */ + add_listens = g_slist_prepend(add_listens, rec); + } else { + /* remove from the list of listens to remove == keep it */ remove_listens = g_slist_remove(remove_listens, rec); + } } - g_strfreev(ports); while (remove_listens != NULL) { - remove_listen(remove_listens->data); + remove_listen(remove_listens->data); remove_listens = g_slist_remove(remove_listens, remove_listens->data); } + + while (add_listens != NULL) { + rec = add_listens->data; + add_listen(rec->ircnet, rec->port, rec->port_or_path); + add_listens = g_slist_remove(add_listens, rec); + g_free(rec); + } + + g_strfreev(ports); } static void sig_dump(CLIENT_REC *client, const char *data) @@ -676,6 +824,11 @@ static void sig_dump(CLIENT_REC *client, const char *data) void proxy_listen_init(void) { + if (enabled) { + return; + } + enabled = TRUE; + next_line = g_string_new(NULL); proxy_clients = NULL; @@ -697,6 +850,11 @@ void proxy_listen_init(void) void proxy_listen_deinit(void) { + if (!enabled) { + return; + } + enabled = FALSE; + while (proxy_listens != NULL) remove_listen(proxy_listens->data); g_string_free(next_line, TRUE); diff --git a/src/irc/proxy/proxy.c b/src/irc/proxy/proxy.c index c8f47bdf..2875d2c2 100644 --- a/src/irc/proxy/proxy.c +++ b/src/irc/proxy/proxy.c @@ -23,11 +23,61 @@ #include "settings.h" #include "levels.h" +#include "fe-common/core/printtext.h" + +/* SYNTAX: IRSSIPROXY STATUS */ +static void cmd_irssiproxy_status(const char *data, IRC_SERVER_REC *server) +{ + GSList *tmp; + + if (!settings_get_bool("irssiproxy")) { + printtext(server, NULL, MSGLEVEL_CLIENTNOTICE, + "Proxy is currently disabled"); + return; + } + + + printtext(server, NULL, MSGLEVEL_CLIENTNOTICE, + "Proxy: Currently connected clients: %d", + g_slist_length(proxy_clients)); + + for (tmp = proxy_clients; tmp != NULL; tmp = tmp->next) { + CLIENT_REC *rec = tmp->data; + + printtext(server, NULL, MSGLEVEL_CLIENTNOTICE, + " %s connect%s to %s (%s)", + rec->addr, + rec->connected ? "ed" : "ing", + rec->listen->port_or_path, rec->listen->ircnet); + } +} + +/* SYNTAX: IRSSIPROXY */ +static void cmd_irssiproxy(const char *data, IRC_SERVER_REC *server, void *item) +{ + if (*data == '\0') { + cmd_irssiproxy_status(data, server); + return; + } + + command_runsub("irssiproxy", data, server, item); +} + +static void irc_proxy_setup_changed(void) +{ + if (settings_get_bool("irssiproxy")) { + proxy_listen_init(); + } else { + proxy_listen_deinit(); + } +} + void irc_proxy_init(void) { settings_add_str("irssiproxy", "irssiproxy_ports", ""); settings_add_str("irssiproxy", "irssiproxy_password", ""); settings_add_str("irssiproxy", "irssiproxy_bind", ""); + settings_add_bool("irssiproxy", "irssiproxy", TRUE); if (*settings_get_str("irssiproxy_password") == '\0') { /* no password - bad idea! */ @@ -43,7 +93,14 @@ void irc_proxy_init(void) "... to set them."); } - proxy_listen_init(); + command_bind("irssiproxy", NULL, (SIGNAL_FUNC) cmd_irssiproxy); + command_bind("irssiproxy status", NULL, (SIGNAL_FUNC) cmd_irssiproxy_status); + + signal_add_first("setup changed", (SIGNAL_FUNC) irc_proxy_setup_changed); + + if (settings_get_bool("irssiproxy")) { + proxy_listen_init(); + } settings_check(); module_register("proxy", "irc"); } @@ -52,3 +109,8 @@ void irc_proxy_deinit(void) { proxy_listen_deinit(); } + +void irc_proxy_abicheck(int *version) +{ + *version = IRSSI_ABI_VERSION; +} diff --git a/src/irc/proxy/proxy.h b/src/irc/proxy/proxy.h index 4ddc9da9..620ea605 100644 --- a/src/irc/proxy/proxy.h +++ b/src/irc/proxy/proxy.h @@ -9,16 +9,18 @@ typedef struct { int port; + char *port_or_path; char *ircnet; int tag; GIOChannel *handle; GSList *clients; + } LISTEN_REC; typedef struct { - char *nick, *host; + char *nick, *addr; NET_SENDBUF_REC *handle; int recv_tag; char *proxy_address; @@ -28,6 +30,7 @@ typedef struct { unsigned int user_sent:1; unsigned int connected:1; unsigned int want_ctcp:1; + unsigned int multiplex:1; } CLIENT_REC; #endif |