diff options
-rw-r--r-- | irc.h | 2 | ||||
-rw-r--r-- | irc_cap.c | 7 | ||||
-rw-r--r-- | irc_commands.c | 122 | ||||
-rw-r--r-- | root_commands.c | 12 |
4 files changed, 143 insertions, 0 deletions
@@ -49,6 +49,7 @@ typedef enum { USTATUS_SHUTDOWN = 8, /* Now used to indicate we're shutting down. Currently just blocks irc_vawrite(). */ USTATUS_CAP_PENDING = 16, + USTATUS_SASL_PLAIN_PENDING = 32, /* Not really status stuff, but other kinds of flags: For slightly better password security, since the only way to send passwords @@ -66,6 +67,7 @@ typedef enum { } irc_status_t; typedef enum { + CAP_SASL = (1 << 0), CAP_MULTI_PREFIX = (1 << 1), } irc_cap_flag_t; @@ -37,6 +37,7 @@ typedef struct { } cap_info_t; static const cap_info_t supported_caps[] = { + {"sasl", CAP_SASL}, {"multi-prefix", CAP_MULTI_PREFIX}, {NULL}, }; @@ -169,6 +170,12 @@ void irc_cmd_cap(irc_t *irc, char **cmd) } else if (g_strcasecmp(cmd[1], "END") == 0) { irc->status &= ~USTATUS_CAP_PENDING; + + if (irc->status & USTATUS_SASL_PLAIN_PENDING) { + irc_send_num(irc, 906, ":SASL authentication aborted"); + irc->status &= ~USTATUS_SASL_PLAIN_PENDING; + } + irc_check_login(irc); } else { diff --git a/irc_commands.c b/irc_commands.c index 14a3fd9d..aa0ecb73 100644 --- a/irc_commands.c +++ b/irc_commands.c @@ -27,6 +27,7 @@ #include "bitlbee.h" #include "help.h" #include "ipc.h" +#include "base64.h" static void irc_cmd_pass(irc_t *irc, char **cmd) { @@ -57,6 +58,120 @@ static void irc_cmd_pass(irc_t *irc, char **cmd) } } +static gboolean irc_sasl_plain_parse(char *input, char **user, char **pass) +{ + int i, part, len; + guint8 *decoded; + char *parts[2]; + + /* bitlbee's base64_decode wrapper adds an extra null terminator at the end */ + len = base64_decode(input, &decoded); + + /* this loop splits the decoded string into the parts array, like this: + "username\0username\0password" -> {"username", "username", "password"} */ + + for (i = 0, part = 0; i < len && part < 3; part++) { + /* set each of parts[] to point to the beginning of a string */ + parts[part] = (char *) decoded + i; + + /* move the cursor forward to the next null terminator*/ + i += strlen(parts[part]) + 1; + } + + /* sanity checks */ + if (part != 3 || i != (len + 1) || strcmp(parts[0], parts[1]) != 0) { + g_free(decoded); + return FALSE; + } else { + *user = g_strdup(parts[0]); + *pass = g_strdup(parts[2]); + g_free(decoded); + return TRUE; + } +} + +static gboolean irc_sasl_check_pass(irc_t *irc, char *user, char *pass) +{ + storage_status_t status; + + /* just check the password here to be able to reply with useful numerics + * the actual identification will be handled later */ + status = storage_check_pass(user, pass); + + if (status == STORAGE_OK) { + if (!irc->user->nick) { + /* set the nick here so we have it for the following numeric */ + irc->user->nick = g_strdup(user); + } + irc_send_num(irc, 903, ":Password accepted"); + return TRUE; + + } else if (status == STORAGE_INVALID_PASSWORD) { + irc_send_num(irc, 904, ":Incorrect password"); + } else if (status == STORAGE_NO_SUCH_USER) { + irc_send_num(irc, 904, ":The nick is (probably) not registered"); + } else { + irc_send_num(irc, 904, ":Unknown SASL authentication error"); + } + + return FALSE; +} + +static void irc_cmd_authenticate(irc_t *irc, char **cmd) +{ + /* require the CAP to be enabled, and don't allow authentication before server password */ + if (!(irc->caps & CAP_SASL) || + (global.conf->authmode == AUTHMODE_CLOSED && !(irc->status & USTATUS_AUTHORIZED))) { + return; + } + + if (irc->status & USTATUS_SASL_PLAIN_PENDING) { + char *user, *pass; + + irc->status &= ~USTATUS_SASL_PLAIN_PENDING; + + if (!irc_sasl_plain_parse(cmd[1], &user, &pass)) { + irc_send_num(irc, 904, ":SASL authentication failed"); + return; + } + + /* let's not support the nick != user case + * if NICK is received after SASL, it will just fail after registration */ + if (user && irc->user->nick && strcmp(user, irc->user->nick) != 0) { + irc_send_num(irc, 902, ":Your SASL username does not match your nickname"); + + } else if (irc_sasl_check_pass(irc, user, pass)) { + /* and here we do the same thing as the PASS command*/ + if (irc->status & USTATUS_LOGGED_IN) { + char *send_cmd[] = { "identify", pass, NULL }; + root_command(irc, send_cmd); + } else { + /* no check_login here - wait for CAP END */ + irc_setpass(irc, pass); + } + } + + g_free(user); + g_free(pass); + + } else if (irc->status & USTATUS_IDENTIFIED) { + irc_send_num(irc, 907, ":You have already authenticated"); + + } else if (strcmp(cmd[1], "*") == 0) { + irc_send_num(irc, 906, ":SASL authentication aborted"); + irc->status &= ~USTATUS_SASL_PLAIN_PENDING; + + } else if (g_strcasecmp(cmd[1], "PLAIN") == 0) { + irc_write(irc, "AUTHENTICATE +"); + irc->status |= USTATUS_SASL_PLAIN_PENDING; + + } else { + irc_send_num(irc, 908, "PLAIN :is the available SASL mechanism"); + irc_send_num(irc, 904, ":SASL authentication failed"); + irc->status &= ~USTATUS_SASL_PLAIN_PENDING; + } +} + static void irc_cmd_user(irc_t *irc, char **cmd) { irc->user->user = g_strdup(cmd[1]); @@ -82,6 +197,12 @@ static void irc_cmd_nick(irc_t *irc, char **cmd) irc_setpass(irc, NULL); irc->status &= ~USTATUS_IDENTIFIED; irc_umode_set(irc, "-R", 1); + + if (irc->caps & CAP_SASL) { + irc_send_num(irc, 901, "%s!%s@%s :You are now logged out", + irc->user->nick, irc->user->user, irc->user->host); + } + irc_rootmsg(irc, "Changing nicks resets your identify status. " "Re-identify or register a new account if you want " "your configuration to be saved. See \x02help " @@ -721,6 +842,7 @@ static const command_t irc_commands[] = { { "rehash", 0, irc_cmd_rehash, IRC_CMD_OPER_ONLY }, { "restart", 0, NULL, IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER }, { "kill", 2, NULL, IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER }, + { "authenticate", 1, irc_cmd_authenticate, 0 }, { NULL } }; diff --git a/root_commands.c b/root_commands.c index 7e1dc2a7..b5d0aef1 100644 --- a/root_commands.c +++ b/root_commands.c @@ -162,6 +162,12 @@ static void cmd_identify(irc_t *irc, char **cmd) irc->status |= USTATUS_IDENTIFIED; irc_umode_set(irc, "+R", 1); + if (irc->caps & CAP_SASL) { + irc_user_t *iu = irc->user; + irc_send_num(irc, 900, "%s!%s@%s %s :You are now logged in as %s", + iu->nick, iu->user, iu->host, iu->nick, iu->nick); + } + bitlbee_whatsnew(irc); /* The following code is a bit hairy now. With takeover @@ -238,6 +244,12 @@ static void cmd_register(irc_t *irc, char **cmd) irc->status |= USTATUS_IDENTIFIED; irc_umode_set(irc, "+R", 1); + if (irc->caps & CAP_SASL) { + irc_user_t *iu = irc->user; + irc_send_num(irc, 900, "%s!%s@%s %s :You are now logged in as %s", + iu->nick, iu->user, iu->host, iu->nick, iu->nick); + } + /* Set this var now, or anyone who logs in to his/her newly created account for the first time gets the whatsnew story. */ |