diff options
author | LemonBoy <thatlemon@gmail.com> | 2015-02-12 00:07:22 +0100 |
---|---|---|
committer | LemonBoy <thatlemon@gmail.com> | 2015-05-05 23:14:26 +0200 |
commit | 2d7030a84445ee4547af43970cfb252b36246e62 (patch) | |
tree | 3ab0cfc3cde4243decd42145e0a9122aa931c09c /src/irc | |
parent | c122a2a226363114e11f32072395e2f06ad6a450 (diff) | |
download | irssi-2d7030a84445ee4547af43970cfb252b36246e62.zip |
Implement support for IRCv3.1 CAP negotiation
Diffstat (limited to 'src/irc')
-rw-r--r-- | src/irc/core/Makefile.am | 2 | ||||
-rw-r--r-- | src/irc/core/irc-cap.c | 172 | ||||
-rw-r--r-- | src/irc/core/irc-cap.h | 9 | ||||
-rw-r--r-- | src/irc/core/irc-core.c | 3 | ||||
-rw-r--r-- | src/irc/core/irc-servers.c | 14 | ||||
-rw-r--r-- | src/irc/core/irc-servers.h | 7 |
6 files changed, 206 insertions, 1 deletions
diff --git a/src/irc/core/Makefile.am b/src/irc/core/Makefile.am index 3db5cf0e..7d885d20 100644 --- a/src/irc/core/Makefile.am +++ b/src/irc/core/Makefile.am @@ -26,6 +26,7 @@ libirc_core_a_SOURCES = \ irc-servers-reconnect.c \ irc-servers-setup.c \ irc-session.c \ + irc-cap.c \ lag.c \ massjoin.c \ modes.c \ @@ -48,6 +49,7 @@ pkginc_irc_core_HEADERS = \ irc-queries.h \ irc-servers.h \ irc-servers-setup.h \ + irc-cap.h \ modes.h \ mode-lists.h \ module.h \ diff --git a/src/irc/core/irc-cap.c b/src/irc/core/irc-cap.c new file mode 100644 index 00000000..c5bf4e67 --- /dev/null +++ b/src/irc/core/irc-cap.c @@ -0,0 +1,172 @@ +#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)) { + 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-core.c b/src/irc/core/irc-core.c index bf7386ad..e3ceeeef 100644 --- a/src/irc/core/irc-core.c +++ b/src/irc/core/irc-core.c @@ -26,6 +26,7 @@ #include "irc-chatnets.h" #include "irc-channels.h" #include "irc-queries.h" +#include "irc-cap.h" #include "irc-servers-setup.h" #include "channels-setup.h" @@ -117,6 +118,7 @@ void irc_core_init(void) lag_init(); netsplit_init(); irc_expandos_init(); + cap_init(); settings_check(); module_register("core", "irc"); @@ -126,6 +128,7 @@ void irc_core_deinit(void) { signal_emit("chat protocol deinit", 1, chat_protocol_find("IRC")); + cap_deinit(); irc_expandos_deinit(); netsplit_deinit(); lag_deinit(); diff --git a/src/irc/core/irc-servers.c b/src/irc/core/irc-servers.c index d7122eae..335c88ef 100644 --- a/src/irc/core/irc-servers.c +++ b/src/irc/core/irc-servers.c @@ -32,6 +32,7 @@ #include "irc-queries.h" #include "irc-servers-setup.h" #include "irc-servers.h" +#include "irc-cap.h" #include "channel-rejoin.h" #include "servers-idle.h" #include "servers-reconnect.h" @@ -201,6 +202,8 @@ static void server_init(IRC_SERVER_REC *server) conn = server->connrec; + irc_send_cmd_now(server, "CAP LS"); + if (conn->proxy != NULL && conn->proxy_password != NULL && *conn->proxy_password != '\0') { cmd = g_strdup_printf("PASS %s", conn->proxy_password); @@ -409,7 +412,16 @@ 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; /* these are dynamically allocated only if isupport was sent */ g_hash_table_foreach(server->isupport, diff --git a/src/irc/core/irc-servers.h b/src/irc/core/irc-servers.h index 7e4eeabf..f809fab5 100644 --- a/src/irc/core/irc-servers.h +++ b/src/irc/core/irc-servers.h @@ -69,6 +69,13 @@ struct _IRC_SERVER_REC { 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 */ + int cap_complete:1; /* We've done the initial CAP negotiation */ + + 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 |