diff options
author | C. McEnroe | 2020-02-01 01:18:01 -0500 |
---|---|---|
committer | C. McEnroe | 2020-02-01 01:18:01 -0500 |
commit | 843160236381d0c76bef1eac89e556920d700a9d (patch) | |
tree | 469dc9d9e9c6b6928984f3fad8a545ef27385184 /handle.c | |
parent | f76145645e6e183c53c5601294c985246c00fa92 (diff) |
Blindly implement login flow
Diffstat (limited to 'handle.c')
-rw-r--r-- | handle.c | 163 |
1 files changed, 163 insertions, 0 deletions
diff --git a/handle.c b/handle.c new file mode 100644 index 0000000..4084525 --- /dev/null +++ b/handle.c @@ -0,0 +1,163 @@ +/* Copyright (C) 2020 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <err.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> + +#include "chat.h" + +static const char *CapNames[] = { +#define X(name, id) [id] = name, + ENUM_CAP +#undef X +}; + +static enum Cap capParse(const char *list) { + enum Cap caps = 0; + while (*list) { + enum Cap cap = 0; + size_t len = strcspn(list, " "); + for (size_t i = 0; i < ARRAY_LEN(CapNames); ++i) { + if (len != strlen(CapNames[i])) continue; + if (strncmp(list, CapNames[i], len)) continue; + cap = 1 << i; + break; + } + caps |= cap; + list += len; + if (*list) list++; + } + return caps; +} + +static const char *capList(enum Cap caps) { + static char buf[1024]; + buf[0] = '\0'; + for (size_t i = 0; i < ARRAY_LEN(CapNames); ++i) { + if (caps & (1 << i)) { + if (buf[0]) strlcat(buf, " ", sizeof(buf)); + strlcat(buf, CapNames[i], sizeof(buf)); + } + } + return buf; +} + +static void set(char **field, const char *value) { + free(*field); + *field = strdup(value); + if (!*field) err(EX_OSERR, "strdup"); +} + +typedef void Handler(struct Message *msg); + +static void require(const struct Message *msg, bool origin, size_t len) { + if (origin && !msg->nick) { + errx(EX_PROTOCOL, "%s missing origin", msg->cmd); + } + for (size_t i = 0; i < len; ++i) { + if (msg->params[i]) continue; + errx(EX_PROTOCOL, "%s missing parameter %zu", msg->cmd, 1 + i); + } +} + +static void handleCap(struct Message *msg) { + require(msg, false, 3); + enum Cap caps = capParse(msg->params[2]); + if (!strcmp(msg->params[1], "LS")) { + caps &= ~CapSASL; + ircFormat("CAP REQ :%s\r\n", capList(caps)); + } else if (!strcmp(msg->params[1], "ACK")) { + self.caps |= caps; + if (caps & CapSASL) { + ircFormat("AUTHENTICATE %s\r\n", (self.plain ? "PLAIN" : "EXTERNAL")); + } + if (!(self.caps & CapSASL)) ircFormat("CAP END\r\n"); + } else if (!strcmp(msg->params[1], "NAK")) { + errx(EX_CONFIG, "server does not support %s", msg->params[2]); + } +} + +static void handleAuthenticate(struct Message *msg) { + (void)msg; + if (!self.plain) { + ircFormat("AUTHENTICATE +\r\n"); + return; + } + + byte buf[299]; + size_t len = 1 + strlen(self.plain); + if (sizeof(buf) < len) errx(EX_CONFIG, "SASL PLAIN is too long"); + buf[0] = 0; + for (size_t i = 0; self.plain[i]; ++i) { + buf[1 + i] = (self.plain[i] == ':' ? 0 : self.plain[i]); + } + + char b64[BASE64_SIZE(sizeof(buf))]; + base64(b64, buf, len); + ircFormat("AUTHENTICATE "); + ircSend(b64, BASE64_SIZE(len)); + ircFormat("\r\n"); + + explicit_bzero(b64, sizeof(b64)); + explicit_bzero(buf, sizeof(buf)); + explicit_bzero(self.plain, strlen(self.plain)); +} + +static void handleReplyLoggedIn(struct Message *msg) { + (void)msg; + ircFormat("CAP END\r\n"); +} + +static void handleErrorSASLFail(struct Message *msg) { + require(msg, false, 2); + errx(EX_CONFIG, "%s", msg->params[1]); +} + +static void handleReplyWelcome(struct Message *msg) { + require(msg, false, 1); + set(&self.nick, msg->params[0]); + if (self.join) ircFormat("JOIN :%s\r\n", self.join); +} + +static const struct Handler { + const char *cmd; + Handler *fn; +} Handlers[] = { + { "001", handleReplyWelcome }, + { "900", handleReplyLoggedIn }, + { "904", handleErrorSASLFail }, + { "905", handleErrorSASLFail }, + { "906", handleErrorSASLFail }, + { "AUTHENTICATE", handleAuthenticate }, + { "CAP", handleCap }, +}; + +static int compar(const void *cmd, const void *_handler) { + const struct Handler *handler = _handler; + return strcmp(cmd, handler->cmd); +} + +void handle(struct Message msg) { + if (!msg.cmd) return; + const struct Handler *handler = bsearch( + msg.cmd, Handlers, ARRAY_LEN(Handlers), sizeof(*handler), compar + ); + if (handler) handler->fn(&msg); +} |