Commit Diff


commit - e39d79e3c6d16ad1f982e9c9a8c2f99d368bd15c
commit + ff2b55acc0b4c0c704bfbafbb9ff1a74be3f49af
blob - 545a13d2e5aba6dc9df486f7300af0acf23c4df7
blob + 0505fbd1f8d0b37fded75218be702fa5b8fe4fab
--- gotwebd/Makefile
+++ gotwebd/Makefile
@@ -5,7 +5,7 @@
 .include "Makefile.inc"
 
 PROG =		gotwebd
-SRCS =		config.c sockets.c gotwebd.c parse.y \
+SRCS =		auth.c config.c sockets.c gotwebd.c parse.y \
 		fcgi.c gotweb.c got_operations.c tmpl.c pages.c
 SRCS +=		blame.c commit_graph.c delta.c diff.c \
 		diffreg.c error.c object.c object_cache.c \
blob - /dev/null
blob + c1c7d625060f9c5984c837f36bb35b671ad96ee9 (mode 644)
--- /dev/null
+++ gotwebd/auth.c
@@ -0,0 +1,512 @@
+/*
+ * Copyright (c) 2025 Stefan Sperling <stsp@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <signal.h>
+#include <siphash.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "got_error.h"
+#include "got_reference.h"
+
+#include "gotwebd.h"
+#include "log.h"
+
+#define AUTH_SOCKET_BACKLOG 4
+
+struct gotwebd_auth_client {
+	TAILQ_ENTRY(gotwebd_auth_client) entry;
+	uint32_t id;
+	int fd;
+	uid_t euid;
+	struct imsgev iev;
+};
+TAILQ_HEAD(gotwebd_auth_clients, gotwebd_auth_client);
+
+static struct gotwebd_auth_clients clients[GOTWEBD_MAXCLIENTS * 4];
+static SIPHASH_KEY clients_hash_key;
+static volatile int client_cnt;
+static int inflight;
+
+static void
+clients_init(void)
+{
+	uint64_t slot;
+
+	arc4random_buf(&clients_hash_key, sizeof(clients_hash_key));
+
+	for (slot = 0; slot < nitems(clients); slot++)
+		TAILQ_INIT(&clients[slot]);
+}
+
+static uint64_t
+client_hash(uint32_t client_id)
+{
+	return SipHash24(&clients_hash_key, &client_id, sizeof(client_id));
+}
+
+static void
+add_client(struct gotwebd_auth_client *client)
+{
+	uint64_t slot = client_hash(client->id) % nitems(clients);
+	TAILQ_INSERT_HEAD(&clients[slot], client, entry);
+	client_cnt++;
+}
+
+static struct gotwebd_auth_client *
+find_client(uint32_t client_id)
+{
+	uint64_t slot;
+	struct gotwebd_auth_client *c;
+
+	slot = client_hash(client_id) % nitems(clients);
+	TAILQ_FOREACH(c, &clients[slot], entry) {
+		if (c->id == client_id)
+			return c;
+	}
+
+	return NULL;
+}
+
+static uint32_t
+get_client_id(void)
+{
+	int duplicate = 0;
+	uint32_t id;
+
+	do {
+		id = arc4random();
+		duplicate = (find_client(id) != NULL);
+	} while (duplicate || id == 0);
+
+	return id;
+}
+
+static int
+auth_socket_listen(struct gotwebd *env, struct socket *sock,
+    uid_t uid, gid_t gid)
+{
+	int u_fd = -1;
+	mode_t old_umask, mode;
+
+	u_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK| SOCK_CLOEXEC, 0);
+	if (u_fd == -1) {
+		log_warn("%s: socket", __func__);
+		return -1;
+	}
+
+	if (unlink(sock->conf.unix_socket_name) == -1) {
+		if (errno != ENOENT) {
+			log_warn("%s: unlink %s", __func__,
+			    sock->conf.unix_socket_name);
+			close(u_fd);
+			return -1;
+		}
+	}
+
+	old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
+	mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
+
+	if (bind(u_fd, (struct sockaddr *)&sock->conf.addr.ss,
+	    sock->conf.addr.slen) == -1) {
+		log_warn("%s: bind: %s", __func__, sock->conf.unix_socket_name);
+		close(u_fd);
+		(void)umask(old_umask);
+		return -1;
+	}
+
+	(void)umask(old_umask);
+
+	if (chmod(sock->conf.unix_socket_name, mode) == -1) {
+		log_warn("%s: chmod", __func__);
+		close(u_fd);
+		(void)unlink(sock->conf.unix_socket_name);
+		return -1;
+	}
+
+	if (chown(sock->conf.unix_socket_name, uid, gid) == -1) {
+		log_warn("%s: chown", __func__);
+		close(u_fd);
+		(void)unlink(sock->conf.unix_socket_name);
+		return -1;
+	}
+
+	if (listen(u_fd, AUTH_SOCKET_BACKLOG) == -1) {
+		log_warn("%s: listen", __func__);
+		return -1;
+	}
+
+	return u_fd;
+}
+
+int
+auth_privinit(struct gotwebd *env, uid_t uid, gid_t gid)
+{
+	struct socket *sock = env->auth_sock;
+
+	if (sock == NULL)
+		fatalx("no authentication socket configured");
+
+	log_info("initializing authentication socket %s",
+	    sock->conf.unix_socket_name);
+
+	sock->fd = auth_socket_listen(env, sock, uid, gid);
+	if (sock->fd == -1)
+		return -1;
+
+	return 0;
+}
+
+static void
+auth_shutdown(void)
+{
+	struct gotwebd *env = gotwebd_env;
+
+	imsgbuf_clear(&env->iev_parent->ibuf);
+	free(env->iev_parent);
+	if (env->iev_auth) {
+		imsgbuf_clear(&env->iev_auth->ibuf);
+		free(env->iev_auth);
+	}
+	free(env);
+
+	exit(0);
+}
+
+static void
+auth_sighdlr(int sig, short event, void *arg)
+{
+	switch (sig) {
+	case SIGHUP:
+		log_info("%s: ignoring SIGHUP", __func__);
+		break;
+	case SIGPIPE:
+		log_info("%s: ignoring SIGPIPE", __func__);
+		break;
+	case SIGUSR1:
+		log_info("%s: ignoring SIGUSR1", __func__);
+		break;
+	case SIGCHLD:
+		break;
+	case SIGINT:
+	case SIGTERM:
+		auth_shutdown();
+		break;
+	default:
+		log_warn("unexpected signal %d", sig);
+		break;
+	}
+}
+
+static int
+accept_reserve(int fd, struct sockaddr *addr, socklen_t *addrlen,
+    int reserve, volatile int *counter)
+{
+	int ret;
+
+	if (getdtablecount() + reserve +
+	    ((*counter + 1) * FD_NEEDED) >= getdtablesize()) {
+		log_debug("inflight fds exceeded");
+		errno = EMFILE;
+		return -1;
+	}
+
+	if ((ret = accept4(fd, addr, addrlen,
+	    SOCK_NONBLOCK | SOCK_CLOEXEC)) > -1) {
+		(*counter)++;
+	}
+
+	return ret;
+}
+
+static const struct got_error *
+disconnect(struct gotwebd_auth_client *client)
+{
+	uint64_t slot;
+	int client_fd;
+
+	log_debug("client on fd %d disconnecting", client->fd);
+
+	slot = client_hash(client->id) % nitems(clients);
+	TAILQ_REMOVE(&clients[slot], client, entry);
+
+	if (client->iev.ibuf.fd != -1)
+		imsgbuf_clear(&client->iev.ibuf);
+
+	client_fd = client->fd;
+	free(client);
+	inflight--;
+	client_cnt--;
+	if (close(client_fd) == -1)
+		return got_error_from_errno("close");
+
+	return NULL;
+}
+
+static void
+auth_request(int fd, short event, void *arg)
+{
+	log_warnx("%s", __func__);
+}
+
+static void
+auth_accept(int fd, short event, void *arg)
+{
+	struct imsgev *iev = arg;
+	struct gotwebd *env = gotwebd_env;
+	struct sockaddr_storage ss;
+	struct timeval backoff;
+	socklen_t len;
+	int s = -1;
+	struct gotwebd_auth_client *client = NULL;
+	uid_t euid;
+	gid_t egid;
+
+	backoff.tv_sec = 1;
+	backoff.tv_usec = 0;
+
+	if (event_add(&iev->ev, NULL) == -1) {
+		log_warn("event_add");
+		return;
+	}
+	if (event & EV_TIMEOUT)
+		return;
+
+	len = sizeof(ss);
+
+	/* Other backoff conditions apart from EMFILE/ENFILE? */
+	s = accept_reserve(fd, (struct sockaddr *)&ss, &len, FD_RESERVE,
+	    &inflight);
+	if (s == -1) {
+		switch (errno) {
+		case EINTR:
+		case EWOULDBLOCK:
+		case ECONNABORTED:
+			return;
+		case EMFILE:
+		case ENFILE:
+			event_del(&iev->ev);
+			evtimer_add(&env->auth_pause_ev, &backoff);
+			return;
+		default:
+			log_warn("accept");
+			return;
+		}
+	}
+
+	if (client_cnt >= GOTWEBD_MAXCLIENTS)
+		goto err;
+
+	if (getpeereid(s, &euid, &egid) == -1) {
+		log_warn("getpeerid");
+		goto err;
+	}
+
+	client = calloc(1, sizeof(*client));
+	if (client == NULL) {
+		log_warn("%s: calloc", __func__);
+		goto err;
+	}
+	client->iev.ibuf.fd = -1;
+	client->id = get_client_id();
+	client->fd = s;
+	client->euid = euid;
+	s = -1;
+
+	if (imsgbuf_init(&client->iev.ibuf, client->fd) == -1) {
+		log_warn("imsgbuf_init");
+		goto err;
+	}
+
+	client->iev.handler = auth_request;;
+	client->iev.data = client;
+	event_set(&client->iev.ev, client->fd, EV_READ, auth_request, client);
+	event_add(&client->iev.ev, NULL);
+
+	add_client(client);
+
+	log_debug("%s: new client connected on fd %d uid %d gid %d", __func__,
+	    client->fd, euid, egid);
+	return;
+err:
+	inflight--;
+	if (client)
+		disconnect(client);
+	if (s != -1)
+		close(s);
+}
+
+
+static void
+get_auth_sock(struct gotwebd *env, struct imsg *imsg)
+{
+	const struct got_error *err;
+	struct imsgev *iev;
+	int fd;
+
+	if (env->iev_auth != NULL) {
+		err = got_error(GOT_ERR_PRIVSEP_MSG);
+		fatalx("%s", err->msg);
+	}
+
+	if (IMSG_DATA_SIZE(imsg) != 0) {
+		err = got_error(GOT_ERR_PRIVSEP_LEN);
+		fatalx("%s", err->msg);
+	}
+
+	fd = imsg_get_fd(imsg);
+	if (fd == -1) {
+		err = got_error(GOT_ERR_PRIVSEP_NO_FD);
+		fatalx("%s", err->msg);
+	}
+
+	iev = calloc(1, sizeof(*iev));
+	if (iev == NULL)
+		fatal("calloc");
+
+	if (imsgbuf_init(&iev->ibuf, fd) == -1)
+		fatal("imsgbuf_init");
+
+	iev->handler = auth_accept;
+	iev->data = iev;
+	event_set(&iev->ev, fd, EV_READ, auth_accept, iev);
+	imsg_event_add(iev);
+
+	env->iev_auth = iev;
+}
+
+static void
+auth_launch(struct gotwebd *env)
+{
+#ifndef PROFILE
+	if (pledge("stdio unix", NULL) == -1)
+		fatal("pledge");
+#endif
+	event_add(&env->iev_auth->ev, NULL);
+}
+
+static void
+auth_dispatch_main(int fd, short event, void *arg)
+{
+	struct imsgev		*iev = arg;
+	struct imsgbuf		*ibuf;
+	struct imsg		 imsg;
+	struct gotwebd		*env = gotwebd_env;
+	ssize_t			 n;
+	int			 shut = 0;
+
+	ibuf = &iev->ibuf;
+
+	if (event & EV_READ) {
+		if ((n = imsgbuf_read(ibuf)) == -1)
+			fatal("imsgbuf_read error");
+		if (n == 0)	/* Connection closed */
+			shut = 1;
+	}
+	if (event & EV_WRITE) {
+		if (imsgbuf_write(ibuf) == -1)
+			fatal("imsgbuf_write");
+	}
+
+	for (;;) {
+		if ((n = imsg_get(ibuf, &imsg)) == -1)
+			fatal("imsg_get");
+		if (n == 0)	/* No more messages. */
+			break;
+
+		switch (imsg.hdr.type) {
+		case GOTWEBD_IMSG_CFG_SOCK:
+			get_auth_sock(env, &imsg);
+			break;
+		case GOTWEBD_IMSG_CTL_START:
+			auth_launch(env);
+			break;
+		default:
+			fatalx("%s: unknown imsg type %d", __func__,
+			    imsg.hdr.type);
+		}
+
+		imsg_free(&imsg);
+	}
+
+	if (!shut)
+		imsg_event_add(iev);
+	else {
+		/* This pipe is dead.  Remove its event handler */
+		event_del(&iev->ev);
+		event_loopexit(NULL);
+	}
+}
+
+static void
+accept_paused(int fd, short event, void *arg)
+{
+	struct gotwebd *env = gotwebd_env;
+
+	event_add(&env->iev_auth->ev, NULL);
+}
+
+void
+gotwebd_auth(struct gotwebd *env, int fd)
+{
+	struct event	 sighup, sigint, sigusr1, sigchld, sigterm;
+	struct event_base *evb;
+
+	clients_init();
+	evb = event_init();
+
+	if ((env->iev_parent = malloc(sizeof(*env->iev_parent))) == NULL)
+		fatal("malloc");
+	if (imsgbuf_init(&env->iev_parent->ibuf, fd) == -1)
+		fatal("imsgbuf_init");
+	imsgbuf_allow_fdpass(&env->iev_parent->ibuf);
+	env->iev_parent->handler = auth_dispatch_main;
+	env->iev_parent->data = env->iev_parent;
+	event_set(&env->iev_parent->ev, fd, EV_READ, auth_dispatch_main,
+	    env->iev_parent);
+	event_add(&env->iev_parent->ev, NULL);
+	evtimer_set(&env->auth_pause_ev, accept_paused, NULL);
+
+	signal(SIGPIPE, SIG_IGN);
+
+	signal_set(&sighup, SIGHUP, auth_sighdlr, env);
+	signal_add(&sighup, NULL);
+	signal_set(&sigint, SIGINT, auth_sighdlr, env);
+	signal_add(&sigint, NULL);
+	signal_set(&sigusr1, SIGUSR1, auth_sighdlr, env);
+	signal_add(&sigusr1, NULL);
+	signal_set(&sigchld, SIGCHLD, auth_sighdlr, env);
+	signal_add(&sigchld, NULL);
+	signal_set(&sigterm, SIGTERM, auth_sighdlr, env);
+	signal_add(&sigterm, NULL);
+
+#ifndef PROFILE
+	if (pledge("stdio recvfd unix", NULL) == -1)
+		fatal("pledge");
+#endif
+
+	event_dispatch();
+	event_base_free(evb);
+	auth_shutdown();
+}
blob - f66b9f38c71db3ea73f08f317e7446e2d3929e59
blob + 3d8bc92a0b02b0b2082667549e4fe43c6c1d7d8e
--- gotwebd/gotwebd.c
+++ gotwebd/gotwebd.c
@@ -52,6 +52,7 @@ int	 gotwebd_configure(struct gotwebd *, uid_t, gid_t)
 void	 gotwebd_configure_done(struct gotwebd *);
 void	 gotwebd_sighdlr(int sig, short event, void *arg);
 void	 gotwebd_shutdown(void);
