summary refs log tree commit diff
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2020-01-12 18:07:54 -0500
committerJune McEnroe <june@causal.agency>2020-01-12 18:07:54 -0500
commit5e6094e437a5437ceb6b083d16995ea629a4d720 (patch)
tree34ad1b244212caab1b832a6243988e388282434a
parentAdd a vendor capability for passive clients (diff)
downloadpounce-5e6094e437a5437ceb6b083d16995ea629a4d720.tar.gz
pounce-5e6094e437a5437ceb6b083d16995ea629a4d720.zip
Add option to set local client CA
This is a little bit messy. Allows setting either -A or -W or both.
Implements SASL EXTERNAL for clients that expect that when connecting
with a client certificate.

Need to test that reloading still works inside capsicum, since I suspect
that rewind call may be blocked.
-rw-r--r--bounce.c16
-rw-r--r--bounce.h3
-rw-r--r--client.c28
-rw-r--r--local.c20
-rw-r--r--pounce.122
5 files changed, 83 insertions, 6 deletions
diff --git a/bounce.c b/bounce.c
index 89b2ff1..7ea1c3f 100644
--- a/bounce.c
+++ b/bounce.c
@@ -215,6 +215,7 @@ int main(int argc, char *argv[]) {
 	char bindPath[PATH_MAX] = "";
 	char certPath[PATH_MAX] = "";
 	char privPath[PATH_MAX] = "";
+	const char *caPath = NULL;
 
 	bool insecure = false;
 	const char *clientCert = NULL;
@@ -232,9 +233,10 @@ int main(int argc, char *argv[]) {
 	const char *join = NULL;
 	const char *quit = "connection reset by purr";
 
-	const char *Opts = "!C:H:K:NP:U:W:a:c:ef:g:h:j:k:n:p:q:r:s:u:vw:xy:";
+	const char *Opts = "!A:C:H:K:NP:U:W:a:c:ef:g:h:j:k:n:p:q:r:s:u:vw:xy:";
 	const struct option LongOpts[] = {
 		{ "insecure", no_argument, NULL, '!' },
+		{ "client-ca", required_argument, NULL, 'A' },
 		{ "cert", required_argument, NULL, 'C' },
 		{ "bind-host", required_argument, NULL, 'H' },
 		{ "priv", required_argument, NULL, 'K' },
@@ -265,6 +267,7 @@ int main(int argc, char *argv[]) {
 	while (0 < (opt = getopt_config(argc, argv, Opts, LongOpts, NULL))) {
 		switch (opt) {
 			break; case '!': insecure = true;
+			break; case 'A': clientCA = true; caPath = optarg;
 			break; case 'C': strlcpy(certPath, optarg, sizeof(certPath));
 			break; case 'H': bindHost = optarg;
 			break; case 'K': strlcpy(privPath, optarg, sizeof(privPath));
@@ -331,11 +334,17 @@ int main(int argc, char *argv[]) {
 	ringAlloc(ringSize);
 	if (savePath) saveLoad(savePath);
 
+	FILE *localCA = NULL;
+	if (caPath) {
+		localCA = fopen(caPath, "r");
+		if (!localCA) err(EX_NOINPUT, "%s", caPath);
+	}
+
 	struct SplitPath certSplit = splitPath(certPath);
 	struct SplitPath privSplit = splitPath(privPath);
 	FILE *cert = splitOpen(certSplit);
 	FILE *priv = splitOpen(privSplit);
-	localConfig(cert, priv);
+	localConfig(cert, priv, localCA, !clientPass);
 	fclose(cert);
 	fclose(priv);
 
@@ -359,6 +368,7 @@ int main(int argc, char *argv[]) {
 	cap_rights_merge(&bindRights, &sockRights);
 
 	if (saveFile) capLimit(fileno(saveFile), &saveRights);
+	if (localCA) capLimit(fileno(localCA), &fileRights);
 	capLimitSplit(certSplit, &fileRights);
 	capLimitSplit(privSplit, &fileRights);
 	for (size_t i = 0; i < binds; ++i) {
@@ -401,7 +411,7 @@ int main(int argc, char *argv[]) {
 		if (signals[SIGUSR1]) {
 			cert = splitOpen(certSplit);
 			priv = splitOpen(privSplit);
-			localConfig(cert, priv);
+			localConfig(cert, priv, localCA, !clientPass);
 			fclose(cert);
 			fclose(priv);
 			signals[SIGUSR1] = 0;
diff --git a/bounce.h b/bounce.h
index 5bff619..a0f9160 100644
--- a/bounce.h
+++ b/bounce.h
@@ -127,7 +127,7 @@ void ringInfo(void);
 int ringSave(FILE *file);
 void ringLoad(FILE *file);
 
-void localConfig(FILE *cert, FILE *priv);
+void localConfig(FILE *cert, FILE *priv, FILE *ca, bool require);
 size_t localBind(int fds[], size_t cap, const char *host, const char *port);
 size_t localUnix(int fds[], size_t cap, const char *path);
 struct tls *localAccept(int *fd, int bind);
@@ -139,6 +139,7 @@ void serverSend(const char *ptr, size_t len);
 void serverFormat(const char *format, ...)
 	__attribute__((format(printf, 1, 2)));
 
+extern bool clientCA;
 extern char *clientPass;
 extern char *clientAway;
 struct Client *clientAlloc(struct tls *tls);
diff --git a/client.c b/client.c
index a598ec0..fe5eceb 100644
--- a/client.c
+++ b/client.c
@@ -30,6 +30,7 @@
 
 #include "bounce.h"
 
+bool clientCA;
 char *clientPass;
 char *clientAway;
 
@@ -57,6 +58,9 @@ struct Client *clientAlloc(struct tls *tls) {
 	if (!client) err(EX_OSERR, "calloc");
 	client->tls = tls;
 	client->need = NeedNick | NeedUser | (clientPass ? NeedPass : 0);
+	if (clientCA && tls_peer_cert_provided(tls)) {
+		client->need &= ~NeedPass;
+	}
 	return client;
 }
 
@@ -160,7 +164,9 @@ static void handlePass(struct Client *client, struct Message *msg) {
 
 static void handleCap(struct Client *client, struct Message *msg) {
 	if (!msg->params[0]) msg->params[0] = "";
+
 	enum Cap avail = CapServerTime | CapPassive | (stateCaps & ~CapSASL);
+	if (clientCA) avail |= CapSASL;
 
 	if (!strcmp(msg->params[0], "END")) {
 		if (!client->need) return;
@@ -192,6 +198,27 @@ static void handleCap(struct Client *client, struct Message *msg) {
 	}
 }
 
+static void handleAuthenticate(struct Client *client, struct Message *msg) {
+	if (!msg->params[0]) msg->params[0] = "";
+	if (!strcmp(msg->params[0], "EXTERNAL")) {
+		clientFormat(client, "AUTHENTICATE +\r\n");
+	} else if (!strcmp(msg->params[0], "+")) {
+		clientFormat(
+			client, ":%s 900 * %s * :You are now logged in as *\r\n",
+			ORIGIN, stateEcho()
+		);
+		clientFormat(
+			client, ":%s 903 * :SASL authentication successful\r\n",
+			ORIGIN
+		);
+	} else {
+		clientFormat(
+			client, ":%s 904 * :SASL authentication failed\r\n",
+			ORIGIN
+		);
+	}
+}
+
 static void handleQuit(struct Client *client, struct Message *msg) {
 	(void)msg;
 	clientFormat(client, "ERROR :Detaching\r\n");
@@ -218,6 +245,7 @@ static const struct {
 	Handler *fn;
 	bool need;
 } Handlers[] = {
+	{ "AUTHENTICATE", handleAuthenticate, false },
 	{ "CAP", handleCap, false },
 	{ "NICK", handleNick, false },
 	{ "NOTICE", handlePrivmsg, true },
diff --git a/local.c b/local.c
index c147259..a4de1bc 100644
--- a/local.c
+++ b/local.c
@@ -47,13 +47,14 @@ static byte *readFile(size_t *len, FILE *file) {
 	byte *buf = malloc(stat.st_size);
 	if (!buf) err(EX_OSERR, "malloc");
 
+	rewind(file);
 	*len = fread(buf, 1, stat.st_size, file);
 	if (ferror(file)) err(EX_IOERR, "fread");
 
 	return buf;
 }
 
-void localConfig(FILE *cert, FILE *priv) {
+void localConfig(FILE *cert, FILE *priv, FILE *ca, bool require) {
 	tls_free(server);
 	server = tls_server();
 	if (!server) errx(EX_SOFTWARE, "tls_server");
@@ -76,6 +77,23 @@ void localConfig(FILE *cert, FILE *priv) {
 	}
 	free(buf);
 
+	if (ca) {
+		buf = readFile(&len, ca);
+		error = tls_config_set_ca_mem(config, buf, len);
+		if (error) {
+			errx(
+				EX_CONFIG, "tls_config_set_ca_mem: %s",
+				tls_config_error(config)
+			);
+		}
+		free(buf);
+		if (require) {
+			tls_config_verify_client(config);
+		} else {
+			tls_config_verify_client_optional(config);
+		}
+	}
+
 	error = tls_configure(server, config);
 	if (error) errx(EX_SOFTWARE, "tls_configure: %s", tls_error(server));
 	tls_config_free(config);
diff --git a/pounce.1 b/pounce.1
index 3b7f8e1..3aed409 100644
--- a/pounce.1
+++ b/pounce.1
@@ -1,4 +1,4 @@
-.Dd January 10, 2020
+.Dd January 12, 2020
 .Dt POUNCE 1
 .Os
 .
@@ -9,6 +9,7 @@
 .Sh SYNOPSIS
 .Nm
 .Op Fl Nev
+.Op Fl A Ar cert
 .Op Fl C Ar cert
 .Op Fl H Ar host
 .Op Fl K Ar priv
@@ -68,6 +69,20 @@ following their corresponding flags.
 The arguments are as follows:
 .
 .Bl -tag -width Ds
+.It Fl A Ar path , Cm client-ca = Ar path
+Load the TLS client certificate authority (CA) from
+.Ar path .
+If
+.Fl W
+is unset,
+clients must present a certificate signed by the CA
+to connect.
+If
+.Fl W
+is also set,
+clients may either connect using the password
+or a client certificate.
+.
 .It Fl C Ar path , Cm cert = Ar path
 Load TLS certificate from
 .Ar path .
@@ -132,6 +147,11 @@ The
 .Ar pass
 string must be hashed using
 .Fl x .
+If
+.Fl A
+is also set,
+clients may instead connect
+using a client certificate.
 .
 .It Fl a Ar user : Ns Ar pass , Cm sasl-plain = Ar user : Ns Ar pass
 Authenticate as