summary refs log tree commit diff
path: root/handle.c
diff options
context:
space:
mode:
authorC. McEnroe2020-02-01 01:18:01 -0500
committerC. McEnroe2020-02-01 01:18:01 -0500
commit843160236381d0c76bef1eac89e556920d700a9d (patch)
tree469dc9d9e9c6b6928984f3fad8a545ef27385184 /handle.c
parentf76145645e6e183c53c5601294c985246c00fa92 (diff)
Blindly implement login flow
Diffstat (limited to 'handle.c')
-rw-r--r--handle.c163
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);
+}