Commit Diff


commit - 518fc9bf283a8410fd83a49addb3662f8b119498
commit + c2b405718eabe04c7ac3fa5916c1951227ab99fe
blob - 877e4d8fa52533fe457b51eefc3aed259e36e6a5
blob + bb6d62aaf04d48053d8c07f0eeab918d402677c8
--- gotd/Makefile
+++ gotd/Makefile
@@ -15,7 +15,7 @@ SRCS=		gotd.c auth.c repo_read.c repo_write.c log.c pr
 		object_open_io.c object_parse.c opentemp.c pack.c path.c \
 		read_gitconfig.c read_gotconfig.c reference.c repository.c  \
 		hash.c sigs.c pack_create_io.c pollfd.c reference_parse.c \
-		repo_imsg.c pack_index.c session.c object_qid.c
+		repo_imsg.c pack_index.c session.c object_qid.c notify.c
 
 CLEANFILES = parse.h
 
blob - b46b5573bc4b7b42b5b311fec8206262a1111da3
blob + b3a4fae0b273aa937461ce7fe139b1410c594ab5
--- gotd/gotd.c
+++ gotd/gotd.c
@@ -64,6 +64,7 @@
 #include "session.h"
 #include "repo_read.h"
 #include "repo_write.h"
+#include "notify.h"
 
 #ifndef nitems
 #define nitems(_a)	(sizeof((_a)) / sizeof((_a)[0]))
@@ -735,7 +736,8 @@ static const char *gotd_proc_names[PROC_MAX] = {
 	"session_write",
 	"repo_read",
 	"repo_write",
-	"gitwrapper"
+	"gitwrapper",
+	"notify"
 };
 
 static void
@@ -1550,6 +1552,9 @@ start_child(enum gotd_procid proc_id, const char *repo
 	case PROC_REPO_WRITE:
 		argv[argc++] = (char *)"-W";
 		break;
+	case PROC_NOTIFY:
+		argv[argc++] = (char *)"-N";
+		break;
 	default:
 		fatalx("invalid process id %d", proc_id);
 	}
@@ -1820,7 +1825,26 @@ set_max_datasize(void)
 	rl.rlim_cur = rl.rlim_max;
 	setrlimit(RLIMIT_DATA, &rl);
 }
+
+static void
+unveil_notification_helpers(void)
+{
+	const char *helpers[] = {
+	    GOTD_PATH_PROG_NOTIFY_EMAIL,
+	    GOTD_PATH_PROG_NOTIFY_HTTP,
+	};
+	size_t i;
 
+	for (i = 0; i < nitems(helpers); i++) {
+		if (unveil(helpers[i], "x") == 0)
+			continue;
+		fatal("unveil %s", helpers[i]);
+	}
+
+	if (unveil(NULL, NULL) == -1)
+		fatal("unveil");
+}
+
 int
 main(int argc, char **argv)
 {
@@ -1857,6 +1881,9 @@ main(int argc, char **argv)
 		case 'n':
 			noaction = 1;
 			break;
+		case 'N':
+			proc_id = PROC_NOTIFY;
+			break;
 		case 'P':
 			repo_path = realpath(optarg, NULL);
 			if (repo_path == NULL)
@@ -1954,6 +1981,9 @@ main(int argc, char **argv)
 			fatalx("repository path not specified");
 		snprintf(title, sizeof(title), "%s %s",
 		    gotd_proc_names[proc_id], repo_path);
+	} else if (proc_id == PROC_NOTIFY) {
+		snprintf(title, sizeof(title), "%s %s",
+		    gotd_proc_names[proc_id], repo_path);
 	} else
 		fatal("invalid process id %d", proc_id);
 
@@ -2050,6 +2080,19 @@ main(int argc, char **argv)
 		    &repo->protected_tag_namespaces,
 		    &repo->protected_branch_namespaces,
 		    &repo->protected_branches);
+		/* NOTREACHED */
+		exit(0);
+	case PROC_NOTIFY:
+#ifndef PROFILE
+		if (pledge("stdio proc exec unveil", NULL) == -1)
+			err(1, "pledge");
+#endif
+		/*
+		 * Limit "exec" promise to notification helpers via unveil(2).
+		 */
+		unveil_notification_helpers();
+
+		notify_main(title);
 		/* NOTREACHED */
 		exit(0);
 	default:
blob - acb40dee8cd351b48669c3c3247c42ed5f44501b
blob + bd50b7baeef6f944713df1b7a9cd66e274d6b537
--- gotd/gotd.h
+++ gotd/gotd.h
@@ -21,6 +21,23 @@
 #define GOTD_CONF_PATH	"/etc/gotd.conf"
 #define GOTD_EMPTY_PATH	"/var/empty"
 
