diff --git a/src/core/server-setup-rec.h b/src/core/server-setup-rec.h index ecdde3f3..2c9614c7 100644 --- a/src/core/server-setup-rec.h +++ b/src/core/server-setup-rec.h @@ -8,6 +8,9 @@ char *address; int port; char *password; +int sasl_mechanism; +char *sasl_password; + char *ssl_cert; char *ssl_pkey; char *ssl_pass; diff --git a/src/irc/core/Makefile.am b/src/irc/core/Makefile.am index 7d885d20..20caaeb1 100644 --- a/src/irc/core/Makefile.am +++ b/src/irc/core/Makefile.am @@ -27,6 +27,7 @@ libirc_core_a_SOURCES = \ irc-servers-setup.c \ irc-session.c \ irc-cap.c \ + sasl.c \ lag.c \ massjoin.c \ modes.c \ @@ -50,6 +51,7 @@ pkginc_irc_core_HEADERS = \ 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/irc-chatnets.c b/src/irc/core/irc-chatnets.c index d757bf8d..cfb7deec 100644 --- a/src/irc/core/irc-chatnets.c +++ b/src/irc/core/irc-chatnets.c @@ -48,6 +48,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 = config_node_get_str(node, "sasl_mechanism", NULL); + rec->sasl_username = config_node_get_str(node, "sasl_username", NULL); + rec->sasl_password = config_node_get_str(node, "sasl_password", NULL); } static void sig_chatnet_saved(IRC_CHATNET_REC *rec, CONFIG_NODE *node) @@ -78,7 +82,7 @@ static void sig_chatnet_saved(IRC_CHATNET_REC *rec, CONFIG_NODE *node) static void sig_chatnet_destroyed(IRC_CHATNET_REC *rec) { if (IS_IRC_CHATNET(rec)) - g_free(rec->usermode); + g_free(rec->usermode); } diff --git a/src/irc/core/irc-chatnets.h b/src/irc/core/irc-chatnets.h index 22da90c5..2bb10fa9 100644 --- a/src/irc/core/irc-chatnets.h +++ b/src/irc/core/irc-chatnets.h @@ -17,12 +17,15 @@ struct _IRC_CHATNET_REC { #include "chatnet-rec.h" - char *usermode; + char *usermode; + + 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-core.c b/src/irc/core/irc-core.c index e3ceeeef..a9221e02 100644 --- a/src/irc/core/irc-core.c +++ b/src/irc/core/irc-core.c @@ -27,6 +27,7 @@ #include "irc-channels.h" #include "irc-queries.h" #include "irc-cap.h" +#include "sasl.h" #include "irc-servers-setup.h" #include "channels-setup.h" @@ -119,6 +120,7 @@ void irc_core_init(void) netsplit_init(); irc_expandos_init(); cap_init(); + sasl_init(); settings_check(); module_register("core", "irc"); @@ -128,6 +130,7 @@ void irc_core_deinit(void) { signal_emit("chat protocol deinit", 1, chat_protocol_find("IRC")); + sasl_deinit(); cap_deinit(); irc_expandos_deinit(); netsplit_deinit(); @@ -140,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-servers-setup.c b/src/irc/core/irc-servers-setup.c index 5659991b..9a9a8347 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, @@ -79,6 +80,29 @@ 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() */ + conn->sasl_mechanism = SASL_MECHANISM_NONE; + + 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 undefined or empty"); + } + else if (!g_ascii_strcasecmp(ircnet->sasl_mechanism, "external")) { + conn->sasl_mechanism = SASL_MECHANISM_EXTERNAL; + conn->sasl_username = NULL; + conn->sasl_password = NULL; + } + else + g_warning("Unsupported SASL mechanism \"%s\" selected", ircnet->sasl_mechanism); + } } static void init_userinfo(void) diff --git a/src/irc/core/irc-servers.c b/src/irc/core/irc-servers.c index e9920d91..ad4d09cc 100644 --- a/src/irc/core/irc-servers.c +++ b/src/irc/core/irc-servers.c @@ -33,6 +33,7 @@ #include "irc-servers-setup.h" #include "irc-servers.h" #include "irc-cap.h" +#include "sasl.h" #include "channel-rejoin.h" #include "servers-idle.h" #include "servers-reconnect.h" @@ -215,6 +216,9 @@ static void server_init(IRC_SERVER_REC *server) g_free(cmd); } + if (conn->sasl_mechanism != SASL_MECHANISM_NONE) + cap_toggle(server, "sasl", TRUE); + irc_send_cmd_now(server, "CAP LS"); if (conn->password != NULL && *conn->password != '\0') { diff --git a/src/irc/core/irc-servers.h b/src/irc/core/irc-servers.h index f809fab5..41c4b9c2 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; diff --git a/src/irc/core/sasl.c b/src/irc/core/sasl.c new file mode 100644 index 00000000..8bffc3d9 --- /dev/null +++ b/src/irc/core/sasl.c @@ -0,0 +1,134 @@ +#include "module.h" +#include "misc.h" +#include "settings.h" + +#include "irc-cap.h" +#include "irc-servers.h" +#include "sasl.h" + +#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 */ + g_critical("The authentication timed out, try increasing the timeout and check your connection " + "to the network."); + irc_send_cmd_now(server, "AUTHENTICATE *"); + cap_finish_negotiation(server); + + return G_SOURCE_REMOVE; +} + +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) +{ + /* Stop any pending timeout, if any */ + g_source_remove(server->sasl_timeout); + + g_critical("Authentication failed, make sure your credentials are correct and that the mechanism " + "you have selected is supported by this server."); + + /* Terminate the negotiation */ + cap_finish_negotiation(server); +} + +static void sasl_already (IRC_SERVER_REC *server, const char *data, const char *from) +{ + g_source_remove(server->sasl_timeout); + + /* We're already authenticated, do nothing */ + cap_finish_negotiation(server); +} + +static void sasl_success (IRC_SERVER_REC *server, const char *data, const char *from) +{ + g_source_remove(server->sasl_timeout); + + /* The authentication succeeded, time to finish the CAP negotiation */ + g_warning("SASL authentication succeeded"); + cap_finish_negotiation(server); +} + +static void sasl_step (IRC_SERVER_REC *server, const char *data, const char *from) +{ + IRC_SERVER_CONNECT_REC *conn; + GString *req; + char *enc_req; + + conn = server->connrec; + + /* Stop the timer */ + g_source_remove(server->sasl_timeout); + + switch (conn->sasl_mechanism) { + case SASL_MECHANISM_PLAIN: + /* At this point we assume that conn->{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 optional and can be omitted, the server will derive the + * identity by looking at the credentials provided. + * The whole request is then encoded in base64. */ + + req = g_string_new(NULL); + + g_string_append_c(req, '\0'); + g_string_append(req, conn->sasl_username); + g_string_append_c(req, '\0'); + g_string_append(req, conn->sasl_password); + + enc_req = g_base64_encode((const guchar *)req->str, req->len); + + irc_send_cmdv(server, "AUTHENTICATE %s", enc_req); + + g_free(enc_req); + g_string_free(req, TRUE); + break; + + case SASL_MECHANISM_EXTERNAL: + /* Empty response */ + irc_send_cmdv(server, "+"); + break; + } + + /* We expect a response within a reasonable time */ + server->sasl_timeout = g_timeout_add(SASL_TIMEOUT, (GSourceFunc) sasl_timeout, server); +} + +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 900", (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 907", (SIGNAL_FUNC) sasl_already); +} + +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 900", (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 907", (SIGNAL_FUNC) sasl_already); +} diff --git a/src/irc/core/sasl.h b/src/irc/core/sasl.h new file mode 100644 index 00000000..fcf87e16 --- /dev/null +++ b/src/irc/core/sasl.h @@ -0,0 +1,14 @@ +#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