Commit Diff


commit - 4fd3ab65db81313a54815adef56b05709e08fce6
commit + 8b10295ec9ddde4f3bfef322b31d3601d6040c62
blob - dfaea693e455367a6ee8fea158b8dbdfa779fed7
blob + 1210e46dd5c67d6073935d5542ac84dc5a6f6a15
--- gotd/gotd.c
+++ gotd/gotd.c
@@ -1146,6 +1146,14 @@ connect_repo_child(struct gotd_client *client,
 		fatal("socketpair");
 
 	memset(&ireq, 0, sizeof(ireq));
+	if (strlcpy(ireq.repo_name, repo_proc->repo_name,
+	    sizeof(ireq.repo_name)) >= sizeof(ireq.repo_name)) {
+		err = got_error_msg(GOT_ERR_NO_SPACE,
+		    "repository name too long");
+		close(pipe[0]);
+		close(pipe[1]);
+		return err;
+	}
 	ireq.proc_id = repo_proc->type;
 
 	/* Pass repo child pipe to session child process. */
@@ -1807,8 +1815,259 @@ run_gotsys_apply(struct gotd_repo *repo)
 
 	execvp(argv[0], (char * const *)argv);
 	fatal("execvp");
+}
+
+static const struct got_error *
+send_pathlist_elem(struct gotd_imsgev *iev, const char *refname, int imsg_type)
+{
+	struct gotd_imsg_pathlist_elem ielem;
+	struct ibuf *wbuf = NULL;
+
+	memset(&ielem, 0, sizeof(ielem));
+	ielem.path_len = strlen(refname);
+
+	wbuf = imsg_create(&iev->ibuf, imsg_type, GOTD_PROC_GOTD, gotd.pid,
+	    sizeof(ielem) + ielem.path_len);
+	if (wbuf == NULL)
+		return got_error_from_errno_fmt("imsg_create %d", imsg_type);
+
+	if (imsg_add(wbuf, &ielem, sizeof(ielem)) == -1)
+		return got_error_from_errno_fmt("imsg_add %d", imsg_type);
+	if (imsg_add(wbuf, refname, ielem.path_len) == -1)
+		return got_error_from_errno_fmt("imsg_add %d", imsg_type);
+
+	imsg_close(&iev->ibuf, wbuf);
+	return gotd_imsg_flush(&iev->ibuf);
+}
+
+static const struct got_error *
+send_request_timeout(struct gotd_imsgev *iev, struct timeval *timeout)
+{
+	if (gotd_imsg_compose_event(iev, GOTD_IMSG_REQUEST_TIMEOUT,
+	    GOTD_PROC_GOTD, -1, timeout, sizeof(*timeout)) == -1) {
+		return got_error_from_errno("imsg compose REQUEST_TIMEOUT");
+	}
+
+	return NULL;
+}
+
+static const struct got_error *
+send_notification_target_email(struct gotd_imsgev *iev,
+    struct gotd_notification_target *target)
+{
+	struct gotd_imsg_notitfication_target_email itarget;
+	struct ibuf *wbuf = NULL;
+
+	memset(&itarget, 0, sizeof(itarget));
+
+	if (target->conf.email.sender)
+		itarget.sender_len = strlen(target->conf.email.sender);
+	if (target->conf.email.recipient)
+		itarget.recipient_len = strlen(target->conf.email.recipient);
+	if (target->conf.email.responder)
+		itarget.responder_len = strlen(target->conf.email.responder);
+	if (target->conf.email.hostname)
+		itarget.hostname_len = strlen(target->conf.email.hostname);
+	if (target->conf.email.port)
+		itarget.port_len = strlen(target->conf.email.port);
+
+	wbuf = imsg_create(&iev->ibuf, GOTD_IMSG_NOTIFICATION_TARGET_EMAIL,
+	    0, 0, sizeof(itarget) + itarget.sender_len + itarget.recipient_len +
+	    itarget.responder_len + itarget.hostname_len + itarget.port_len);
+	if (wbuf == NULL) {
+		return got_error_from_errno("imsg_create "
+		    "NOTIFICATION_TARGET_EMAIL");
+	}
+
+	if (imsg_add(wbuf, &itarget, sizeof(itarget)) == -1) {
+		return got_error_from_errno("imsg_add "
+		    "NOTIFICATION_TARGET_EMAIL");
+	}
+	if (target->conf.email.sender) {
+		if (imsg_add(wbuf, target->conf.email.sender,
+		    itarget.sender_len) == -1) {
+			return got_error_from_errno("imsg_add "
+			    "NOTIFICATION_TARGET_EMAIL");
+		}
+	}
+	if (target->conf.email.recipient) {
+		if (imsg_add(wbuf, target->conf.email.recipient,
+		    itarget.recipient_len) == -1) {
+			return got_error_from_errno("imsg_add "
+			    "NOTIFICATION_TARGET_EMAIL");
+		}
+	}
+	if (target->conf.email.responder) {
+		if (imsg_add(wbuf, target->conf.email.responder,
+		    itarget.responder_len) == -1) {
+			return got_error_from_errno("imsg_add "
+			    "NOTIFICATION_TARGET_EMAIL");
+		}
+	}
+	if (target->conf.email.hostname) {
+		if (imsg_add(wbuf, target->conf.email.hostname,
+		    itarget.hostname_len) == -1) {
+			return got_error_from_errno("imsg_add "
+			    "NOTIFICATION_TARGET_EMAIL");
+		}
+	}
+	if (target->conf.email.port) {
+		if (imsg_add(wbuf, target->conf.email.port,
+		    itarget.port_len) == -1) {
+			return got_error_from_errno("imsg_add "
+			    "NOTIFICATION_TARGET_EMAIL");
+		}
+	}
+
+	imsg_close(&iev->ibuf, wbuf);
+	return gotd_imsg_flush(&iev->ibuf);
 }
 