+#ifndef GOT_LIBEXECDIR
+#define GOT_LIBEXECDIR /usr/libexec
+#endif
+
+#define GOTD_STRINGIFY(x) #x
+#define GOTD_STRINGVAL(x) GOTD_STRINGIFY(x)
+
+#define GOTD_PROG_NOTIFY_EMAIL	got-notify-email
+#define GOTD_PROG_NOTIFY_HTTP	got-notify-http
+
+#define GOTD_PATH_PROG_NOTIFY_EMAIL \
+	GOTD_STRINGVAL(GOT_LIBEXECDIR) "/" \
+	GOTD_STRINGVAL(GOTD_PROG_NOTIFY_EMAIL)
+#define GOTD_PATH_PROG_NOTIFY_HTTP \
+	GOTD_STRINGVAL(GOT_LIBEXECDIR) "/" \
+	GOTD_STRINGVAL(GOTD_PROG_NOTIFY_HTTP)
+
 #define GOTD_MAXCLIENTS		1024
 #define GOTD_MAX_CONN_PER_UID	4
 #define GOTD_FD_RESERVE		5
@@ -41,6 +58,7 @@ enum gotd_procid {
 	PROC_REPO_READ,
 	PROC_REPO_WRITE,
 	PROC_GITWRAPPER,
+	PROC_NOTIFY,
 	PROC_MAX,
 };
 
@@ -193,6 +211,9 @@ enum gotd_imsg_type {
 	/* Auth child process. */
 	GOTD_IMSG_AUTHENTICATE,
 	GOTD_IMSG_ACCESS_GRANTED,
+
+	/* Notify child process. */
+	GOTD_IMSG_NOTIFY_READY,
 };
 
 /* Structure for GOTD_IMSG_ERROR. */
blob - /dev/null
blob + 929e63947c237f8baaeb6b5c9a27980e7054685c (mode 644)
--- /dev/null
+++ gotd/notify.c
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 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/types.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+#include <sys/socket.h>
+
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <sha1.h>
+#include <sha2.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <imsg.h>
+#include <unistd.h>
+
+#include "got_error.h"
+#include "got_path.h"
+
+#include "gotd.h"
+#include "log.h"
+#include "notify.h"
+
+static struct gotd_notify {
+	pid_t pid;
+	const char *title;
+	struct gotd_imsgev parent_iev;
+} gotd_notify;
+
+void gotd_notify_sighdlr(int sig, short event, void *arg);
+static void gotd_notify_shutdown(void);
+
+void
+gotd_notify_sighdlr(int sig, short event, void *arg)
+{
+	/*
+	 * Normal signal handler rules don't apply because libevent
+	 * decouples for us.
+	 */
+
+	switch (sig) {
+	case SIGHUP:
+		log_info("%s: ignoring SIGHUP", __func__);
+		break;
+	case SIGUSR1:
+		log_info("%s: ignoring SIGUSR1", __func__);
+		break;
+	case SIGTERM:
+	case SIGINT:
+		gotd_notify_shutdown();
+		/* NOTREACHED */
+		break;
+	default:
+		fatalx("unexpected signal");
+	}
+}
+
+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) {
+		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 {
+		/* This pipe is dead. Remove its event handler */
+		event_del(&iev->ev);
+		event_loopexit(NULL);
+	}
+
+}
+
+void
+notify_main(const char *title)
+{
+	const struct got_error *err = NULL;
+	struct event evsigint, evsigterm, evsighup, evsigusr1;
+
+	gotd_notify.title = title;
+	gotd_notify.pid = getpid();
+
+	signal_set(&evsigint, SIGINT, gotd_notify_sighdlr, NULL);
+	signal_set(&evsigterm, SIGTERM, gotd_notify_sighdlr, NULL);
+	signal_set(&evsighup, SIGHUP, gotd_notify_sighdlr, NULL);
+	signal_set(&evsigusr1, SIGUSR1, gotd_notify_sighdlr, NULL);
+	signal(SIGPIPE, SIG_IGN);
+
+	signal_add(&evsigint, NULL);
+	signal_add(&evsigterm, NULL);
+	signal_add(&evsighup, NULL);
+	signal_add(&evsigusr1, NULL);
+
+	imsg_init(&gotd_notify.parent_iev.ibuf, GOTD_FILENO_MSG_PIPE);
+	gotd_notify.parent_iev.handler = notify_dispatch;
+	gotd_notify.parent_iev.events = EV_READ;
+	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);
+	if (gotd_imsg_compose_event(&gotd_notify.parent_iev,
+	    GOTD_IMSG_NOTIFY_READY, PROC_NOTIFY, -1, NULL, 0) == -1) {
+		err = got_error_from_errno("imsg compose NOTIFY_READY");
+		goto done;
+	}
+
+	event_dispatch();
+done:
+	if (err)
+		log_warnx("%s: %s", title, err->msg);
+	gotd_notify_shutdown();
+}
+
+void
+gotd_notify_shutdown(void)
+{
+	log_debug("shutting down");
+	exit(0);
+}
blob - /dev/null
blob + 5ba452e52c2a67baf8c8220364b01201f02427a0 (mode 644)
--- /dev/null
+++ gotd/notify.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2023 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.
+ */
+
+void notify_main(const char *);