+void	 gotwebd_dispatch_auth(int, short, void *);
 void	 gotwebd_dispatch_server(int, short, void *);
 void	 gotwebd_dispatch_gotweb(int, short, void *);
 
@@ -148,10 +149,63 @@ main_compose_gotweb(struct gotwebd *env, uint32_t type
 }
 
 int
+main_compose_auth(struct gotwebd *env, uint32_t type, int fd,
+    const void *data, uint16_t len)
+{
+	return send_imsg(env->iev_auth, type, fd, data, len);
+}
+
+int
 sockets_compose_main(struct gotwebd *env, uint32_t type, const void *d,
     uint16_t len)
 {
 	return (imsg_compose_event(env->iev_parent, type, 0, -1, -1, d, len));
+}
+
+void
+gotwebd_dispatch_auth(int fd, short event, void *arg)
+{
+	struct imsgev		*iev = arg;
+	struct imsgbuf		*ibuf;
+	struct imsg		 imsg;
+	ssize_t			 n;
+	int			 shut = 0;
+
+	ibuf = &iev->ibuf;
+
+	if (event & EV_READ) {
+		if ((n = imsgbuf_read(ibuf)) == -1)
+			fatal("imsgbuf_read error");
+		if (n == 0)	/* Connection closed */
+			shut = 1;
+	}
+	if (event & EV_WRITE) {
+		if (imsgbuf_write(ibuf) == -1)
+			fatal("imsgbuf_write");
+	}
+
+	for (;;) {
+		if ((n = imsg_get(ibuf, &imsg)) == -1)
+			fatal("imsg_get");
+		if (n == 0)	/* No more messages. */
+			break;
+
+		switch (imsg.hdr.type) {
+		default:
+			fatalx("%s: unknown imsg type %d", __func__,
+			    imsg.hdr.type);
+		}
+
+		imsg_free(&imsg);
+	}
+
+	if (!shut)
+		imsg_event_add(iev);
+	else {
+		/* This pipe is dead.  Remove its event handler */
+		event_del(&iev->ev);
+		event_loopexit(NULL);
+	}
 }
 
 void
