This commit is contained in:
Kenny Root 2016-09-22 07:55:07 +00:00 committed by GitHub
commit fe04602e02
14 changed files with 286 additions and 96 deletions

View file

@ -34,8 +34,8 @@
-cmdmax: Specifies the maximum number of commands to perform before
starting the internal flood protection.
-sasl_mechanism Specifies the mechanism to use for the SASL authentication.
At the moment irssi only supports the 'plain' and the
'external' mechanisms.
Irssi supports the 'plain' and 'external' mechanisms by
default and other mechanisms through scripts.
-sasl_username Specifies the username to use during the SASL authentication.
-sasl_password Specifies the password to use during the SASL authentication.

View file

@ -137,6 +137,9 @@ irc-cap.c
"server cap end", SERVER_REC
sasl.c
"server sasl init "<mechanism>, SERVER_REC
"server sasl step "<mechanism>, SERVER_REC, GBytes *
"server sasl abort "<mechanism>, SERVER_REC
"server sasl failure", SERVER_REC, char *reason
"server sasl success", SERVER_REC

View file

@ -8,7 +8,7 @@ char *address;
int port;
char *password;
int sasl_mechanism;
char *sasl_mechanism;
char *sasl_password;
char *ssl_cert;

View file

@ -88,27 +88,24 @@ static void sig_server_setup_fill_chatnet(IRC_SERVER_CONNECT_REC *conn,
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;
g_free_and_null(conn->sasl_mechanism);
if (ircnet->sasl_mechanism != NULL) {
if (!g_ascii_strcasecmp(ircnet->sasl_mechanism, "plain")) {
conn->sasl_mechanism = g_strdup(ircnet->sasl_mechanism);
if (ircnet->sasl_username != NULL && *ircnet->sasl_username) {
g_free_and_null(conn->sasl_username);
conn->sasl_username = g_strdup(ircnet->sasl_username);
}
if (ircnet->sasl_password != NULL && *ircnet->sasl_password) {
g_free_and_null(conn->sasl_password);
conn->sasl_password = g_strdup(ircnet->sasl_password);
}
/* 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
if (!g_ascii_strcasecmp(ircnet->sasl_mechanism, "plain") &&
(conn->sasl_username == NULL || conn->sasl_password == NULL))
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;
conn->sasl_username = NULL;
conn->sasl_password = NULL;
}
else
g_warning("Unsupported SASL mechanism \"%s\" selected", ircnet->sasl_mechanism);
}
}
static void init_userinfo(void)

View file

@ -232,7 +232,7 @@ static void server_init(IRC_SERVER_REC *server)
g_free(cmd);
}
if (conn->sasl_mechanism != SASL_MECHANISM_NONE)
if (conn->sasl_mechanism != NULL)
cap_toggle(server, "sasl", TRUE);
cap_toggle(server, "multi-prefix", TRUE);

View file

@ -27,7 +27,7 @@ struct _IRC_SERVER_CONNECT_REC {
char *usermode;
char *alternate_nick;
int sasl_mechanism;
char *sasl_mechanism;
char *sasl_username;
char *sasl_password;

View file

@ -67,7 +67,7 @@ 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_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);
@ -96,10 +96,11 @@ 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_mechanism);
server->connrec->sasl_mechanism = g_strdup(config_node_get_str(node, "sasl_mechanism", NULL));
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);

View file

@ -28,50 +28,131 @@
#define SASL_TIMEOUT (20 * 1000) // ms
static gboolean sasl_timeout(IRC_SERVER_REC *server)
static GSList *sasl_mechanisms;
static GHashTable *sasl_buffers;
void sasl_mechanism_register(const char *name)
{
if (gslist_find_string(sasl_mechanisms, name) == NULL) {
sasl_mechanisms = g_slist_append(sasl_mechanisms, g_strdup(name));
}
}
void sasl_mechanism_unregister(const char *name)
{
GSList *pos;
pos = gslist_find_string(sasl_mechanisms, name);
if (pos != NULL) {
g_free(pos->data);
sasl_mechanisms = g_slist_remove(sasl_mechanisms, pos->data);
}
}
static gboolean is_registered_mechanism(const char *name)
{
return gslist_find_string(sasl_mechanisms, name) != NULL;
}
static void sasl_emit_abort_signal(IRC_SERVER_REC *server)
{
IRC_SERVER_CONNECT_REC *conn;
conn = server->connrec;
if (is_registered_mechanism(conn->sasl_mechanism)) {
char *str;
str = g_strconcat("server sasl abort ", conn->sasl_mechanism, NULL);
ascii_strdown(str+18);
if (!signal_emit(str, 1, server)) {
g_warning("No one handled the abort of SASL mechanism %s",
conn->sasl_mechanism);
}
g_free(str);
}
}
/*
* This is the callback from g_timeout_add.
*/
static gboolean sasl_timeout_callback(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;
sasl_abort(server);
signal_emit("server sasl failure", 2, server, "The authentication timed out");
return FALSE;
}
static void sasl_timeout_cancel(IRC_SERVER_REC *server)
{
if (server->sasl_timeout != 0) {
g_source_remove(server->sasl_timeout);
server->sasl_timeout = 0;
}
}
static void sasl_timeout_start(IRC_SERVER_REC *server)
{
sasl_timeout_cancel(server);
server->sasl_timeout = g_timeout_add_seconds(SASL_TIMEOUT, (GSourceFunc) sasl_timeout_callback,
server);
}
void sasl_abort(IRC_SERVER_REC *server)
{
sasl_timeout_cancel(server);
irc_send_cmd_now(server, "AUTHENTICATE *");
cap_finish_negotiation(server);
sasl_emit_abort_signal(server);
}
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:
if (!g_strcmp0(conn->sasl_mechanism, "PLAIN")) {
irc_send_cmd_now(server, "AUTHENTICATE PLAIN");
break;
case SASL_MECHANISM_EXTERNAL:
sasl_timeout_start(server);
} else if (!g_strcmp0(conn->sasl_mechanism, "external")) {
irc_send_cmd_now(server, "AUTHENTICATE EXTERNAL");
break;
sasl_timeout_start(server);
} else if (is_registered_mechanism(conn->sasl_mechanism)) {
char *str;
str = g_strconcat("server sasl init ", conn->sasl_mechanism, NULL);
ascii_strdown(str+17);
if (!signal_emit(str, 1, server)) {
g_warning("Nothing handled initialization of SASL mechanism %s",
conn->sasl_mechanism);
}
g_free(str);
str = g_strconcat("AUTHENTICATE ", conn->sasl_mechanism, NULL);
irc_send_cmd_now(server, str);
g_free(str);
sasl_timeout_start(server);
} else {
g_warning("Unsupported SASL mechanism \"%s\" selected", conn->sasl_mechanism);
sasl_abort(server);
}
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;
}
sasl_timeout_cancel(server);
params = event_get_params(data, 2, NULL, &error);
signal_emit("server sasl failure", 2, server, error);
sasl_emit_abort_signal(server);
/* Terminate the negotiation */
cap_finish_negotiation(server);
@ -81,12 +162,10 @@ static void sasl_fail(IRC_SERVER_REC *server, const char *data, const char *from
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;
}
sasl_timeout_cancel(server);
signal_emit("server sasl success", 1, server);
sasl_emit_abort_signal(server);
/* We're already authenticated, do nothing */
cap_finish_negotiation(server);
@ -94,10 +173,7 @@ static void sasl_already(IRC_SERVER_REC *server, const char *data, const char *f
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;
}
sasl_timeout_cancel(server);
signal_emit("server sasl success", 1, server);
@ -105,27 +181,91 @@ static void sasl_success(IRC_SERVER_REC *server, const char *data, const char *f
cap_finish_negotiation(server);
}
static void sasl_send_step_if_complete(IRC_SERVER_REC *server, const char *enc_req)
{
char *buffer;
size_t enc_req_len;
enc_req_len = strlen(enc_req);
buffer = g_hash_table_lookup(sasl_buffers, server->tag);
if (buffer != NULL) {
if (!g_strcmp0("+", enc_req)) {
enc_req = buffer;
} else {
enc_req = g_strconcat(buffer, enc_req, NULL);
}
g_hash_table_remove(sasl_buffers, server->tag);
}
if (enc_req_len == 400) {
g_hash_table_insert(sasl_buffers, server->tag, g_strdup(enc_req));
} else {
gchar *output;
GBytes *decoded;
IRC_SERVER_CONNECT_REC *conn;
if (!g_strcmp0("+", enc_req)) {
decoded = g_bytes_new("", 0);
} else {
gsize dec_len;
guchar *tmp;
tmp = g_base64_decode(enc_req, &dec_len);
decoded = g_bytes_new(tmp, dec_len);
}
conn = server->connrec;
output = g_strconcat("server sasl step ", conn->sasl_mechanism, NULL);
ascii_strdown(output+17);
if (!signal_emit(output, 2, server, decoded)) {
g_warning("No one can handle SASL mechanism: %s",
conn->sasl_mechanism);
}
g_free(output);
g_bytes_unref(decoded);
}
g_free_not_null(buffer);
}
void sasl_send_response(IRC_SERVER_REC *server, GBytes *response)
{
char *enc;
const guchar *p;
gsize len;
size_t offset, enc_len;
p = g_bytes_get_data(response, &len);
enc = g_base64_encode(p, len);
enc_len = strlen(enc);
offset = 0;
while (offset < enc_len) {
irc_send_cmdv(server, "AUTHENTICATE %400s",
offset == enc_len ? "+" : (enc + offset));
offset += 400;
}
g_free(enc);
}
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 */
if (server->sasl_timeout != 0) {
g_source_remove(server->sasl_timeout);
server->sasl_timeout = 0;
}
sasl_timeout_cancel(server);
switch (conn->sasl_mechanism) {
case SASL_MECHANISM_PLAIN:
if (!g_strcmp0(conn->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.
* The whole request is then encoded in base64. */
GBytes *bytes;
req = g_string_new(NULL);
@ -135,22 +275,18 @@ static void sasl_step(IRC_SERVER_REC *server, const char *data, const char *from
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);
bytes = g_bytes_new(req->str, req->len);
sasl_send_response(server, bytes);
g_string_free(req, TRUE);
break;
case SASL_MECHANISM_EXTERNAL:
} else if (!g_strcmp0(conn->sasl_mechanism, "external")) {
/* Empty response */
irc_send_cmdv(server, "AUTHENTICATE +");
break;
} else {
sasl_send_step_if_complete(server, data);
}
/* We expect a response within a reasonable time */
server->sasl_timeout = g_timeout_add(SASL_TIMEOUT, (GSourceFunc) sasl_timeout, server);
sasl_timeout_start(server);
}
static void sasl_disconnected(IRC_SERVER_REC *server)
@ -161,14 +297,15 @@ static void sasl_disconnected(IRC_SERVER_REC *server)
return;
}
if (server->sasl_timeout != 0) {
g_source_remove(server->sasl_timeout);
server->sasl_timeout = 0;
}
sasl_timeout_cancel(server);
sasl_emit_abort_signal(server);
}
void sasl_init(void)
{
sasl_buffers = g_hash_table_new((GHashFunc) g_str_hash, (GCompareFunc) g_str_equal);
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);
@ -182,6 +319,8 @@ void sasl_init(void)
void sasl_deinit(void)
{
g_hash_table_destroy(sasl_buffers);
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);

View file

@ -21,12 +21,10 @@
#ifndef __SASL_H
#define __SASL_H
enum {
SASL_MECHANISM_NONE = 0,
SASL_MECHANISM_PLAIN,
SASL_MECHANISM_EXTERNAL,
SASL_MECHANISM_MAX
};
void sasl_mechanism_register(const char *);
void sasl_mechanism_unregister(const char *);
void sasl_send_response(IRC_SERVER_REC *, GBytes *);
void sasl_abort(IRC_SERVER_REC *);
void sasl_init(void);
void sasl_deinit(void);

View file

@ -18,6 +18,7 @@ while (<STDIN>) {
s/GList \* of ([^,]*)/glistptr_\1/g;
s/GSList of (\w+)s/gslist_\1/g;
s/GBytes \*/gbytes/g;
s/char \*[^,]*/string/g;
s/ulong \*[^,]*/ulongptr/g;

View file

@ -7,6 +7,9 @@ static void perl_irc_connect_fill_hash(HV *hv, IRC_SERVER_CONNECT_REC *conn)
{
perl_connect_fill_hash(hv, (SERVER_CONNECT_REC *) conn);
(void) hv_store(hv, "alternate_nick", 14, new_pv(conn->alternate_nick), 0);
(void) hv_store(hv, "sasl_username", 13, new_pv(conn->sasl_username), 0);
(void) hv_store(hv, "sasl_password", 13, new_pv(conn->sasl_password), 0);
}
static void perl_irc_server_fill_hash(HV *hv, IRC_SERVER_REC *server)
@ -227,3 +230,11 @@ BOOT:
irssi_boot(Irc__Query);
irssi_boot(Irc__Server);
irssi_boot(Irc__Client);
void
sasl_mechanism_register(name)
char *name
void
sasl_mechanism_unregister(name)
char *name

View file

@ -149,3 +149,25 @@ CODE:
OUTPUT:
RETVAL
MODULE = Irssi::Irc::Server PACKAGE = Irssi::Irc::Server
PROTOTYPES: ENABLE
void
send_sasl_response(server, response)
Irssi::Irc::Server server
SV *response
PREINIT:
char *p;
STRLEN len;
GBytes *bytes;
CODE:
p = SvPV(response, len);
bytes = g_bytes_new(p, len);
sasl_send_response(server, bytes);
g_bytes_unref(bytes);
void
send_sasl_abort(server)
Irssi::Irc::Server server
CODE:
sasl_abort(server);

View file

@ -12,6 +12,7 @@
#include "mode-lists.h"
#include "netsplit.h"
#include "servers-redirect.h"
#include "sasl.h"
#include "dcc/dcc.h"
#include "dcc/dcc-file.h"

View file

@ -80,6 +80,7 @@ void perl_signal_args_to_c(
unsigned long v_ulong;
GSList *v_gslist;
GList *v_glist;
GBytes *v_gbytes;
} saved_args[SIGNAL_MAX_ARGUMENTS];
void *p[SIGNAL_MAX_ARGUMENTS];
PERL_SIGNAL_ARGS_REC *rec;
@ -101,6 +102,12 @@ void perl_signal_args_to_c(
c_arg = NULL;
} else if (g_strcmp0(rec->args[n], "string") == 0) {
c_arg = SvPV_nolen(arg);
} else if (g_strcmp0(rec->args[n], "gbytes") == 0) {
char *bytes;
STRLEN len;
bytes = SvPV(arg, len);
c_arg = saved_args[n].v_gbytes = g_bytes_new(bytes, len);
} else if (g_strcmp0(rec->args[n], "int") == 0) {
c_arg = (void *)SvIV(arg);
} else if (g_strcmp0(rec->args[n], "ulongptr") == 0) {
@ -181,7 +188,9 @@ void perl_signal_args_to_c(
continue;
}
if (g_strcmp0(rec->args[n], "intptr") == 0) {
if (g_strcmp0(rec->args[n], "gbytes") == 0) {
g_bytes_unref(saved_args[n].v_gbytes);
} else if (g_strcmp0(rec->args[n], "intptr") == 0) {
SV *t = SvRV(arg);
SvIOK_only(t);
SvIV_set(t, saved_args[n].v_int);
@ -291,6 +300,14 @@ static void perl_call_signal(PERL_SCRIPT_REC *script, SV *func,
/* "simple irssi object" - any struct that has
int type; as it's first variable (dcc) */
perlarg = simple_iobject_bless((SERVER_REC *) arg);
} else if (g_strcmp0(rec->args[n], "gbytes") == 0) {
GBytes *tmp;
const char *data;
STRLEN data_len;
tmp = arg;
data = g_bytes_get_data(tmp, &data_len);
perlarg = newSVpvn(data, data_len);
} else {
/* blessed object */
perlarg = plain_bless(arg, rec->args[n]);