aboutsummaryrefslogtreecommitdiffstats
path: root/irc_commands.c
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 /irc_commands.c
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
Diffstat (limited to 'irc_commands.c')
-rw-r--r--irc_commands.c122
1 files changed, 122 insertions, 0 deletions
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 }
};