+static const struct got_error *
+send_notification_target_http(struct gotd_imsgev *iev,
+    struct gotd_notification_target *target)
+{
+	struct gotd_imsg_notitfication_target_http itarget;
+	struct ibuf *wbuf = NULL;
+
+	memset(&itarget, 0, sizeof(itarget));
+
+	itarget.tls = target->conf.http.tls;
+	itarget.hostname_len = strlen(target->conf.http.hostname);
+	itarget.port_len = strlen(target->conf.http.port);
+	itarget.path_len = strlen(target->conf.http.path);
+	if (target->conf.http.auth)
+		itarget.auth_len = strlen(target->conf.http.auth);
+	if (target->conf.http.hmac)
+		itarget.hmac_len = strlen(target->conf.http.hmac);
+
+	wbuf = imsg_create(&iev->ibuf, GOTD_IMSG_NOTIFICATION_TARGET_HTTP,
+	    0, 0, sizeof(itarget) + itarget.hostname_len + itarget.port_len +
+	    itarget.path_len + itarget.auth_len + itarget.hmac_len);
+	if (wbuf == NULL) {
+		return got_error_from_errno("imsg_create "
+		    "NOTIFICATION_TARGET_HTTP");
+	}
+
+	if (imsg_add(wbuf, &itarget, sizeof(itarget)) == -1) {
+		return got_error_from_errno("imsg_add "
+		    "NOTIFICATION_TARGET_HTTP");
+	}
+	if (imsg_add(wbuf, target->conf.http.hostname,
+	    itarget.hostname_len) == -1) {
+		return got_error_from_errno("imsg_add "
+		    "NOTIFICATION_TARGET_HTTP");
+	}
+	if (imsg_add(wbuf, target->conf.http.port,
+	    itarget.port_len) == -1) {
+		return got_error_from_errno("imsg_add "
+		    "NOTIFICATION_TARGET_HTTP");
+	}
+	if (imsg_add(wbuf, target->conf.http.path,
+	    itarget.path_len) == -1) {
+		return got_error_from_errno("imsg_add "
+		    "NOTIFICATION_TARGET_HTTP");
+	}
+
+	if (target->conf.http.auth) {
+		if (imsg_add(wbuf, target->conf.http.auth,
+		    itarget.auth_len) == -1) {
+			return got_error_from_errno("imsg_add "
+			    "NOTIFICATION_TARGET_HTTP");
+		}
+	}
+	if (target->conf.http.hmac) {
+		if (imsg_add(wbuf, target->conf.http.hmac,
+		    itarget.hmac_len) == -1) {
+			return got_error_from_errno("imsg_add "
+			    "NOTIFICATION_TARGET_HTTP");
+		}
+	}
+
+	imsg_close(&iev->ibuf, wbuf);
+	return gotd_imsg_flush(&iev->ibuf);
+}
+
+static const struct got_error *
+send_notification_target(struct gotd_imsgev *iev,
+    struct gotd_notification_target *target)
+{
+	const struct got_error *err = NULL;
+
+	switch (target->type) {
+	case GOTD_NOTIFICATION_VIA_EMAIL:
+		err = send_notification_target_email(iev, target);
+		break;
+	case GOTD_NOTIFICATION_VIA_HTTP:
+		err = send_notification_target_http(iev, target);
+		break;
+	default:
+		log_warn("unsupported notification target type %d",
+		    target->type);
+		break;
+	}
+
+	return err;
+}
+
+static const struct got_error *
+send_notification_config(struct gotd_imsgev *iev, char *repo_name)
+{
+	const struct got_error *err = NULL;
+	struct gotd_repo *repo;
+	struct got_pathlist_entry *pe;
+	struct gotd_imsg_pathlist ilist;
+	struct gotd_notification_target *target;
+
+	memset(&ilist, 0, sizeof(ilist));
+
+	repo = gotd_find_repo_by_name(repo_name, &gotd.repos);
+	if (repo == NULL)
+		return got_error(GOT_ERR_NOT_GIT_REPO);
+
+	ilist.nelem = repo->num_notification_refs;
+	if (ilist.nelem > 0) {
+		if (gotd_imsg_compose_event(iev, GOTD_IMSG_NOTIFICATION_REFS,
+		    GOTD_PROC_GOTD, -1, &ilist, sizeof(ilist)) == -1) {
+			return got_error_from_errno("imsg compose "
+			    "NOTIFICATION_REFS");
+		}
+
+		RB_FOREACH(pe, got_pathlist_head, &repo->notification_refs) {
+			err = send_pathlist_elem(iev, pe->path,
+			    GOTD_IMSG_NOTIFICATION_REFS_ELEM);
+			if (err)
+				return err;
+		}
+	}
+
+	ilist.nelem = repo->num_notification_ref_namespaces;
+	if (ilist.nelem > 0) {
+		if (gotd_imsg_compose_event(iev,
+		    GOTD_IMSG_NOTIFICATION_REF_NAMESPACES,
+		    GOTD_PROC_GOTD, -1, &ilist, sizeof(ilist)) == -1) {
+			return got_error_from_errno("imsg compose "
+			    "NOTIFICATION_REF_NAMESPACES");
+		}
+
+		RB_FOREACH(pe, got_pathlist_head,
+		    &repo->notification_ref_namespaces) {
+			err = send_pathlist_elem(iev, pe->path,
+			    GOTD_IMSG_NOTIFICATION_REF_NAMESPACES_ELEM);
+			if (err)
+				return err;
+		}
+	}
+
+	STAILQ_FOREACH(target, &repo->notification_targets, entry) {
+		err = send_notification_target(iev, target);
+		if (err)
+			return err;
+	}
+
+	return NULL;
+}
+
 static void
 gotd_dispatch_client_session(int fd, short event, void *arg)
 {
@@ -1871,6 +2130,16 @@ gotd_dispatch_client_session(int fd, short event, void
 				err = got_error(GOT_ERR_PRIVSEP_MSG);
 				break;
 			}
+			if (client_is_writing(client)) {
+				err = send_request_timeout(iev,
+				    &gotd.request_timeout);
+				if (err)
+					break;
+				err = send_notification_config(iev,
+				    proc->repo_name);
+				if (err)
+					break;
+			}
 			do_start_repo_child = 1;
 			break;
 		case GOTD_IMSG_RUN_GOTSYS_CHECK:
@@ -2033,30 +2302,6 @@ connect_notifier_and_session(struct gotd_client *clien
 	}
 
 	return NULL;
-}
-
-static const struct got_error *
-send_protected_ref(struct gotd_imsgev *iev, const char *refname,
-    int imsg_type)
-{
-	struct gotd_imsg_pathlist_elem ielem;
-	struct ibuf *wbuf = NULL;
-
-	memset(&ielem, 0, sizeof(ielem));
-	ielem.path_len = strlen(refname);
-
-	wbuf = imsg_create(&iev->ibuf, imsg_type, GOTD_PROC_GOTD, gotd.pid,
-	    sizeof(ielem) + ielem.path_len);
-	if (wbuf == NULL)
-		return got_error_from_errno_fmt("imsg_create %d", imsg_type);
-
-	if (imsg_add(wbuf, &ielem, sizeof(ielem)) == -1)
-		return got_error_from_errno_fmt("imsg_add %d", imsg_type);
-	if (imsg_add(wbuf, refname, ielem.path_len) == -1)
-		return got_error_from_errno_fmt("imsg_add %d", imsg_type);
-
-	imsg_close(&iev->ibuf, wbuf);
-	return gotd_imsg_flush(&iev->ibuf);
 }
 
 static const struct got_error *
