about summary refs log tree commit diff
path: root/bounce.c
diff options
context:
space:
mode:
Diffstat (limited to 'bounce.c')
-rw-r--r--bounce.c349
1 files changed, 139 insertions, 210 deletions
diff --git a/bounce.c b/bounce.c
index 381e334..9ab0f1d 100644
--- a/bounce.c
+++ b/bounce.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
@@ -25,7 +25,6 @@
  * covered work.
  */
 
-#include <assert.h>
 #include <err.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -44,18 +43,10 @@
 #include <sys/stat.h>
 #include <sys/time.h>
 #include <sysexits.h>
+#include <time.h>
 #include <tls.h>
 #include <unistd.h>
 
-#ifdef __FreeBSD__
-#include <sys/capsicum.h>
-#endif
-
-// For getentropy(2):
-#ifdef __APPLE__
-#include <sys/random.h>
-#endif
-
 #ifndef SIGINFO
 #define SIGINFO SIGUSR2
 #endif
@@ -126,51 +117,6 @@ static void saveLoad(const char *path) {
 	atexit(saveSave);
 }
 
-#ifdef __FreeBSD__
-static void capLimit(int fd, const cap_rights_t *rights) {
-	int error = cap_rights_limit(fd, rights);
-	if (error) err(EX_OSERR, "cap_rights_limit");
-}
-#endif
-
-#ifdef __OpenBSD__
-static void unveilParent(const char *path, const char *mode) {
-	char buf[PATH_MAX];
-	strlcpy(buf, path, sizeof(buf));
-	char *base = strrchr(buf, '/');
-	if (base) *base = '\0';
-	int error = unveil((base ? buf : "."), mode);
-	if (error && errno != ENOENT) err(EX_OSERR, "unveil");
-}
-
-static void unveilTarget(const char *path, const char *mode) {
-	char buf[PATH_MAX];
-	strlcpy(buf, path, sizeof(buf));
-	char *base = strrchr(buf, '/');
-	base = (base ? base + 1 : buf);
-	ssize_t len = readlink(path, base, sizeof(buf) - (base - buf) - 1);
-	if (len < 0) return;
-	base[len] = '\0';
-	unveilParent(buf, mode);
-}
-
-static void unveilConfig(const char *path) {
-	const char *dirs = NULL;
-	for (const char *abs; NULL != (abs = configPath(&dirs, path));) {
-		unveilParent(abs, "r");
-		unveilTarget(abs, "r");
-	}
-}
-
-static void unveilData(const char *path) {
-	const char *dirs = NULL;
-	for (const char *abs; NULL != (abs = dataPath(&dirs, path));) {
-		int error = unveil(abs, "rwc");
-		if (error && errno != ENOENT) err(EX_OSERR, "unveil");
-	}
-}
-#endif /* __OpenBSD__ */
-
 static size_t parseSize(const char *str) {
 	char *rest;
 	size_t size = strtoull(str, &rest, 0);
@@ -206,6 +152,8 @@ int main(int argc, char *argv[]) {
 	const char *genPath = NULL;
 
 	bool insecure = false;
+	bool printCert = false;
+	const char *trust = NULL;
 	const char *clientCert = NULL;
 	const char *clientPriv = NULL;
 	const char *serverBindHost = NULL;
@@ -219,6 +167,7 @@ int main(int argc, char *argv[]) {
 	const char *user = NULL;
 	const char *real = NULL;
 
+	const char *mode = NULL;
 	const char *join = NULL;
 	const char *quit = "connection reset by purr";
 
@@ -245,11 +194,14 @@ int main(int argc, char *argv[]) {
 		{ .val = 'h', .name = "host", required_argument },
 		{ .val = 'j', .name = "join", required_argument },
 		{ .val = 'k', .name = "client-priv", required_argument },
+		{ .val = 'm', .name = "mode", required_argument },
 		{ .val = 'n', .name = "nick", required_argument },
+		{ .val = 'o', .name = "print-cert", no_argument },
 		{ .val = 'p', .name = "port", required_argument },
 		{ .val = 'q', .name = "quit", required_argument },
 		{ .val = 'r', .name = "real", required_argument },
 		{ .val = 's', .name = "size", required_argument },
+		{ .val = 't', .name = "trust", required_argument },
 		{ .val = 'u', .name = "user", required_argument },
 		{ .val = 'v', .name = "verbose", no_argument },
 		{ .val = 'w', .name = "pass", required_argument },
@@ -287,42 +239,40 @@ int main(int argc, char *argv[]) {
 			break; case 'h': host = optarg;
 			break; case 'j': join = optarg;
 			break; case 'k': clientPriv = optarg;
+			break; case 'm': mode = optarg;
 			break; case 'n': nick = optarg;
+			break; case 'o': printCert = true;
 			break; case 'p': port = optarg;
 			break; case 'q': quit = optarg;
 			break; case 'r': real = optarg;
 			break; case 's': ringSize = parseSize(optarg);
+			break; case 't': trust = optarg;
 			break; case 'u': user = optarg;
-			break; case 'v': verbose = true;
+			break; case 'v': verbose = true; setlinebuf(stdout);
 			break; case 'w': pass = optarg;
 			break; case 'x': hashPass(); return EX_OK;
 			break; case 'y': clientAway = optarg;
 			break; default:  return EX_USAGE;
 		}
 	}
-	if (blindReq & CapUnsupported) errx(EX_CONFIG, "unsupported capability");
+	if (blindReq & CapUnsupported) errx(EX_USAGE, "unsupported capability");
 	if (genPath) genCert(genPath, caPath);
 
 	if (bindPath[0]) {
 		struct stat st;
 		int error = stat(bindPath, &st);
-		if (error && errno != ENOENT) err(EX_CANTCREAT, "%s", bindPath);
-		if (S_ISDIR(st.st_mode)) {
+		if (error) {
+			if (errno != ENOENT) err(EX_CANTCREAT, "%s", bindPath);
+		} else if (S_ISDIR(st.st_mode)) {
 			size_t len = strlen(bindPath);
 			snprintf(&bindPath[len], sizeof(bindPath) - len, "/%s", bindHost);
 		}
 	}
 	if (!certPath[0]) {
-		snprintf(
-			certPath, sizeof(certPath), CERTBOT_PATH "/live/%s/fullchain.pem",
-			bindHost
-		);
+		snprintf(certPath, sizeof(certPath), "%s.pem", bindHost);
 	}
 	if (!privPath[0]) {
-		snprintf(
-			privPath, sizeof(privPath), CERTBOT_PATH "/live/%s/privkey.pem",
-			bindHost
-		);
+		snprintf(privPath, sizeof(privPath), "%s.key", bindHost);
 	}
 
 	if (!host) errx(EX_USAGE, "host required");
@@ -334,24 +284,27 @@ int main(int argc, char *argv[]) {
 	if (!real) real = nick;
 	if (!clientAway) clientAway = "pounced :3";
 	if (clientPass && clientPass[0] != '$') {
-		errx(EX_CONFIG, "password must be hashed with -x");
+		errx(EX_USAGE, "password must be hashed with -x");
+	}
+	if (strchr(bindHost, '.')) {
+		clientOrigin = strdup(bindHost);
+		if (!clientOrigin) err(EX_OSERR, "strdup");
+	} else {
+		int n = asprintf(&clientOrigin, "%s.", bindHost);
+		if (n < 0) err(EX_OSERR, "asprintf");
 	}
 
+	if (printCert) {
 #ifdef __OpenBSD__
-	unveilConfig(certPath);
-	unveilConfig(privPath);
-	if (caPath) unveilConfig(caPath);
-	if (clientCert) unveilConfig(clientCert);
-	if (clientPriv) unveilConfig(clientPriv);
-	if (savePath) unveilData(savePath);
-	if (bindPath[0]) unveilParent(bindPath, "rwc");
-
-	error = unveil(tls_default_ca_cert_file(), "r");
-	if (error) err(EX_OSFILE, "%s", tls_default_ca_cert_file());
-
-	error = pledge("stdio rpath wpath cpath inet flock unix dns recvfd", NULL);
-	if (error) err(EX_OSERR, "pledge");
+		error = pledge("stdio inet dns", NULL);
+		if (error) err(EX_OSERR, "pledge");
 #endif
+		serverConfig(true, NULL, NULL, NULL);
+		serverConnect(serverBindHost, host, port);
+		serverPrintCert();
+		serverClose();
+		return EX_OK;
+	}
 
 	// Either exit with cleanup or ignore signals until entering the main loop.
 	signal(SIGINT, justExit);
@@ -361,78 +314,47 @@ int main(int argc, char *argv[]) {
 
 	ringAlloc(ringSize);
 	if (savePath) saveLoad(savePath);
+	serverConfig(insecure, trust, clientCert, clientPriv);
 
-	struct Cert localCA = { -1, -1, "" };
-	if (caPath) {
-		error = 0;
-		const char *dirs = NULL;
-		for (const char *path; NULL != (path = configPath(&dirs, caPath));) {
-			error = certOpen(&localCA, path);
-			if (!error) break;
+#ifdef __OpenBSD__
+	char buf[PATH_MAX];
+	const char *paths[] = { certPath, privPath, caPath };
+	for (size_t i = 0; i < ARRAY_LEN(paths); ++i) {
+		if (!paths[i]) continue;
+		for (int j = 0; configPath(buf, sizeof(buf), paths[i], j); ++j) {
+			error = unveil(buf, "r");
+			if (error && errno != ENOENT) err(EX_NOINPUT, "%s", buf);
 		}
-		if (error) err(EX_NOINPUT, "%s", caPath);
 	}
+	error = unveil(tls_default_ca_cert_file(), "r");
+	if (error) err(EX_OSFILE, "%s", tls_default_ca_cert_file());
 
-	const char *dirs;
-	struct Cert cert;
-	struct Cert priv;
-	dirs = NULL;
-	for (const char *path; NULL != (path = configPath(&dirs, certPath));) {
-		error = certOpen(&cert, path);
-		if (!error) break;
-	}
-	if (error) err(EX_NOINPUT, "%s", certPath);
-	dirs = NULL;
-	for (const char *path; NULL != (path = configPath(&dirs, privPath));) {
-		error = certOpen(&priv, path);
-		if (!error) break;
+	if (bindPath[0]) {
+		error = unveil(bindPath, "c");
+		if (error) err(EX_NOINPUT, "%s", bindPath);
+		error = pledge("stdio rpath inet dns cpath unix recvfd", NULL);
+	} else {
+		error = pledge("stdio rpath inet dns", NULL);
 	}
-	if (error) err(EX_NOINPUT, "%s", privPath);
-
-	FILE *certRead = certFile(&cert);
-	if (!certRead) err(EX_NOINPUT, "%s", certPath);
-	FILE *privRead = certFile(&priv);
-	if (!privRead) err(EX_NOINPUT, "%s", privPath);
-	FILE *caRead = (caPath ? certFile(&localCA) : NULL);
-	if (caPath && !caRead) err(EX_NOINPUT, "%s", caPath);
+	if (error) err(EX_OSERR, "pledge");
+#endif
 
-	localConfig(certRead, privRead, caRead, !clientPass);
-	fclose(certRead);
-	fclose(privRead);
-	if (caPath) fclose(caRead);
+	error = localConfig(certPath, privPath, caPath, !clientPass);
+	if (error) return EX_NOINPUT;
 
 	int bind[8];
 	size_t binds = bindPath[0]
 		? localUnix(bind, ARRAY_LEN(bind), bindPath)
 		: localBind(bind, ARRAY_LEN(bind), bindHost, bindPort);
-
-	serverConfig(insecure, clientCert, clientPriv);
 	int server = serverConnect(serverBindHost, host, port);
 
-#ifdef __FreeBSD__
-	error = cap_enter();
-	if (error) err(EX_OSERR, "cap_enter");
-
-	cap_rights_t saveRights, fileRights, sockRights, bindRights;
-	cap_rights_init(&saveRights, CAP_WRITE);
-	cap_rights_init(&fileRights, CAP_FCNTL, CAP_FSTAT, CAP_LOOKUP, CAP_PREAD);
-	cap_rights_init(&sockRights, CAP_EVENT, CAP_RECV, CAP_SEND, CAP_SETSOCKOPT);
-	cap_rights_init(&bindRights, CAP_LISTEN, CAP_ACCEPT);
-	cap_rights_merge(&bindRights, &sockRights);
-
-	if (saveFile) capLimit(fileno(saveFile), &saveRights);
-	capLimit(cert.parent, &fileRights);
-	capLimit(cert.target, &fileRights);
-	capLimit(priv.parent, &fileRights);
-	capLimit(priv.target, &fileRights);
-	if (caPath) {
-		capLimit(localCA.parent, &fileRights);
-		capLimit(localCA.target, &fileRights);
-	}
-	for (size_t i = 0; i < binds; ++i) {
-		capLimit(bind[i], &bindRights);
+#ifdef __OpenBSD__
+	if (bindPath[0]) {
+		error = pledge("stdio rpath cpath unix recvfd", NULL);
+	} else {
+		error = pledge("stdio rpath inet", NULL);
 	}
-	capLimit(server, &sockRights);
+	if (error) err(EX_OSERR, "pledge");
 #endif
 
 	stateLogin(pass, blindReq, plain, nick, user, real);
@@ -441,7 +363,8 @@ int main(int argc, char *argv[]) {
 
 	while (!stateReady()) serverRecv();
 	serverFormat("AWAY :%s\r\n", clientAway);
-	if (join) serverFormat("JOIN :%s\r\n", join);
+	if (mode) serverFormat("MODE %s %s\r\n", stateNick(), mode);
+	if (join) serverFormat("JOIN %s\r\n", join);
 
 	signal(SIGINT, signalHandler);
 	signal(SIGTERM, signalHandler);
@@ -456,120 +379,120 @@ int main(int argc, char *argv[]) {
 		eventAdd(bind[i], NULL);
 	}
 	eventAdd(server, NULL);
+	size_t clientIndex = event.len;
 
+	enum {
+		NeedTime = 10,
+		IdleTime = 15 * 60,
+	};
 	for (;;) {
-		for (size_t i = binds + 1; i < event.len; ++i) {
-			assert(event.clients[i]);
-			if (clientDiff(event.clients[i])) {
+		enum Need needs = 0;
+		time_t now = time(NULL);
+		for (size_t i = clientIndex; i < event.len; ++i) {
+			struct Client *client = event.clients[i];
+			event.fds[i].events = POLLIN;
+			needs |= client->need;
+			if (client->need) continue;
+			if (ringDiff(client->consumer) || now - client->idle >= IdleTime) {
 				event.fds[i].events |= POLLOUT;
-			} else {
-				event.fds[i].events &= ~POLLOUT;
 			}
 		}
 
-		int nfds = poll(event.fds, event.len, -1);
-		if (nfds < 0 && errno != EINTR) err(EX_IOERR, "poll");
+		int ready = poll(event.fds, event.len, (needs ? NeedTime * 1000 : -1));
+		if (ready < 0 && errno != EINTR) err(EX_IOERR, "poll");
+
+		if (needs) {
+			time_t now = time(NULL);
+			for (size_t i = event.len - 1; i >= clientIndex; --i) {
+				struct Client *client = event.clients[i];
+				if (!client->need) continue;
+				if (now - client->time < NeedTime) continue;
+				clientFree(client);
+				eventRemove(i);
+			}
+		}
 
-		for (size_t i = event.len - 1; nfds > 0 && i < event.len; --i) {
+		for (size_t i = event.len - 1; ready > 0 && i < event.len; --i) {
 			short revents = event.fds[i].revents;
 			if (!revents) continue;
 
-			if (event.fds[i].fd == server) {
+			struct Client *client = event.clients[i];
+			if (client) {
+				if (revents & POLLOUT) {
+					clientConsume(client);
+					if (now - client->idle >= IdleTime) {
+						clientFormat(client, "PING :%s\r\n", clientOrigin);
+					}
+				}
+				if (revents & POLLIN) clientRecv(client);
+				if (client->remove || revents & (POLLHUP | POLLERR)) {
+					clientFree(client);
+					eventRemove(i);
+				}
+			} else if (event.fds[i].fd == server) {
 				serverRecv();
-				continue;
-			}
-
-			if (!event.clients[i]) {
-				int fd;
-				struct tls *tls = localAccept(&fd, event.fds[i].fd);
-				if (!tls) {
+			} else {
+				struct tls *tls = NULL;
+				int sock = localAccept(&tls, event.fds[i].fd);
+				if (sock < 0) {
 					warn("accept");
 					continue;
 				}
-
-				error = tls_handshake(tls);
-				if (error) {
-					warnx("tls_handshake: %s", tls_error(tls));
-					tls_free(tls);
-					close(fd);
-				} else {
-					eventAdd(fd, clientAlloc(tls));
-				}
-				continue;
-			}
-
-			struct Client *client = event.clients[i];
-			if (revents & POLLOUT) clientConsume(client);
-			if (revents & POLLIN) clientRecv(client);
-			if (clientError(client) || revents & (POLLHUP | POLLERR)) {
-				clientFree(client);
-				eventRemove(i);
+				eventAdd(sock, clientAlloc(sock, tls));
 			}
 		}
 
-		if (signals[SIGINT] || signals[SIGTERM]) break;
-
+		if (clientQuit || signals[SIGINT] || signals[SIGTERM]) {
+			break;
+		}
 		if (signals[SIGALRM]) {
 			signals[SIGALRM] = 0;
 			serverDequeue();
 		}
-
 		if (signals[SIGINFO]) {
 			signals[SIGINFO] = 0;
 			ringInfo();
 		}
-
 		if (signals[SIGUSR1]) {
 			signals[SIGUSR1] = 0;
-			certRead = certFile(&cert);
-			if (!certRead) {
-				warn("%s", certPath);
-				continue;
-			}
-			privRead = certFile(&priv);
-			if (!privRead) {
-				warn("%s", privPath);
-				continue;
-			}
-			caRead = (caPath ? certFile(&localCA) : NULL);
-			if (caPath && !caRead) {
-				warn("%s", caPath);
-				continue;
-			}
-			localConfig(certRead, privRead, caRead, !clientPass);
-			fclose(certRead);
-			fclose(privRead);
-			if (caPath) fclose(caRead);
+			localConfig(certPath, privPath, caPath, !clientPass);
 		}
 	}
 
+	if (clientQuit && clientQuit[0]) quit = clientQuit;
 	serverFormat("QUIT :%s\r\n", quit);
-	for (size_t i = binds + 1; i < event.len; ++i) {
-		assert(event.clients[i]);
-		clientFormat(event.clients[i], ":%s QUIT :%s\r\n", stateEcho(), quit);
-		clientFormat(event.clients[i], "ERROR :Disconnecting\r\n");
-		clientFree(event.clients[i]);
-		close(event.fds[i].fd);
+	serverClose();
+	for (size_t i = clientIndex; i < event.len; ++i) {
+		struct Client *client = event.clients[i];
+		if (!client->need) {
+			clientFormat(client, ":%s QUIT :%s\r\n", stateEcho(), quit);
+			clientFormat(client, "ERROR :Disconnecting\r\n");
+		}
+		clientFree(client);
 	}
+	if (bindPath[0]) unlink(bindPath);
 }
 
 #ifdef __OpenBSD__
 static void hashPass(void) {
+	int error = pledge("stdio tty", NULL);
+	if (error) err(EX_OSERR, "pledge");
 	char hash[_PASSWORD_LEN];
 	char *pass = getpass("Password: ");
-	int error = crypt_newhash(pass, "bcrypt,a", hash, sizeof(hash));
+	error = crypt_newhash(pass, "bcrypt,a", hash, sizeof(hash));
 	if (error) err(EX_OSERR, "crypt_newhash");
 	printf("%s\n", hash);
 }
 #else
 static void hashPass(void) {
 	byte rand[12];
-	int error = getentropy(rand, sizeof(rand));
-	if (error) err(EX_OSERR, "getentropy");
-
+	FILE *file = fopen("/dev/urandom", "r");
+	if (!file) err(EX_OSFILE, "/dev/urandom");
+	size_t n = fread(rand, sizeof(rand), 1, file);
+	if (!n) err(EX_IOERR, "/dev/urandom");
+	fclose(file);
 	char salt[3 + BASE64_SIZE(sizeof(rand))] = "$6$";
 	base64(&salt[3], rand, sizeof(rand));
-
 	char *pass = getpass("Password: ");
 	printf("%s\n", crypt(pass, salt));
 }
@@ -599,8 +522,14 @@ static void genCert(const char *path, const char *ca) {
 	int out = open(path, O_WRONLY | O_APPEND | O_CREAT, 0600);
 	if (out < 0) err(EX_CANTCREAT, "%s", path);
 
+	int error;
+#ifdef __OpenBSD__
+	error = pledge("stdio proc exec", NULL);
+	if (error) err(EX_OSERR, "pledge");
+#endif
+
 	int rw[2];
-	int error = pipe(rw);
+	error = pipe(rw);
 	if (error) err(EX_OSERR, "pipe");
 
 	pid_t pid = fork();