Commit Diff


commit - 6490a1e911df9c4f629efb6a0c6a24ab9dc392dc
commit + 202d187071957eec318c523e66455e0f804ff869
blob - ac01cf603e2090b0c97aa789ce265be2acd8eda7
blob + 815ba754b8247de6076160659c86feb748fab436
--- gotd/gotd.c
+++ gotd/gotd.c
@@ -301,6 +301,9 @@ proc_done(struct gotd_child_proc *proc)
 		disconnect(client);
 	}
 
+	if (proc == gotd.notify_proc)
+		gotd.notify_proc = NULL;
+
 	evtimer_del(&proc->tmo);
 
 	if (proc->iev.ibuf.fd != -1) {
@@ -1179,9 +1182,7 @@ done:
 		 */
 		log_warnx("notify child process (pid %d) closed its imsg pipe "
 		    "unexpectedly", proc->pid);
-		gotd.notify_proc = NULL;
-		imsg_clear(&iev->ibuf);
-		free(proc);
+		proc_done(proc);
 	}
 }
 
@@ -1479,26 +1480,36 @@ done:
 }
 
 static const struct got_error *
-connect_notifier(struct gotd_client *client)
+connect_notifier_and_session(struct gotd_client *client)
 {
 	const struct got_error *err = NULL;
-	int s;
+	struct gotd_imsgev *session_iev = &client->session->iev;
+	int pipe[2];
 
-	if (gotd.notify_proc) {
-		s = dup(gotd.notify_proc->pipe[0]);
-		if (s == -1)
-			return got_error_from_errno("dup");
-	} else
-		s = -1;
+	if (gotd.notify_proc == NULL)
+		return NULL;
 
-	if (gotd_imsg_compose_event(&client->session->iev,
-	    GOTD_IMSG_CONNECT_NOTIFIER, PROC_GOTD, s, NULL, 0) == -1) {
+	if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK,
+	    PF_UNSPEC, pipe) == -1)
+		return got_error_from_errno("socketpair");
+
+	/* Pass notifier pipe to session . */
+	if (gotd_imsg_compose_event(session_iev, GOTD_IMSG_CONNECT_NOTIFIER,
+	    PROC_GOTD, pipe[0], NULL, 0) == -1) {
 		err = got_error_from_errno("imsg compose CONNECT_NOTIFIER");
-		if (s != -1)
-			close(s);
+		close(pipe[0]);
+		close(pipe[1]);
 		return err;
 	}
 
+	/* Pass session pipe to notifier. */
+	if (gotd_imsg_compose_event(&gotd.notify_proc->iev,
+	    GOTD_IMSG_CONNECT_SESSION, PROC_GOTD, pipe[1], NULL, 0) == -1) {
+		err = got_error_from_errno("imsg compose CONNECT_SESSION");
+		close(pipe[1]);
+		return err;
+	}
+
 	return NULL;
 }
 
@@ -1565,7 +1576,7 @@ gotd_dispatch_repo_child(int fd, short event, void *ar
 			err = connect_session(client);
 			if (err)
 				break;
-			err = connect_notifier(client);
+			err = connect_notifier_and_session(client);
 			if (err)
 				break;
 			err = connect_repo_child(client, proc);
@@ -1728,6 +1739,8 @@ start_notifier(char *argv0, const char *confpath, int 
 	proc->iev.handler = gotd_dispatch_notifier;
 	proc->iev.events = EV_READ;
 	proc->iev.handler_arg = NULL;
+	event_set(&proc->iev.ev, proc->iev.ibuf.fd, EV_READ,
+	    gotd_dispatch_notifier, &proc->iev);
 
 	gotd.notify_proc = proc;
 }
@@ -2219,7 +2232,7 @@ main(int argc, char **argv)
 		exit(0);
 	case PROC_NOTIFY:
 #ifndef PROFILE
-		if (pledge("stdio proc exec unveil", NULL) == -1)
+		if (pledge("stdio proc exec recvfd unveil", NULL) == -1)
 			err(1, "pledge");
 #endif
 		/*
@@ -2239,8 +2252,10 @@ main(int argc, char **argv)
 
 	evtimer_set(&gotd.listen_proc->tmo, kill_proc_timeout,
 	    gotd.listen_proc);
-	evtimer_set(&gotd.notify_proc->tmo, kill_proc_timeout,
-	    gotd.notify_proc);
+	if (gotd.notify_proc) {
+		evtimer_set(&gotd.notify_proc->tmo, kill_proc_timeout,
+		    gotd.notify_proc);
+	}
 
 	apply_unveil_selfexec();
 
@@ -2258,6 +2273,8 @@ main(int argc, char **argv)
 	signal_add(&evsigchld, NULL);
 
 	gotd_imsg_event_add(&gotd.listen_proc->iev);
+	if (gotd.notify_proc)
+		gotd_imsg_event_add(&gotd.notify_proc->iev);
 
 	event_dispatch();
 
blob - 97b77d29898f9e0859f385d519bf8e52600928f0
blob + 52cfe7948d93903861aec5c5f4f85960643c6444
--- gotd/gotd.h
+++ gotd/gotd.h
@@ -248,6 +248,7 @@ enum gotd_imsg_type {
 
 	/* Notify child process. */
 	GOTD_IMSG_CONNECT_NOTIFIER,
+	GOTD_IMSG_CONNECT_SESSION,
 	GOTD_IMSG_NOTIFY,
 };
 
