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.c149
1 files changed, 105 insertions, 44 deletions
diff --git a/state.c b/state.c
index 5da5bcc..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
@@ -12,6 +12,17 @@
  *
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ * Additional permission under GNU GPL version 3 section 7:
+ *
+ * If you modify this Program, or any covered work, by linking or
+ * combining it with OpenSSL (or a modified version of that library),
+ * containing parts covered by the terms of the OpenSSL License and the
+ * original SSLeay license, the licensors of this Program grant you
+ * additional permission to convey the resulting work. Corresponding
+ * Source for a non-source form of such a combination shall include the
+ * source code for the parts of OpenSSL used as well as that of the
+ * covered work.
  */
 
 #include <assert.h>
@@ -27,6 +38,8 @@
 
 bool stateNoNames;
 enum Cap stateCaps;
+char *stateAccount;
+bool stateAway;
 
 typedef void Handler(struct Message *msg);
 
@@ -45,31 +58,37 @@ enum { AuthLen = 299 };
 static char plainBase64[BASE64_SIZE(AuthLen)];
 
 void stateLogin(
-	const char *pass, bool sasl, const char *plain,
+	const char *pass, enum Cap blind, const char *plain,
 	const char *nick, const char *user, const char *real
 ) {
+	if (plain) {
+		byte buf[AuthLen] = {0};
+		size_t len = 1 + strlen(plain);
+		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, len);
+	}
+
 	serverFormat("CAP LS 302\r\n");
 	if (pass) serverFormat("PASS :%s\r\n", pass);
-	if (sasl) {
-		serverFormat("CAP REQ :%s\r\n", capList(CapSASL, NULL));
-		if (plain) {
-			byte buf[AuthLen];
-			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]);
-			}
-			base64(plainBase64, buf, len);
-			explicit_bzero(buf, sizeof(buf));
-		}
-	}
+	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;
@@ -80,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;
@@ -111,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");
 }
 
@@ -150,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);
@@ -189,6 +210,7 @@ static void handleReplyMyInfo(struct Message *msg) {
 }
 
 static struct {
+	bool done;
 	char **tokens;
 	size_t cap, len;
 } support;
@@ -206,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;
@@ -258,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;
@@ -271,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;
@@ -308,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]);
@@ -322,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 },
@@ -355,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] : "")
@@ -390,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);
@@ -409,10 +467,13 @@ 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;
-		serverFormat("NAMES %s\r\n", chan->name);
+		serverEnqueue("NAMES %s\r\n", chan->name);
 	}
 }