aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordequis <dx@dxzone.com.ar>2015-09-11 19:07:10 -0300
committerdequis <dx@dxzone.com.ar>2015-10-08 05:34:18 -0300
commit58b63de6f1dd84a4923c623dafd548512ecdf054 (patch)
treec6ded487de0a9538b412819b3f24f6925a3c1135
parent2f736927554d588d00d31f367cd07b9845036e09 (diff)
IRCv3 SASL capability + PLAIN method
Only plain, no other methods. We don't have built-in SSL to implement EXTERNAL (certfp) and nothing else is worth implementing. The actual authentication is pretty much like sending a server password (when the server's authmode isn't closed), which means it happens in cmd_identify, but this code also calls storage_check_pass() to send the required success/failure replies. SASL doesn't give us much benefit other than standards compliance, but some clients might appreciate it. And having a fifth way to do the same thing doesn't hurt! Now we have: - identify in &bitlbee - identify to nickserv (alias for root) - 'nickserv' and 'ns' irc commands - server password - sasl plain
-rw-r--r--irc.h2
-rw-r--r--irc_cap.c7
-rw-r--r--irc_commands.c122
-rw-r--r--root_commands.c12
4 files changed, 143 insertions, 0 deletions
diff --git a/irc.h b/irc.h
index 4be7ce7e..f2df3b2e 100644
--- a/irc.h
+++ b/irc.h
@@ -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;
diff --git a/irc_cap.c b/irc_cap.c
index 79732409..4cfc158b 100644
--- a/irc_cap.c
+++ b/irc_cap.c
@@ -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. */