blob - d8541cd4c711e5330e5a012fabb0dcc19ef71550
blob + 63b435eac961cc3e4ec6d6960946eedd84176f65
--- gotd/notify.c
+++ gotd/notify.c
@@ -22,6 +22,7 @@
 
 #include <errno.h>
 #include <event.h>
+#include <siphash.h>
 #include <limits.h>
 #include <sha1.h>
 #include <sha2.h>
@@ -39,6 +40,10 @@
 #include "log.h"
 #include "notify.h"
 
+#ifndef nitems
+#define nitems(_a)	(sizeof((_a)) / sizeof((_a)[0]))
+#endif
+
 static struct gotd_notify {
 	pid_t pid;
 	const char *title;
@@ -47,10 +52,92 @@ static struct gotd_notify {
 	const char *default_sender;
 } gotd_notify;
 
-void gotd_notify_sighdlr(int sig, short event, void *arg);
+struct gotd_notify_session {
+	STAILQ_ENTRY(gotd_notify_session) entry;
+	uint32_t id;
+	struct gotd_imsgev iev;
+};
+STAILQ_HEAD(gotd_notify_sessions, gotd_notify_session);
+
+static struct gotd_notify_sessions gotd_notify_sessions[GOTD_CLIENT_TABLE_SIZE];
+static SIPHASH_KEY sessions_hash_key;
+
 static void gotd_notify_shutdown(void);
 
-void
+static uint64_t
+session_hash(uint32_t session_id)
+{
+	return SipHash24(&sessions_hash_key, &session_id, sizeof(session_id));
+}
+
+static void
+add_session(struct gotd_notify_session *session)
+{
+	uint64_t slot;
+
+	slot = session_hash(session->id) % nitems(gotd_notify_sessions);
+	STAILQ_INSERT_HEAD(&gotd_notify_sessions[slot], session, entry);
+}
+
+static struct gotd_notify_session *
+find_session(uint32_t session_id)
+{
+	uint64_t slot;
+	struct gotd_notify_session *s;
+
+	slot = session_hash(session_id) % nitems(gotd_notify_sessions);
+	STAILQ_FOREACH(s, &gotd_notify_sessions[slot], entry) {
+		if (s->id == session_id)
+			return s;
+	}
+
+	return NULL;
+}
+
+static struct gotd_notify_session *
+find_session_by_fd(int fd)
+{
+	uint64_t slot;
+	struct gotd_notify_session *s;
+
+	for (slot = 0; slot < nitems(gotd_notify_sessions); slot++) {
+		STAILQ_FOREACH(s, &gotd_notify_sessions[slot], entry) {
+			if (s->iev.ibuf.fd == fd)
+				return s;
+		}
+
+	}
+
+	return NULL;
+}
+
+
+static void
+remove_session(struct gotd_notify_session *session)
+{
+	uint64_t slot;
+
+	slot = session_hash(session->id) % nitems(gotd_notify_sessions);
+	STAILQ_REMOVE(&gotd_notify_sessions[slot], session,
+	    gotd_notify_session, entry);
+	free(session);
+}
+
+static uint32_t
+get_session_id(void)
+{
+	int duplicate = 0;
+	uint32_t id;
+
+	do {
+		id = arc4random();
+		duplicate = (find_session(id) != NULL);
+	} while (duplicate || id == 0);
+
+	return id;
+}
+
+static void
 gotd_notify_sighdlr(int sig, short event, void *arg)
 {
 	/*
@@ -202,7 +289,7 @@ send_notifications(struct imsg *imsg)
 }
 
 static void
-notify_dispatch(int fd, short event, void *arg)
+notify_dispatch_session(int fd, short event, void *arg)
 {
 	struct gotd_imsgev *iev = arg;
 	struct imsgbuf *ibuf = &iev->ibuf;
@@ -242,8 +329,104 @@ notify_dispatch(int fd, short event, void *arg)
 		switch (imsg.hdr.type) {
 		case GOTD_IMSG_NOTIFY:
 			err = send_notifications(&imsg);
-			if (err)
-				break;
+			break;
+		default:
+			log_debug("unexpected imsg %d", imsg.hdr.type);
+			break;
+		}
+		imsg_free(&imsg);
+
+		if (err)
+			log_warnx("%s: %s", __func__, err->msg);
+	}
+done:
+	if (!shut) {
+		gotd_imsg_event_add(iev);
+	} else {
+		struct gotd_notify_session *session;
+
+		/* This pipe is dead. Remove its event handler */
+		event_del(&iev->ev);
+		imsg_clear(&iev->ibuf);
+
+		session = find_session_by_fd(fd);
+		if (session)
+			remove_session(session);
+	}
+}
+
+static const struct got_error *
+recv_session(struct imsg *imsg)
+{
+	struct gotd_notify_session *session;
+	size_t datalen;
+
+	datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+	if (datalen != 0)
+		return got_error(GOT_ERR_PRIVSEP_LEN);
+
+	if (imsg->fd == -1)
+		return got_error(GOT_ERR_PRIVSEP_NO_FD);
+
+	session = calloc(1, sizeof(*session));
+	if (session == NULL)
+		return got_error_from_errno("calloc");
+
+	session->id = get_session_id();
+	imsg_init(&session->iev.ibuf, imsg->fd);
+	session->iev.handler = notify_dispatch_session;
+	session->iev.events = EV_READ;
+	session->iev.handler_arg = NULL;
+	event_set(&session->iev.ev, session->iev.ibuf.fd, EV_READ,
+	    notify_dispatch_session, &session->iev);
+	gotd_imsg_event_add(&session->iev);
+	add_session(session);
+
+	return NULL;
+}
+
+static void
+notify_dispatch(int fd, short event, void *arg)
+{
+	struct gotd_imsgev *iev = arg;
+	struct imsgbuf *ibuf = &iev->ibuf;
+	ssize_t n;
+	int shut = 0;
+	struct imsg imsg;
+
+	if (event & EV_READ) {
+		if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+			fatal("imsg_read error");
+		if (n == 0) {
+			/* Connection closed. */
+			shut = 1;
+			goto done;
+		}
+	}
+
+	if (event & EV_WRITE) {
+		n = msgbuf_write(&ibuf->w);
+		if (n == -1 && errno != EAGAIN)
+			fatal("msgbuf_write");
+		if (n == 0) {
+			/* Connection closed. */
+			shut = 1;
+			goto done;
+		}
+	}
+
+	for (;;) {
+		const struct got_error *err = NULL;
+
+		if ((n = imsg_get(ibuf, &imsg)) == -1)
+			fatal("%s: imsg_get error", __func__);
+		if (n == 0)	/* No more messages. */
+			break;
+
+		switch (imsg.hdr.type) {
+		case GOTD_IMSG_CONNECT_SESSION:
+			err = recv_session(&imsg);
+			break;
 		default:
 			log_debug("unexpected imsg %d", imsg.hdr.type);
 			break;
@@ -271,6 +454,8 @@ notify_main(const char *title, struct gotd_repolist *r
 	const struct got_error *err = NULL;
 	struct event evsigint, evsigterm, evsighup, evsigusr1;
 
+	arc4random_buf(&sessions_hash_key, sizeof(sessions_hash_key));
+
 	gotd_notify.title = title;
 	gotd_notify.repos = repos;
 	gotd_notify.default_sender = default_sender;
@@ -293,6 +478,7 @@ notify_main(const char *title, struct gotd_repolist *r
 	gotd_notify.parent_iev.handler_arg = NULL;
 	event_set(&gotd_notify.parent_iev.ev, gotd_notify.parent_iev.ibuf.fd,
 	    EV_READ, notify_dispatch, &gotd_notify.parent_iev);
+	gotd_imsg_event_add(&gotd_notify.parent_iev);
 
 	event_dispatch();