about summary refs log tree commit diff
path: root/state.c
diff options
context:
space:
mode:
Diffstat (limited to 'state.c')
-rw-r--r--state.c128
1 files changed, 90 insertions, 38 deletions
diff --git a/state.c b/state.c
index 59a61ed..a28b3ba 100644
--- a/state.c
+++ b/state.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 2019  C. McEnroe <june@causal.agency>
+/* Copyright (C) 2019  June 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
@@ -38,6 +38,8 @@
 
 bool stateNoNames;
 enum Cap stateCaps;
+char *stateAccount;
+bool stateAway;
 
 typedef void Handler(struct Message *msg);
 
@@ -59,26 +61,34 @@ void stateLogin(
 	const char *pass, enum Cap blind, const char *plain,
 	const char *nick, const char *user, const char *real
 ) {
-	serverFormat("CAP LS 302\r\n");
-	if (pass) serverFormat("PASS :%s\r\n", pass);
-	if (blind) serverFormat("CAP REQ :%s\r\n", capList(blind, NULL));
 	if (plain) {
-		byte buf[AuthLen];
+		byte buf[AuthLen] = {0};
 		size_t len = 1 + strlen(plain);
-		if (sizeof(buf) < len) {
-			errx(EX_SOFTWARE, "SASL PLAIN is too long");
-		}
-		buf[0] = 0;
-		for (size_t i = 0; plain[i]; ++i) {
-			buf[1 + i] = (plain[i] == ':' ? 0 : plain[i]);
-		}
+		if (len > sizeof(buf)) errx(EX_USAGE, "SASL PLAIN too long");
+		memcpy(&buf[1], plain, len - 1);
+		byte *sep = memchr(buf, ':', len);
+		if (!sep) errx(EX_USAGE, "SASL PLAIN missing colon");
+		*sep = 0;
 		base64(plainBase64, buf, len);
-		explicit_bzero(buf, sizeof(buf));
+		explicit_bzero(buf, len);
 	}
+
+	serverFormat("CAP LS 302\r\n");
+	if (pass) serverFormat("PASS :%s\r\n", pass);
+	if (blind) serverFormat("CAP REQ :%s\r\n", capList(blind, NULL));
 	serverFormat("NICK %s\r\n", nick);
 	serverFormat("USER %s 0 * :%s\r\n", user, real);
 }
 
+static const enum Cap DontReq = 0
+	| CapConsumer
+	| CapPalaverApp
+	| CapPassive
+	| CapReadMarker
+	| CapSASL
+	| CapSTS
+	| CapUnsupported;
+
 static void handleCap(struct Message *msg) {
 	require(msg, false, 3);
 	enum Cap caps;
@@ -89,8 +99,15 @@ static void handleCap(struct Message *msg) {
 	}
 
 	if (!strcmp(msg->params[1], "LS") || !strcmp(msg->params[1], "NEW")) {
-		caps &= ~(CapSASL | CapSTS | CapUnsupported);
-		if (caps) serverFormat("CAP REQ :%s\r\n", capList(caps, NULL));
+		caps &= ~DontReq;
+		if (caps & CapEchoMessage && !(caps & CapLabeledResponse)) {
+			caps &= ~CapEchoMessage;
+		}
+		if (caps) {
+			serverFormat("CAP REQ :%s\r\n", capList(caps, NULL));
+		} else {
+			if (!(stateCaps & CapSASL)) serverFormat("CAP END\r\n");
+		}
 
 	} else if (!strcmp(msg->params[1], "ACK")) {
 		stateCaps |= caps;
@@ -120,7 +137,8 @@ static void handleAuthenticate(struct Message *msg) {
 }
 
 static void handleReplyLoggedIn(struct Message *msg) {
-	(void)msg;
+	require(msg, false, 3);
+	set(&stateAccount, msg->params[2]);
 	serverFormat("CAP END\r\n");
 }
 
@@ -159,12 +177,6 @@ bool stateReady(void) {
 		&& intro.myInfo[0];
 }
 
-static void set(char **field, const char *value) {
-	if (*field) free(*field);
-	*field = strdup(value);
-	if (!*field) err(EX_OSERR, "strdup");
-}
-
 static void handleErrorNicknameInUse(struct Message *msg) {
 	if (self.nick) return;
 	require(msg, false, 2);
@@ -198,6 +210,7 @@ static void handleReplyMyInfo(struct Message *msg) {
 }
 
 static struct {
+	bool done;
 	char **tokens;
 	size_t cap, len;
 } support;
@@ -215,12 +228,18 @@ static void supportAdd(const char *token) {
 
 static void handleReplyISupport(struct Message *msg) {
 	require(msg, false, 1);
+	if (support.done) return;
 	for (size_t i = 1; i < ParamCap; ++i) {
 		if (!msg->params[i] || strchr(msg->params[i], ' ')) break;
 		supportAdd(msg->params[i]);
 	}
 }
 
+static void handleReplyMOTDStart(struct Message *msg) {
+	(void)msg;
+	support.done = true;
+}
+
 struct Channel {
 	char *name;
 	char *topic;
@@ -267,9 +286,9 @@ static bool originSelf(const char *origin) {
 	size_t len = strlen(self.nick);
 	if (strlen(origin) < len) return false;
 	if (strncmp(origin, self.nick, len)) return false;
-	if (origin[len] != '!') return false;
+	if (origin[len] && origin[len] != '!') return false;
 
-	if (!self.origin || strcmp(self.origin, origin)) {
+	if (origin[len] && (!self.origin || strcmp(self.origin, origin))) {
 		set(&self.origin, origin);
 	}
 	return true;
@@ -280,6 +299,7 @@ static void handleNick(struct Message *msg) {
 	if (!originSelf(msg->origin)) return;
 	set(&self.nick, msg->params[0]);
 
+	if (!self.origin) return;
 	char *rest = strchr(self.origin, '!');
 	assert(rest);
 	size_t size = strlen(self.nick) + strlen(rest) + 1;
@@ -317,6 +337,16 @@ static void handleReplyTopic(struct Message *msg) {
 	chanTopic(msg->params[1], msg->params[2]);
 }
 
+static void handleReplyUnaway(struct Message *msg) {
+	(void)msg;
+	stateAway = false;
+}
+
+static void handleReplyNowAway(struct Message *msg) {
+	(void)msg;
+	stateAway = true;
+}
+
 static void handleError(struct Message *msg) {
 	require(msg, false, 1);
 	errx(EX_UNAVAILABLE, "%s", msg->params[0]);
@@ -331,8 +361,13 @@ static const struct {
 	{ "003", handleReplyCreated },
 	{ "004", handleReplyMyInfo },
 	{ "005", handleReplyISupport },
+	{ "305", handleReplyUnaway },
+	{ "306", handleReplyNowAway },
 	{ "332", handleReplyTopic },
+	{ "375", handleReplyMOTDStart },
+	{ "422", handleReplyMOTDStart },
 	{ "433", handleErrorNicknameInUse },
+	{ "437", handleErrorNicknameInUse },
 	{ "900", handleReplyLoggedIn },
 	{ "904", handleErrorSASLFail },
 	{ "905", handleErrorSASLFail },
@@ -364,18 +399,30 @@ void stateSync(struct Client *client) {
 		client,
 		":%s NOTICE %s :"
 		"pounce is GPLv3 fwee softwawe ^w^  code is avaiwable fwom %s\r\n",
-		ORIGIN, self.nick, SOURCE_URL
+		clientOrigin, self.nick, SOURCE_URL
 	);
 
+	if (stateAccount) {
+		clientFormat(
+			client, ":%s 900 %s %s %s :You are now logged in as %s\r\n",
+			clientOrigin, self.nick, stateEcho(), stateAccount, stateAccount
+		);
+	}
+
 	clientFormat(
-		client,
-		":%s 001 %s :%s\r\n"
-		":%s 002 %s :%s\r\n"
-		":%s 003 %s :%s\r\n"
-		":%s 004 %s %s %s %s %s%s%s\r\n",
-		intro.origin, self.nick, intro.welcome,
-		intro.origin, self.nick, intro.yourHost,
-		intro.origin, self.nick, intro.created,
+		client, ":%s 001 %s :%s\r\n",
+		intro.origin, self.nick, intro.welcome
+	);
+	clientFormat(
+		client, ":%s 002 %s :%s\r\n",
+		intro.origin, self.nick, intro.yourHost
+	);
+	clientFormat(
+		client, ":%s 003 %s :%s\r\n",
+		intro.origin, self.nick, intro.created
+	);
+	clientFormat(
+		client, ":%s 004 %s %s %s %s %s%s%s\r\n",
 		intro.origin, self.nick,
 		intro.myInfo[0], intro.myInfo[1], intro.myInfo[2], intro.myInfo[3],
 		(intro.myInfo[4] ? " " : ""), (intro.myInfo[4] ? intro.myInfo[4] : "")
@@ -399,16 +446,18 @@ void stateSync(struct Client *client) {
 		);
 	}
 	if (i < support.len) {
-		clientFormat(client, ":%s 005 %s", intro.origin, self.nick);
+		char buf[512], *ptr = buf, *end = &buf[sizeof(buf)];
+		ptr = seprintf(ptr, end, ":%s 005 %s", intro.origin, self.nick);
 		for (; i < support.len; ++i) {
-			clientFormat(client, " %s", support.tokens[i]);
+			ptr = seprintf(ptr, end, " %s", support.tokens[i]);
 		}
-		clientFormat(client, " :are supported by this server\r\n");
+		ptr = seprintf(ptr, end, " :are supported by this server\r\n");
+		clientSend(client, buf, ptr - buf);
 	}
 
 	clientFormat(
 		client, ":%s 422 %s :MOTD File is missing\r\n",
-		ORIGIN, self.nick
+		clientOrigin, self.nick
 	);
 
 	if (chans.len) assert(self.origin);
@@ -418,9 +467,12 @@ void stateSync(struct Client *client) {
 		if (chan->topic) {
 			clientFormat(
 				client, ":%s 332 %s %s :%s\r\n",
-				ORIGIN, self.nick, chan->name, chan->topic
+				clientOrigin, self.nick, chan->name, chan->topic
 			);
 		}
+		if (client->caps & CapReadMarker) {
+			clientGetMarker(client, chan->name);
+		}
 		if (stateNoNames) continue;
 		serverEnqueue("NAMES %s\r\n", chan->name);
 	}