@@ -2084,7 +2329,7 @@ send_protected_refs(struct gotd_imsgev *iev, const cha
 
 		RB_FOREACH(pe, got_pathlist_head,
 		    &repo->protected_tag_namespaces) {
-			err = send_protected_ref(iev, pe->path,
+			err = send_pathlist_elem(iev, pe->path,
 			    GOTD_IMSG_PROTECTED_TAG_NAMESPACES_ELEM);
 			if (err)
 				return err;
@@ -2102,7 +2347,7 @@ send_protected_refs(struct gotd_imsgev *iev, const cha
 
 		RB_FOREACH(pe, got_pathlist_head,
 		    &repo->protected_branch_namespaces) {
-			err = send_protected_ref(iev, pe->path,
+			err = send_pathlist_elem(iev, pe->path,
 			    GOTD_IMSG_PROTECTED_BRANCH_NAMESPACES_ELEM);
 			if (err)
 				return err;
@@ -2118,7 +2363,7 @@ send_protected_refs(struct gotd_imsgev *iev, const cha
 		}
 
 		RB_FOREACH(pe, got_pathlist_head, &repo->protected_branches) {
-			err = send_protected_ref(iev, pe->path,
+			err = send_pathlist_elem(iev, pe->path,
 			    GOTD_IMSG_PROTECTED_BRANCHES_ELEM);
 			if (err)
 				return err;
@@ -2747,7 +2992,8 @@ main(int argc, char **argv)
 	}
 
 	if (proc_id != GOTD_PROC_LISTEN && proc_id != GOTD_PROC_AUTH &&
-	    proc_id != GOTD_PROC_REPO_WRITE) {
+	    proc_id != GOTD_PROC_REPO_WRITE &&
+	    proc_id != GOTD_PROC_SESSION_WRITE) {
 		if (gotd_parse_config(confpath, proc_id, secrets, &gotd) != 0)
 			return 1;
 
@@ -2941,11 +3187,8 @@ main(int argc, char **argv)
 			err(1, "pledge");
 #endif
 		apply_unveil_repo_readwrite(repo_path);
-		repo = gotd_find_repo_by_path(repo_path, &gotd);
-		if (repo == NULL)
-			fatalx("no repository for path %s", repo_path);
-		session_write_main(title, repo_path, pack_fds, temp_fds, tmp_fd,
-		    &gotd.request_timeout, repo);
+		session_write_main(title, repo_path, pack_fds, temp_fds,
+		    tmp_fd);
 		/* NOTREACHED */
 		break;
 	case GOTD_PROC_REPO_READ:
blob - bc67d2befa7742192232f3c52b8845a86648e643
blob + 95ebcef04adc3f9d2afc5df131874ee0f9170ed7
--- gotd/gotd.h
+++ gotd/gotd.h
@@ -141,7 +141,9 @@ struct gotd_repo {
 	size_t nprotected_branches;
 
 	struct got_pathlist_head notification_refs;
+	size_t num_notification_refs;
 	struct got_pathlist_head notification_ref_namespaces;
+	size_t num_notification_ref_namespaces;
 	struct gotd_notification_targets notification_targets;
 };
 TAILQ_HEAD(gotd_repolist, gotd_repo);
@@ -247,6 +249,7 @@ enum gotd_imsg_type {
 	GOTD_IMSG_LISTENER_READY,
 	GOTD_IMSG_LISTEN_SOCKET,
 	GOTD_IMSG_CONNECTION_LIMIT,
+	GOTD_IMSG_REQUEST_TIMEOUT,
 	GOTD_IMSG_DISCONNECT,
 	GOTD_IMSG_CONNECT,
 
@@ -270,6 +273,12 @@ enum gotd_imsg_type {
 	GOTD_IMSG_PROTECTED_BRANCHES_ELEM,
 
 	/* Notify child process. */
+	GOTD_IMSG_NOTIFICATION_REFS,
+	GOTD_IMSG_NOTIFICATION_REFS_ELEM,
+	GOTD_IMSG_NOTIFICATION_REF_NAMESPACES,
+	GOTD_IMSG_NOTIFICATION_REF_NAMESPACES_ELEM,
+	GOTD_IMSG_NOTIFICATION_TARGET_EMAIL,
+	GOTD_IMSG_NOTIFICATION_TARGET_HTTP,
 	GOTD_IMSG_CONNECT_NOTIFIER,
 	GOTD_IMSG_CONNECT_SESSION,
 	GOTD_IMSG_NOTIFY,
@@ -521,6 +530,7 @@ struct gotd_imsg_connect {
 
 /* Structure for GOTD_IMSG_CONNECT_REPO_CHILD. */
 struct gotd_imsg_connect_repo_child {
+	char repo_name[NAME_MAX];
 	enum gotd_procid proc_id;
 
 	/* repo child imsg pipe is passed via imsg fd */
@@ -549,6 +559,8 @@ struct gotd_imsg_auth_access_rule {
  * GOTD_IMSG_PROTECTED_TAG_NAMESPACES
  * GOTD_IMSG_PROTECTED_BRANCH_NAMESPACES
  * GOTD_IMSG_PROTECTED_BRANCHES
+ * GOTD_IMSG_NOTIFY_BRANCHES
+ * GOTD_IMSG_NOTIFY_REF_NAMESPACES
  */
 struct gotd_imsg_pathlist {
 	size_t nelem;
@@ -561,6 +573,8 @@ struct gotd_imsg_pathlist {
  * GOTD_IMSG_PROTECTED_TAG_NAMESPACES_ELEM
  * GOTD_IMSG_PROTECTED_BRANCH_NAMESPACES_ELEM
  * GOTD_IMSG_PROTECTED_BRANCHES_ELEM
+ * GOTD_IMSG_NOTIFY_BRANCHES_ELEM
+ * GOTD_IMSG_NOTIFY_REF_NAMESPACES_ELEM
  */
 struct gotd_imsg_pathlist_elem {
 	size_t path_len;
@@ -568,8 +582,37 @@ struct gotd_imsg_pathlist_elem {
 
 	/* Followed by path_len bytes. */
 	/* Followed by data_len bytes. */
+};
+
+/* Structure for GOTD_IMSG_NOTIFICATION_TARGET_EMAIL. */
+struct gotd_imsg_notitfication_target_email {
+	size_t sender_len;
+	size_t recipient_len;
+	size_t responder_len;
+	size_t hostname_len;
+	size_t port_len;
+
+	/*
+	 * Followed by sender_len + responder_len + responder_len +
+	 * hostname_len + port_len bytes.
+	 */
 };
 
+/* Structure for GOTD_IMSG_NOTIFICATION_TARGET_HTTP. */
+struct gotd_imsg_notitfication_target_http {
+	int tls;
+	size_t hostname_len;
+	size_t port_len;
+	size_t path_len;
+	size_t auth_len;
+	size_t hmac_len;;
+
+	/*
+	 * Followed by hostname_len + port_len + path_len + auth_len +
+	 * hmac_len bytes.
+	 */
+};
+
 /* Structures for GOTD_IMSG_NOTIFY. */
 enum gotd_notification_action {
 	GOTD_NOTIF_ACTION_CREATED,
@@ -622,3 +665,6 @@ void gotd_imsg_send_ack(struct got_object_id *, struct
     uint32_t, pid_t);
 void gotd_imsg_send_nak(struct got_object_id *, struct imsgbuf *,
     uint32_t, pid_t);
+const struct got_error *gotd_imsg_recv_pathlist(size_t *, struct imsg *);
+const struct got_error *gotd_imsg_recv_pathlist_elem(struct imsg *,
+    struct got_pathlist_head *);
blob - 476171315d6b76a7222751f268472e1275f1373b
blob + e0c130803906829b51d5f392e75336ea4f953991
--- gotd/imsg.c
+++ gotd/imsg.c
@@ -26,6 +26,7 @@
 #include <limits.h>
 #include <poll.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 
@@ -205,3 +206,49 @@ gotd_imsg_forward(struct gotd_imsgev *iev, struct imsg
 	return gotd_imsg_compose_event(iev, imsg->hdr.type, imsg->hdr.peerid,
 	    fd, imsg->data, imsg->hdr.len - IMSG_HEADER_SIZE);
 }
+
+const struct got_error *
+gotd_imsg_recv_pathlist(size_t *npaths, struct imsg *imsg)
+{
+	struct gotd_imsg_pathlist ilist;
+	size_t datalen;
+
+	datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+	if (datalen != sizeof(ilist))
+		return got_error(GOT_ERR_PRIVSEP_LEN);
+	memcpy(&ilist, imsg->data, sizeof(ilist));
+
+	if (ilist.nelem == 0)
+		return got_error(GOT_ERR_PRIVSEP_LEN);
+
+	*npaths = ilist.nelem;
+	return NULL;
+}
+
+const struct got_error *
+gotd_imsg_recv_pathlist_elem(struct imsg *imsg, struct got_pathlist_head *paths)
+{
+	const struct got_error *err = NULL;
+	struct gotd_imsg_pathlist_elem ielem;
+	size_t datalen;
+	char *path;
+	struct got_pathlist_entry *pe;
+
+	datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+	if (datalen < sizeof(ielem))
+		return got_error(GOT_ERR_PRIVSEP_LEN);
+	memcpy(&ielem, imsg->data, sizeof(ielem));
+
+	if (datalen != sizeof(ielem) + ielem.path_len)
+		return got_error(GOT_ERR_PRIVSEP_LEN);
+
+	path = strndup(imsg->data + sizeof(ielem), ielem.path_len);
+	if (path == NULL)
+		return got_error_from_errno("strndup");
+
+	err = got_pathlist_insert(&pe, paths, path, NULL);
+	if (err || pe == NULL)
+		free(path);
+	return err;
+}
+
blob - 4bf1400741232fd1549cacb7f3b0cd4cdd95cf25
blob + daaf2c2aa310ab499970f60fa62149dff6b06347
--- gotd/parse.y
+++ gotd/parse.y
@@ -328,7 +328,6 @@ notifyflags_l	: notifyflags optnl notifyflags_l
 
 notifyflags	: BRANCH STRING {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_branch(new_repo, $2)) {
 					free($2);
@@ -339,7 +338,6 @@ notifyflags	: BRANCH STRING {
 		}
 		| REFERENCE NAMESPACE STRING {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_ref_namespace(new_repo, $3)) {
 					free($3);
@@ -350,7 +348,6 @@ notifyflags	: BRANCH STRING {
 		}
 		| EMAIL TO STRING {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_email(new_repo, NULL, $3,
 				    NULL, NULL, NULL)) {
@@ -362,7 +359,6 @@ notifyflags	: BRANCH STRING {
 		}
 		| EMAIL FROM STRING TO STRING {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_email(new_repo, $3, $5,
 				    NULL, NULL, NULL)) {
@@ -376,7 +372,6 @@ notifyflags	: BRANCH STRING {
 		}
 		| EMAIL TO STRING REPLY TO STRING {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_email(new_repo, NULL, $3,
 				    $6, NULL, NULL)) {
@@ -390,7 +385,6 @@ notifyflags	: BRANCH STRING {
 		}
 		| EMAIL FROM STRING TO STRING REPLY TO STRING {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_email(new_repo, $3, $5,
 				    $8, NULL, NULL)) {
@@ -406,7 +400,6 @@ notifyflags	: BRANCH STRING {
 		}
 		| EMAIL TO STRING RELAY STRING {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_email(new_repo, NULL, $3,
 				    NULL, $5, NULL)) {
@@ -420,7 +413,6 @@ notifyflags	: BRANCH STRING {
 		}
 		| EMAIL FROM STRING TO STRING RELAY STRING {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_email(new_repo, $3, $5,
 				    NULL, $7, NULL)) {
@@ -436,7 +428,6 @@ notifyflags	: BRANCH STRING {
 		}
 		| EMAIL TO STRING REPLY TO STRING RELAY STRING {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_email(new_repo, NULL, $3,
 				    $6, $8, NULL)) {
@@ -452,7 +443,6 @@ notifyflags	: BRANCH STRING {
 		}
 		| EMAIL FROM STRING TO STRING REPLY TO STRING RELAY STRING {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_email(new_repo, $3, $5,
 				    $8, $10, NULL)) {
@@ -470,7 +460,6 @@ notifyflags	: BRANCH STRING {
 		}
 		| EMAIL TO STRING RELAY STRING PORT STRING {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_email(new_repo, NULL, $3,
 				    NULL, $5, $7)) {
@@ -486,7 +475,6 @@ notifyflags	: BRANCH STRING {
 		}
 		| EMAIL FROM STRING TO STRING RELAY STRING PORT STRING {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_email(new_repo, $3, $5,
 				    NULL, $7, $9)) {
@@ -504,7 +492,6 @@ notifyflags	: BRANCH STRING {
 		}
 		| EMAIL TO STRING REPLY TO STRING RELAY STRING PORT STRING {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_email(new_repo, NULL, $3,
 				    $6, $8, $10)) {
@@ -522,7 +509,6 @@ notifyflags	: BRANCH STRING {
 		}
 		| EMAIL FROM STRING TO STRING REPLY TO STRING RELAY STRING PORT STRING {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_email(new_repo, $3, $5,
 				    $8, $10, $12)) {
@@ -542,7 +528,6 @@ notifyflags	: BRANCH STRING {
 		}
 		| EMAIL TO STRING RELAY STRING PORT NUMBER {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_email(new_repo, NULL, $3,
 				    NULL, $5, port_sprintf($7))) {
@@ -556,7 +541,6 @@ notifyflags	: BRANCH STRING {
 		}
 		| EMAIL FROM STRING TO STRING RELAY STRING PORT NUMBER {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_email(new_repo, $3, $5,
 				    NULL, $7, port_sprintf($9))) {
@@ -572,7 +556,6 @@ notifyflags	: BRANCH STRING {
 		}
 		| EMAIL TO STRING REPLY TO STRING RELAY STRING PORT NUMBER {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_email(new_repo, NULL, $3,
 				    $6, $8, port_sprintf($10))) {
@@ -588,7 +571,6 @@ notifyflags	: BRANCH STRING {
 		}
 		| EMAIL FROM STRING TO STRING REPLY TO STRING RELAY STRING PORT NUMBER {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_email(new_repo, $3, $5,
 				    $8, $10, port_sprintf($12))) {
@@ -606,7 +588,6 @@ notifyflags	: BRANCH STRING {
 		}
 		| URL STRING {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_http(new_repo, $2, NULL,
 				    NULL, 0)) {
@@ -618,7 +599,6 @@ notifyflags	: BRANCH STRING {
 		}
 		| URL STRING AUTH STRING {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_http(new_repo, $2, $4, NULL,
 				    0)) {
@@ -632,7 +612,6 @@ notifyflags	: BRANCH STRING {
 		}
 		| URL STRING AUTH STRING INSECURE {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_http(new_repo, $2, $4, NULL,
 				    1)) {
@@ -646,7 +625,6 @@ notifyflags	: BRANCH STRING {
 		}
 		| URL STRING HMAC STRING {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_http(new_repo, $2, NULL, $4,
 				    0)) {
@@ -660,7 +638,6 @@ notifyflags	: BRANCH STRING {
 		}
 		| URL STRING AUTH STRING HMAC STRING {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_http(new_repo, $2, $4, $6,
 				    0)) {
@@ -676,7 +653,6 @@ notifyflags	: BRANCH STRING {
 		}
 		| URL STRING AUTH STRING INSECURE HMAC STRING {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (conf_notify_http(new_repo, $2, $4, $7,
 				    1)) {
@@ -704,7 +680,6 @@ repository	: REPOSITORY STRING {
 			}
 
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_GITWRAPPER |
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				new_repo = conf_new_repo($2);
@@ -716,7 +691,6 @@ repository	: REPOSITORY STRING {
 
 repoopts1	: PATH STRING {
 			if (gotd_proc_id == GOTD_PROC_GOTD ||
-			    gotd_proc_id == GOTD_PROC_SESSION_WRITE ||
 			    gotd_proc_id == GOTD_PROC_GITWRAPPER ||
 			    gotd_proc_id == GOTD_PROC_NOTIFY) {
 				if (!got_path_is_absolute($2)) {
@@ -1523,6 +1497,8 @@ conf_notify_branch(struct gotd_repo *repo, char *branc
 	}
 	if (pe == NULL)
 		free(refname);
+	else
+		repo->num_notification_refs++;
 
 	return 0;
 }
@@ -1552,6 +1528,8 @@ conf_notify_ref_namespace(struct gotd_repo *repo, char
 	}
 	if (pe == NULL)
 		free(s);
+	else
+		repo->num_notification_ref_namespaces++;
 
 	return 0;
 }
blob - 7d8e2cf8d5bc2251c71eadad35e0d5e810910b00
blob + 219767f09e11b7fc1231a8076ebabe3276c1fb22
--- gotd/repo_write.c
+++ gotd/repo_write.c
@@ -2727,55 +2727,10 @@ recv_connect(struct imsg *imsg)
 	event_set(&iev->ev, iev->ibuf.fd, EV_READ,
 	    repo_write_dispatch_session, iev);
 	gotd_imsg_event_add(iev);
-
-	return NULL;
-}
-
-static const struct got_error *
-recv_pathlist(size_t *npaths, struct imsg *imsg)
-{
-	struct gotd_imsg_pathlist ilist;
-	size_t datalen;
-
-	datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
-	if (datalen != sizeof(ilist))
-		return got_error(GOT_ERR_PRIVSEP_LEN);
-	memcpy(&ilist, imsg->data, sizeof(ilist));
 
-	if (ilist.nelem == 0)
-		return got_error(GOT_ERR_PRIVSEP_LEN);
-
-	*npaths = ilist.nelem;
 	return NULL;
 }
 
-static const struct got_error *
-recv_pathlist_elem(struct imsg *imsg, struct got_pathlist_head *paths)
-{
-	const struct got_error *err = NULL;
-	struct gotd_imsg_pathlist_elem ielem;
-	size_t datalen;
-	char *path;
-	struct got_pathlist_entry *pe;
-
-	datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
-	if (datalen < sizeof(ielem))
-		return got_error(GOT_ERR_PRIVSEP_LEN);
-	memcpy(&ielem, imsg->data, sizeof(ielem));
-
-	if (datalen != sizeof(ielem) + ielem.path_len)
-		return got_error(GOT_ERR_PRIVSEP_LEN);
-
-	path = strndup(imsg->data + sizeof(ielem), ielem.path_len);
-	if (path == NULL)
-		return got_error_from_errno("strndup");
-
-	err = got_pathlist_insert(&pe, paths, path, NULL);
-	if (err || pe == NULL)
-		free(path);
-	return err;
-}
-
 static void
 repo_write_dispatch(int fd, short event, void *arg)
 {
@@ -2819,7 +2774,7 @@ repo_write_dispatch(int fd, short event, void *arg)
 				err = got_error(GOT_ERR_PRIVSEP_MSG);
 				break;
 			}
-			err = recv_pathlist(&npaths, &imsg);
+			err = gotd_imsg_recv_pathlist(&npaths, &imsg);
 			if (err)
 				break;
 			repo_write.protected_refs_cur =
@@ -2833,7 +2788,7 @@ repo_write_dispatch(int fd, short event, void *arg)
 				err = got_error(GOT_ERR_PRIVSEP_MSG);
 				break;
 			}
-			err = recv_pathlist(&npaths, &imsg);
+			err = gotd_imsg_recv_pathlist(&npaths, &imsg);
 			if (err)
 				break;
 			repo_write.protected_refs_cur =
@@ -2847,7 +2802,7 @@ repo_write_dispatch(int fd, short event, void *arg)
 				err = got_error(GOT_ERR_PRIVSEP_MSG);
 				break;
 			}
-			err = recv_pathlist(&npaths, &imsg);
+			err = gotd_imsg_recv_pathlist(&npaths, &imsg);
 			if (err)
 				break;
 			repo_write.protected_refs_cur =
@@ -2865,7 +2820,7 @@ repo_write_dispatch(int fd, short event, void *arg)
 				err = got_error(GOT_ERR_PRIVSEP_MSG);
 				break;
 			}
-			err = recv_pathlist_elem(&imsg,
+			err = gotd_imsg_recv_pathlist_elem(&imsg,
 			    repo_write.protected_refs_cur);
 			if (err)
 				break;
blob - 013664a6ec707d98777ecf00c0180d760ca947d7
blob + d139091dacf9e073b2a167c8dca8537a36c7c461
--- gotd/session_write.c
+++ gotd/session_write.c
@@ -76,7 +76,7 @@ static struct gotd_session_write {
 	pid_t pid;
 	const char *title;
 	struct got_repository *repo;
-	struct gotd_repo *repo_cfg;
+	char repo_name[NAME_MAX];
 	int *pack_fds;
 	int *temp_fds;
 	int content_fd;
@@ -85,6 +85,12 @@ static struct gotd_session_write {
 	struct timeval request_timeout;
 	enum gotd_session_write_state state;
 	struct gotd_imsgev repo_child_iev;
+	struct got_pathlist_head notification_refs;
+	struct got_pathlist_head notification_ref_namespaces;
+	size_t num_notification_refs_needed;
+	size_t num_notification_refs_received;
+	struct got_pathlist_head *notification_refs_cur;
+	struct gotd_notification_targets notification_targets;
 } gotd_session;
 
 static struct gotd_session_client {
@@ -258,8 +264,8 @@ request_gotsys_conf(struct gotd_imsgev *iev)
 static int
 need_packfile_verification(void)
 {
-	return (strcmp(gotd_session.repo_cfg->name, "gotsys") == 0 ||
-	    strcmp(gotd_session.repo_cfg->name, "gotsys.git") == 0);
+	return (strcmp(gotd_session.repo_name, "gotsys") == 0 ||
+	    strcmp(gotd_session.repo_name, "gotsys.git") == 0);
 }
 
 static const struct got_error *
@@ -556,23 +562,22 @@ queue_notification(struct got_object_id *old_id, struc
     struct got_repository *repo, struct got_reference *ref)
 {
 	const struct got_error *err = NULL;
-	struct gotd_repo *repo_cfg = gotd_session.repo_cfg;
 	struct gotd_imsgev *iev = &gotd_session.repo_child_iev;
 	struct got_pathlist_entry *pe;
 	struct gotd_session_notif *notif;
 
 	if (iev->ibuf.fd == -1 ||
-	    STAILQ_EMPTY(&repo_cfg->notification_targets))
+	    STAILQ_EMPTY(&gotd_session.notification_targets))
 		return NULL; /* notifications unused */
 
-	RB_FOREACH(pe, got_pathlist_head, &repo_cfg->notification_refs) {
+	RB_FOREACH(pe, got_pathlist_head, &gotd_session.notification_refs) {
 		const char *refname = pe->path;
 		if (strcmp(got_ref_get_name(ref), refname) == 0)
 			break;
 	}
 	if (pe == NULL) {
 		RB_FOREACH(pe, got_pathlist_head,
-		    &repo_cfg->notification_ref_namespaces) {
+		    &gotd_session.notification_ref_namespaces) {
 			const char *namespace = pe->path;
 
 			err = validate_namespace(namespace);
@@ -589,8 +594,8 @@ queue_notification(struct got_object_id *old_id, struc
 	 * configuration file then only send notifications if a match
 	 * was found.
 	 */
-	if (pe == NULL && (!RB_EMPTY(&repo_cfg->notification_refs) ||
-	    !RB_EMPTY(&repo_cfg->notification_ref_namespaces)))
+	if (pe == NULL && (!RB_EMPTY(&gotd_session.notification_refs) ||
+	    !RB_EMPTY(&gotd_session.notification_ref_namespaces)))
 		return NULL;
 
 	notif = calloc(1, sizeof(*notif));
@@ -710,11 +715,11 @@ forward_notification(struct gotd_session_client *clien
 		goto done;
 	}
 
-	strlcpy(inotify.repo_name, gotd_session.repo_cfg->name,
+	strlcpy(inotify.repo_name, gotd_session.repo_name,
 	    sizeof(inotify.repo_name));
 
 	snprintf(inotify.subject_line, sizeof(inotify.subject_line),
-	    "%s: %s %s %s: %.12s", gotd_session.repo_cfg->name,
+	    "%s: %s %s %s: %.12s", gotd_session.repo_name,
 	    client->username, action, notif->refname, id_str);
 
 	inotify.username_len = strlen(client->username);
@@ -1732,6 +1737,12 @@ recv_repo_child(struct imsg *imsg)
 	fd = imsg_get_fd(imsg);
 	if (fd == -1)
 		return got_error(GOT_ERR_PRIVSEP_NO_FD);
+
+	if (strlcpy(gotd_session.repo_name, ichild.repo_name,
+	    sizeof(gotd_session.repo_name)) >= sizeof(gotd_session.repo_name)) {
+		return got_error_msg(GOT_ERR_NO_SPACE,
+		    "repository name too long");
+	}
 
 	if (imsgbuf_init(&gotd_session.repo_child_iev.ibuf, fd) == -1) {
 		close(fd);
@@ -1754,6 +1765,240 @@ recv_repo_child(struct imsg *imsg)
 }
 
 static void
+free_notification_target(struct gotd_notification_target *target)
+{
+	if (target == NULL)
+		return;
+
+	switch (target->type) {
+	case GOTD_NOTIFICATION_VIA_EMAIL:
+		free(target->conf.email.sender);
+		free(target->conf.email.recipient);
+		free(target->conf.email.responder);
+		free(target->conf.email.hostname);
+		free(target->conf.email.port);
+		break;
+	case GOTD_NOTIFICATION_VIA_HTTP:
+		free(target->conf.http.hostname);
+		free(target->conf.http.port);
+		free(target->conf.http.path);
+		free(target->conf.http.auth);
+		free(target->conf.http.hmac);
+		break;
+	default:
+		break;
+	}
+
+	free(target);
+}
+
+static const struct got_error *
+recv_notification_target_email(struct imsg *imsg)
+{
+	const struct got_error *err = NULL;
+	struct gotd_imsg_notitfication_target_email itarget;
+	struct gotd_notification_target *target;
+	size_t datalen;
+
+	datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+	if (datalen < sizeof(itarget))
+		return got_error(GOT_ERR_PRIVSEP_LEN);
+	memcpy(&itarget, imsg->data, sizeof(itarget));
+
+	if (datalen != sizeof(itarget) + itarget.sender_len +
+	    itarget.recipient_len + itarget.responder_len +
+	    itarget.hostname_len + itarget.port_len)
+		return got_error(GOT_ERR_PRIVSEP_LEN);
+	if (itarget.recipient_len == 0)
+		return got_error(GOT_ERR_PRIVSEP_LEN);
+
+	target = calloc(1, sizeof(*target));
+	if (target == NULL)
+		return got_error_from_errno("calloc");
+
+	target->type = GOTD_NOTIFICATION_VIA_EMAIL;
+
+	if (itarget.sender_len) {
+		target->conf.email.sender = strndup(imsg->data +
+		    sizeof(itarget), itarget.sender_len);
+		if (target->conf.email.sender == NULL) {
+			err = got_error_from_errno("strndup");
+			goto done;
+		}
+		if (strlen(target->conf.email.sender) != itarget.sender_len) {
+			err = got_error(GOT_ERR_PRIVSEP_LEN);
+			goto done;
+		}
+	}
+
+	target->conf.email.recipient = strndup(imsg->data + sizeof(itarget) +
+	    itarget.sender_len, itarget.recipient_len);
+	if (target->conf.email.recipient == NULL) {
+		err = got_error_from_errno("strndup");
+		goto done;
+	}
+	if (strlen(target->conf.email.recipient) != itarget.recipient_len) {
+		err = got_error(GOT_ERR_PRIVSEP_LEN);
+		goto done;
+	}
+	
+	if (itarget.responder_len) {
+		target->conf.email.responder = strndup(imsg->data +
+		    sizeof(itarget) + itarget.sender_len + itarget.recipient_len,
+		    itarget.responder_len);
+		if (target->conf.email.responder == NULL) {
+			err = got_error_from_errno("strndup");
+			goto done;
+		}
+		if (strlen(target->conf.email.responder) !=
+		    itarget.responder_len) {
+			err = got_error(GOT_ERR_PRIVSEP_LEN);
+			goto done;
+		}
+	}
+
+	if (itarget.hostname_len) {
+		target->conf.email.hostname = strndup(imsg->data +
+		    sizeof(itarget) + itarget.sender_len +
+		    itarget.recipient_len + itarget.responder_len,
+		    itarget.hostname_len);
+		if (target->conf.email.hostname == NULL) {
+			err = got_error_from_errno("strndup");
+			goto done;
+		}
+		if (strlen(target->conf.email.hostname) !=
+		    itarget.hostname_len) {
+			err = got_error(GOT_ERR_PRIVSEP_LEN);
+			goto done;
+		}
+	}
+
+	if (itarget.port_len) {
+		target->conf.email.port = strndup(imsg->data +
+		    sizeof(itarget) + itarget.sender_len +
+		    itarget.recipient_len + itarget.responder_len +
+		    itarget.hostname_len, itarget.port_len);
+		if (target->conf.email.port == NULL) {
+			err = got_error_from_errno("strndup");
+			goto done;
+		}
+		if (strlen(target->conf.email.port) != itarget.port_len) {
+			err = got_error(GOT_ERR_PRIVSEP_LEN);
+			goto done;
+		}
+	}
+
+	STAILQ_INSERT_TAIL(&gotd_session.notification_targets, target, entry);
+	target = NULL;
+done:
+	if (err)
+		free_notification_target(target);
+	return err;
+}
+
+static const struct got_error *
+recv_notification_target_http(struct imsg *imsg)
+{
+	const struct got_error *err = NULL;
+	struct gotd_imsg_notitfication_target_http itarget;
+	struct gotd_notification_target *target;
+	size_t datalen;
+
+	datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+	if (datalen < sizeof(itarget))
+		return got_error(GOT_ERR_PRIVSEP_LEN);
+	memcpy(&itarget, imsg->data, sizeof(itarget));
+
+	if (datalen != sizeof(itarget) + itarget.hostname_len +
+	    itarget.port_len + itarget.path_len + itarget.auth_len +
+	    itarget.hmac_len)
+		return got_error(GOT_ERR_PRIVSEP_LEN);
+
+	if (itarget.hostname_len == 0 || itarget.port_len == 0 ||
+	    itarget.path_len == 0)
+		return got_error(GOT_ERR_PRIVSEP_LEN);
+
+	target = calloc(1, sizeof(*target));
+	if (target == NULL)
+		return got_error_from_errno("calloc");
+
+	target->type = GOTD_NOTIFICATION_VIA_HTTP;
+
+	target->conf.http.tls = itarget.tls;
+
+	target->conf.http.hostname = strndup(imsg->data +
+	    sizeof(itarget), itarget.hostname_len);
+	if (target->conf.http.hostname == NULL) {
+		err = got_error_from_errno("strndup");
+		goto done;
+	}
+	if (strlen(target->conf.http.hostname) != itarget.hostname_len) {
+		err = got_error(GOT_ERR_PRIVSEP_LEN);
+		goto done;
+	}
+
+	target->conf.http.port = strndup(imsg->data + sizeof(itarget) +
+	    itarget.hostname_len, itarget.port_len);
+	if (target->conf.http.port == NULL) {
+		err = got_error_from_errno("strndup");
+		goto done;
+	}
+	if (strlen(target->conf.http.port) != itarget.port_len) {
+		err = got_error(GOT_ERR_PRIVSEP_LEN);
+		goto done;
+	}
+	
+	target->conf.http.path = strndup(imsg->data +
+	    sizeof(itarget) + itarget.hostname_len + itarget.port_len,
+	    itarget.path_len);
+	if (target->conf.http.path == NULL) {
+		err = got_error_from_errno("strndup");
+		goto done;
+	}
+	if (strlen(target->conf.http.path) != itarget.path_len) {
+		err = got_error(GOT_ERR_PRIVSEP_LEN);
+		goto done;
+	}
+
+	if (itarget.auth_len) {
+		target->conf.http.auth = strndup(imsg->data +
+		    sizeof(itarget) + itarget.hostname_len +
+		    itarget.port_len + itarget.path_len,
+		    itarget.auth_len);
+		if (target->conf.http.auth == NULL) {
+			err = got_error_from_errno("strndup");
+			goto done;
+		}
+		if (strlen(target->conf.http.auth) != itarget.auth_len) {
+			err = got_error(GOT_ERR_PRIVSEP_LEN);
+			goto done;
+		}
+	}
+
+	if (itarget.hmac_len) {
+		target->conf.http.hmac = strndup(imsg->data +
+		    sizeof(itarget) + itarget.hostname_len +
+		    itarget.port_len + itarget.path_len +
+		    itarget.auth_len, itarget.hmac_len);
+		if (target->conf.http.hmac == NULL) {
+			err = got_error_from_errno("strndup");
+			goto done;
+		}
+		if (strlen(target->conf.http.hmac) != itarget.hmac_len) {
+			err = got_error(GOT_ERR_PRIVSEP_LEN);
+			goto done;
+		}
+	}
+
+	STAILQ_INSERT_TAIL(&gotd_session.notification_targets, target, entry);
+	target = NULL;
+done:
+	if (err)
+		free_notification_target(target);
+	return err;
+}
+
+static void
 session_dispatch(int fd, short event, void *arg)
 {
 	const struct got_error *err = NULL;
@@ -1764,6 +2009,7 @@ session_dispatch(int fd, short event, void *arg)
 	ssize_t n;
 	int shut = 0;
 	struct imsg imsg;
+	size_t npaths;
 
 	if (event & EV_READ) {
 		if ((n = imsgbuf_read(ibuf)) == -1)
@@ -1802,7 +2048,61 @@ session_dispatch(int fd, short event, void *arg)
 			break;
 		case GOTD_IMSG_DISCONNECT:
 			do_disconnect = 1;
+			break;
+		case GOTD_IMSG_NOTIFICATION_REFS:
+			if (gotd_session.notification_refs_cur != NULL ||
+			    gotd_session.num_notification_refs_needed != 0) {
+				err = got_error(GOT_ERR_PRIVSEP_MSG);
+				break;
+			}
+			err = gotd_imsg_recv_pathlist(&npaths, &imsg);
+			if (err)
+				break;
+			gotd_session.notification_refs_cur =
+			    &gotd_session.notification_refs;
+			gotd_session.num_notification_refs_needed = npaths;
+			gotd_session.num_notification_refs_received = 0;
+			break;
+		case GOTD_IMSG_NOTIFICATION_REF_NAMESPACES:
+			if (gotd_session.notification_refs_cur != NULL ||
+			    gotd_session.num_notification_refs_needed != 0) {
+				err = got_error(GOT_ERR_PRIVSEP_MSG);
+				break;
+			}
+			err = gotd_imsg_recv_pathlist(&npaths, &imsg);
+			if (err)
+				break;
+			gotd_session.notification_refs_cur =
+			    &gotd_session.notification_ref_namespaces;
+			gotd_session.num_notification_refs_needed = npaths;
+			gotd_session.num_notification_refs_received = 0;
 			break;
+			break;
+		case GOTD_IMSG_NOTIFICATION_REFS_ELEM:
+		case GOTD_IMSG_NOTIFICATION_REF_NAMESPACES_ELEM:
+			if (gotd_session.notification_refs_cur == NULL ||
+			    gotd_session.num_notification_refs_needed == 0 ||
+			    gotd_session.num_notification_refs_received >=
+			    gotd_session.num_notification_refs_needed) {
+				err = got_error(GOT_ERR_PRIVSEP_MSG);
+				break;
+			}
+			err = gotd_imsg_recv_pathlist_elem(&imsg,
+			    gotd_session.notification_refs_cur);
+			if (err)
+				break;
+			if (++gotd_session.num_notification_refs_received >=
+			    gotd_session.num_notification_refs_needed) {
+				gotd_session.notification_refs_cur = NULL;
+				gotd_session.num_notification_refs_needed = 0;
+			}
+			break;
+		case GOTD_IMSG_NOTIFICATION_TARGET_EMAIL:
+			err = recv_notification_target_email(&imsg);
+			break;
+		case GOTD_IMSG_NOTIFICATION_TARGET_HTTP:
+			err = recv_notification_target_http(&imsg);
+			break;
 		case GOTD_IMSG_CONNECT_NOTIFIER:
 			err = recv_notifier(&imsg);
 			break;
@@ -1883,8 +2183,7 @@ done:
 
 void
 session_write_main(const char *title, const char *repo_path,
-    int *pack_fds, int *temp_fds, int content_fd,
-    struct timeval *request_timeout, struct gotd_repo *repo_cfg)
+    int *pack_fds, int *temp_fds, int content_fd)
 {
 	const struct got_error *err = NULL;
 	struct event evsigint, evsigterm, evsighup, evsigusr1;
@@ -1896,9 +2195,11 @@ session_write_main(const char *title, const char *repo
 	gotd_session.pack_fds = pack_fds;
 	gotd_session.temp_fds = temp_fds;
 	gotd_session.content_fd = content_fd;
-	memcpy(&gotd_session.request_timeout, request_timeout,
-	    sizeof(gotd_session.request_timeout));
-	gotd_session.repo_cfg = repo_cfg;
+	gotd_session.request_timeout.tv_sec = GOTD_DEFAULT_REQUEST_TIMEOUT;
+	gotd_session.request_timeout.tv_usec = 0;
+	RB_INIT(&gotd_session.notification_refs);
+	RB_INIT(&gotd_session.notification_ref_namespaces);
+	STAILQ_INIT(&gotd_session.notification_targets);
 
 	if (imsgbuf_init(&gotd_session.notifier_iev.ibuf, -1) == -1) {
 		err = got_error_from_errno("imsgbuf_init");
blob - ee305618aa6d63a642f30434e275cd9891eef086
blob + d49eece585f1b9c8fb4214f0727c03bceda85676
--- gotd/session_write.h
+++ gotd/session_write.h
@@ -14,5 +14,4 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-void session_write_main(const char *, const char *, int *, int *, int,
-    struct timeval *, struct gotd_repo *);
+void session_write_main(const char *, const char *, int *, int *, int);