@@ -312,7 +366,10 @@ spawn_process(struct gotwebd *env, const char *argv0, 
 	close(p[1]);
 
 	argv[argc++] = argv0;
-	if (proc_type == GOTWEBD_PROC_SERVER) {
+	if (proc_type == GOTWEBD_PROC_AUTH) {
+		argv[argc++] = "-A";
+		argv[argc++] = username;
+	} else if (proc_type == GOTWEBD_PROC_SERVER) {
 		argv[argc++] = "-S";
 		argv[argc++] = username;
 	} else if (proc_type == GOTWEBD_PROC_GOTWEB) {
@@ -377,8 +434,12 @@ main(int argc, char **argv)
 		fatal("%s: calloc", __func__);
 	config_init(env);
 
-	while ((ch = getopt(argc, argv, "D:dG:f:nS:vW:")) != -1) {
+	while ((ch = getopt(argc, argv, "A:D:dG:f:nS:vW:")) != -1) {
 		switch (ch) {
+		case 'A':
+			proc_type = GOTWEBD_PROC_AUTH;
+			gotwebd_username = optarg;
+			break;
 		case 'D':
 			if (cmdline_symset(optarg) < 0)
 				log_warnx("could not parse macro definition %s",
@@ -449,6 +510,17 @@ main(int argc, char **argv)
 	log_setverbose(env->gotwebd_verbose);
 
 	switch (proc_type) {
+	case GOTWEBD_PROC_AUTH:
+		setproctitle("auth");
+		log_procinit("auth");
+
+		if (setgroups(1, &pw->pw_gid) == -1 ||
+		    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1 ||
+		    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
+			fatal("failed to drop privileges");
+
+		gotwebd_auth(env, GOTWEBD_SOCK_FILENO);
+		return 1;
 	case GOTWEBD_PROC_SERVER:
 		setproctitle("server");
 		log_procinit("server");
@@ -485,6 +557,9 @@ main(int argc, char **argv)
 
 	evb = event_init();
 
+	env->iev_auth = calloc(1, sizeof(*env->iev_auth));
+	if (env->iev_auth == NULL)
+		fatal("calloc");
 	env->nserver = env->prefork_gotwebd;
 	env->iev_server = calloc(env->nserver, sizeof(*env->iev_server));
 	if (env->iev_server == NULL)
@@ -493,6 +568,9 @@ main(int argc, char **argv)
 	if (env->iev_gotweb == NULL)
 		fatal("calloc");
 
+	spawn_process(env, argv0, env->iev_auth, GOTWEBD_PROC_AUTH,
+	    gotwebd_username, gotwebd_dispatch_auth);
+
 	for (i = 0; i < env->nserver; ++i) {
 		spawn_process(env, argv0, &env->iev_server[i],
 		    GOTWEBD_PROC_SERVER, gotwebd_username,
@@ -603,6 +681,12 @@ gotwebd_configure(struct gotwebd *env, uid_t uid, gid_
 			fatalx("%s: send socket error", __func__);
 	}
 
+	if (auth_privinit(env, uid, gid) == -1)
+		fatalx("cannot open authentication socket");
+	if (main_compose_auth(env, GOTWEBD_IMSG_CFG_SOCK, env->auth_sock->fd,
+	    NULL, 0) == -1)
+		fatal("main_compose_auth GOTWEBD_IMSG_CFG_SOCK");
+
 	/* send the temp files */
 	if (config_setfd(env) == -1)
 		fatalx("%s: send priv_fd error", __func__);
@@ -634,6 +718,12 @@ gotwebd_configure_done(struct gotwebd *env)
 		        -1, NULL, 0) == -1)
 			fatal("main_compose_sockets GOTWEBD_IMSG_CTL_START");
 	}
+	
+	if (env->servers_pending == 0 && env->gotweb_pending == 0) {
+		if (main_compose_auth(env, GOTWEBD_IMSG_CTL_START,
+			-1, NULL, 0) == -1)
+			fatal("main_compose_auth GOTWEBD_IMSG_CTL_START");
+	}
 }
 
 void
@@ -643,6 +733,12 @@ gotwebd_shutdown(void)
 	pid_t		 pid;
 	int		 i, status;
 
+	event_del(&env->iev_auth->ev);
+	imsgbuf_clear(&env->iev_auth->ibuf);
+	close(env->iev_auth->ibuf.fd);
+	env->iev_auth->ibuf.fd = -1;
+	free(env->iev_auth);
+
 	for (i = 0; i < env->nserver; ++i) {
 		event_del(&env->iev_server[i].ev);
 		imsgbuf_clear(&env->iev_server[i].ibuf);
blob - 562d13bf30399506f8f4f2c9eca0d103cf6e5cdd
blob + e26ec9ead0d150423a8c572c43c158099b606307
--- gotwebd/gotwebd.conf.5
+++ gotwebd/gotwebd.conf.5
@@ -47,6 +47,16 @@ listen on $lan_addr port 9090
 .Sh GLOBAL CONFIGURATION
 The available global configuration directives are as follows:
 .Bl -tag -width Ds
+.It Ic authentication socket Ar path
+Set the
+.Ar path
+to the
+.Ux Ns -domain
+socket for user authentication requests from
+.Xr gotsh 1 .
+By default the path
+.Pa /var/run/gotweb-auth.sock
+will be used.
 .It Ic chroot Ar path
 Set the path to the
 .Xr chroot 2
blob - 432a79667a3d6deab012c3bd82c9d857be510a0f
blob + 48619aa37f78c35dc4c873b7d3d8ea3c0ae218c8
--- gotwebd/gotwebd.h
+++ gotwebd/gotwebd.h
@@ -127,6 +127,7 @@ struct got_reflist_head;
 
 enum gotwebd_proc_type {
 	GOTWEBD_PROC_PARENT,
+	GOTWEBD_PROC_AUTH,
 	GOTWEBD_PROC_SERVER,
 	GOTWEBD_PROC_GOTWEB,
 };
@@ -138,6 +139,7 @@ enum imsg_type {
 	GOTWEBD_IMSG_CFG_DONE,
 	GOTWEBD_IMSG_CTL_PIPE,
 	GOTWEBD_IMSG_CTL_START,
+	GOTWEBD_IMSG_AUTH_SOCK,
 	GOTWEBD_IMSG_REQ_PROCESS,
 	GOTWEBD_IMSG_REQ_DONE,
 };
@@ -361,6 +363,9 @@ struct gotwebd {
 	struct socketlist	sockets;
 	struct addresslist	addresses;
 
+	struct socket	*auth_sock;
+	struct event	 auth_pause_ev;
+
 	int		 pack_fds[GOTWEB_PACK_NUM_TEMPFILES];
 	int		 priv_fd[PRIV_FDS__MAX];
 
@@ -372,6 +377,7 @@ struct gotwebd {
 	int		 gotwebd_verbose;
 
 	struct imsgev	*iev_parent;
+	struct imsgev	*iev_auth;
 	struct imsgev	*iev_server;
 	struct imsgev	*iev_gotweb;
 	size_t		 nserver;
@@ -460,11 +466,14 @@ int	 main_compose_sockets(struct gotwebd *, uint32_t, 
 int	 sockets_compose_main(struct gotwebd *, uint32_t,
 	    const void *, uint16_t);
 int	 main_compose_gotweb(struct gotwebd *, uint32_t, int,
+	    const void *, uint16_t);
+int	 main_compose_auth(struct gotwebd *, uint32_t, int,
 	    const void *, uint16_t);
 
 /* sockets.c */
 void sockets(struct gotwebd *, int);
 void sockets_parse_sockets(struct gotwebd *);
+struct socket *sockets_conf_new_socket(int, struct address *);
 void sockets_socket_accept(int, short, void *);
 int sockets_privinit(struct gotwebd *, struct socket *, uid_t, gid_t);
 void sockets_purge(struct gotwebd *);
@@ -543,5 +552,6 @@ int config_getfd(struct gotwebd *, struct imsg *);
 int config_getcfg(struct gotwebd *, struct imsg *);
 int config_init(struct gotwebd *);
 
-/* ../lib/gotweb_auth.c */
-const struct got_error * gotweb_auth(int, int, int);
+/* auth.c */
+void gotwebd_auth(struct gotwebd *, int);
+int auth_privinit(struct gotwebd *env, uid_t, gid_t);
blob - 28cc307a5e9e04f189781f0f3c7c189a87700a17
blob + 016d8a449957831fcb0a45014636a91590ff80fc
--- gotwebd/parse.y
+++ gotwebd/parse.y
@@ -96,7 +96,7 @@ int				 getservice(const char *);
 int				 n;
 
 int		 get_addrs(const char *, const char *);
-int		 get_unix_addr(const char *);
+static struct address	*get_unix_addr(const char *);
 int		 addr_dup_check(struct addresslist *, struct address *);
 void		 add_addr(struct address *);
 
@@ -115,7 +115,7 @@ typedef struct {
 %token	MAX_REPOS_DISPLAY REPOS_PATH MAX_COMMITS_DISPLAY ON ERROR
 %token	SHOW_SITE_OWNER SHOW_REPO_CLONEURL PORT PREFORK RESPECT_EXPORTOK
 %token	SERVER CHROOT CUSTOM_CSS SOCKET
-%token	SUMMARY_COMMITS_DISPLAY SUMMARY_TAGS_DISPLAY USER
+%token	SUMMARY_COMMITS_DISPLAY SUMMARY_TAGS_DISPLAY USER AUTHENTICATION
 
 %token	<v.string>	STRING
 %token	<v.number>	NUMBER
@@ -234,11 +234,14 @@ main		: PREFORK NUMBER {
 			free($3);
 		}
 		| LISTEN ON SOCKET STRING {
-			if (get_unix_addr($4) == -1) {
+			struct address *h;
+			h = get_unix_addr($4);
+			if (h == NULL) {
 				yyerror("can't listen on %s", $4);
 				free($4);
 				YYERROR;
 			}
+			add_addr(h);
 			free($4);
 		}
 		| USER STRING {
@@ -252,6 +255,19 @@ main		: PREFORK NUMBER {
 				yyerror("www user already specified");
 			free(gotwebd->www_user);
 			gotwebd->www_user = $3;
+		}
+		| AUTHENTICATION SOCKET STRING {
+			struct address *h;
+			h = get_unix_addr($3);
+			if (h == NULL) {
+				yyerror("can't listen on %s", $3);
+				free($3);
+				YYERROR;
+			}
+			if (gotwebd->auth_sock != NULL)
+				free(gotwebd->auth_sock);
+			gotwebd->auth_sock = sockets_conf_new_socket(-1, h);
+			free($3);
 		}
 		;
 
@@ -454,6 +470,7 @@ lookup(char *s)
 {
 	/* This has to be sorted always. */
 	static const struct keywords keywords[] = {
+		{ "authentication",		AUTHENTICATION },
 		{ "chroot",			CHROOT },
 		{ "custom_css",			CUSTOM_CSS },
 		{ "listen",			LISTEN },
@@ -845,6 +862,7 @@ parse_config(const char *filename, struct gotwebd *env
 
 	/* add the implicit listen on socket */
 	if (TAILQ_EMPTY(&gotwebd->addresses)) {
+		struct address *h;
 		char path[_POSIX_PATH_MAX];
 
 		if (strlcpy(path, gotwebd->httpd_chroot, sizeof(path))
@@ -857,8 +875,10 @@ parse_config(const char *filename, struct gotwebd *env
 			yyerror("chroot path too long: %s",
 			    gotwebd->httpd_chroot);
 		}
-		if (get_unix_addr(path) == -1)
+		h = get_unix_addr(path);
+		if (h == NULL)
 			yyerror("can't listen on %s", path);
+		add_addr(h);
 	}
 
 	if (errors)
@@ -866,6 +886,18 @@ parse_config(const char *filename, struct gotwebd *env
 
 	/* setup our listening sockets */
 	sockets_parse_sockets(env);
+
+	/* Add implicit auth socket */
+	if (gotwebd->auth_sock == NULL) {
+		struct address *h;
+		h = get_unix_addr(GOTWEBD_AUTH_SOCKET);
+		if (h == NULL) {
+			fprintf(stderr, "cannot listen on %s",
+			    GOTWEBD_AUTH_SOCKET);
+			return (-1);
+		}
+		gotwebd->auth_sock = sockets_conf_new_socket(-1, h);
+	}
 
 	return (0);
 }
@@ -1068,7 +1100,7 @@ get_addrs(const char *hostname, const char *servname)
 	return (0);
 }
 
-int
+static struct address *
 get_unix_addr(const char *path)
 {
 	struct address *h;
@@ -1087,11 +1119,10 @@ get_unix_addr(const char *path)
 	if (strlcpy(sun->sun_path, path, sizeof(sun->sun_path)) >=
 	    sizeof(sun->sun_path)) {
 		log_warnx("socket path too long: %s", sun->sun_path);
-		return (-1);
+		return NULL;
 	}
 
-	add_addr(h);
-	return (0);
+	return h;
 }
 
 int
blob - 78ec58685de17ce8a866d30f36a5597b2d6f85da
blob + 66d8e76df0aa38dd5b4c508d577b4b6fa7039ba4
--- gotwebd/sockets.c
+++ gotwebd/sockets.c
@@ -74,9 +74,6 @@ static int	 sockets_create_socket(struct address *);
 static int	 sockets_accept_reserve(int, struct sockaddr *, socklen_t *,
 		    int, volatile int *);
 
-static struct socket *sockets_conf_new_socket(struct gotwebd *,
-		    int, struct address *);
-
 int cgi_inflight = 0;
 
 /* Request hash table needs some spare room to avoid collisions. */
@@ -213,7 +210,7 @@ sockets_parse_sockets(struct gotwebd *env)
 	int sock_id = 1;
 
 	TAILQ_FOREACH(a, &env->addresses, entry) {
-		new_sock = sockets_conf_new_socket(env, sock_id, a);
+		new_sock = sockets_conf_new_socket(sock_id, a);
 		if (new_sock) {
 			sock_id++;
 			TAILQ_INSERT_TAIL(&env->sockets,
@@ -222,8 +219,8 @@ sockets_parse_sockets(struct gotwebd *env)
 	}
 }
 
-static struct socket *
-sockets_conf_new_socket(struct gotwebd *env, int id, struct address *a)
+struct socket *
+sockets_conf_new_socket(int id, struct address *a)
 {
 	struct socket *sock;
 	struct address *acp;