Subject: [RFC] Secure central repositories by UNIX socket authentication

From: "Shawn O. Pearce" <[email-masked]>
Date: Sun, 27 Jan 2008 05:39:35 -0500

This isn't anywhere near ready for application, but I'm floating
it out there to see what people think.  Its a cool new feature that
will certainly *not* be in 1.5.4.  :-)

In a central repository configuration users may not have access
to write new objects into a Git repository, or to edit the refs,
especially if the repository is being protected by an update hook
(e.g. contrib/hooks/updated-paranoid).

This change allows any repository owner to setup a git-daemon
that other users on the same host can connect through to perform
upload-pack or receive-pack.  It also allows the update hook to
authorize the user via $GIT_REMOTE_USER.  For example:

    ## Owner (not jdoe)
    ##
  cat >foo.git/hooks/update <<'EOF'
  #!/bin/sh
  test -z "$GIT_REMOTE_USER" || exit
  case "$GIT_REMOTE_USER" in
  jdoe)     exit 0;;
  spearce)  exit 0;;
  *)        exit 1
  esac
  EOF
  chmod u+x foo.git/hooks/update
  chmod 700 foo.git

  git daemon \
      --export-all \
      --enable=receive-pack \
      --base=`pwd` \
      --listen=/tmp/shawn-git

    ## Other User
    ##
  git push jdoe@server:/tmp/shawn-git/foo.git master

allows user jdoe to push into foo.git, even though it is restricted.
This works by:

  *) git-push executes
     "ssh jdoe@server git-receive-pack /tmp/shawn-git/foo.git"

  *) git-receive-pack realizes its actually a socket, connects
     to /tmp/shawn-git and asks for service "git-receive-pack"
     on directory "/foo.git"

  *) git-daemon authenticates the user via the kernel's secure
     peer id call and sets GIT_REMOTE_USER with their username.

  *) git-daemon starts another git-receive-pack on foo.git, using
     its normal access control and path resolving rules.

  *) The first git-receive-pack acts as a shuttle to copy
     data between the two peers.

  *) The second git-receive-pack runs the update hook, which has
     GIT_REMOTE_USER defined from git-daemon.

There's a few small problems with this, which is partly why this
isn't ready for application yet:

1) Error messages from the pre-receive or update hooks don't make it
   back to the client as send-pack and receive-pack don't implement
   the sideband protocol.  This is harder than it sounds as the
   pkt-line protocol they currently use for status can't be easily
   layered into a sideband protocol.

2) There is currently no "authorization" hook for upload-pack (aka
   the fetch server) like we have for receive-pack (aka the push
   server) so we cannot make use of $GIT_REMOTE_USER to perform
   user authorization.  Currently Git relies on UNIX filesystem
   access, but this daemon approach bypasses those.  There was a
   conversation on #git tonight about adding a hook that filters
   the list of allowed refs during fetch, for exactly this reason.

3) The system call to get the peer UID via the kernel is not POSIX
   so its implemented differently on basically every OS we support,
   if it is even implemented there.  The patch below should provide
   support for Solaris 8/9/10, FreeBSD, Darwin/Mac OS X, and Linux.
   But is thus far only tested on Mac OS 10.4.

---

 Documentation/git-daemon.txt |   11 ++-
 Makefile                     |   14 ++-
 builtin-upload-archive.c     |    3 +
 daemon-redirect.c            |  282 ++++++++++++++++++++++++++++++++++++++++++
 daemon-redirect.h            |   12 ++
 daemon.c                     |  132 +++++++++++++++-----
 git-compat-util.h            |    1 +
 receive-pack.c               |    3 +
 upload-pack.c                |    3 +
 9 files changed, 429 insertions(+), 32 deletions(-)
 create mode 100644 daemon-redirect.c
 create mode 100644 daemon-redirect.h

diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt
index fd83bc7..1f9d9a1 100644
--- a/Documentation/git-daemon.txt
+++ b/Documentation/git-daemon.txt
@@ -15,7 +15,7 @@ SYNOPSIS
              [--reuseaddr] [--detach] [--pid-file=file]
              [--enable=service] [--disable=service]
 	     [--allow-override=service] [--forbid-override=service]
-	     [--inetd | [--listen=host_or_ipaddr] [--port=n] [--user=user [--group=group]]
+	     [--inetd | [--listen=host_or_ipaddr_or_path] [--port=n] [--user=user [--group=group]]
 	     [directory...]
 
 DESCRIPTION
@@ -79,12 +79,19 @@ OPTIONS
 	Have the server run as an inetd service. Implies --syslog.
 	Incompatible with --port, --listen, --user and --group options.
 
---listen=host_or_ipaddr::
+--listen=host_or_ipaddr_or_path::
 	Listen on an a specific IP address or hostname.  IP addresses can
 	be either an IPv4 address or an IPV6 address if supported.  If IPv6
 	is not supported, then --listen=hostname is also not supported and
 	--listen must be given an IPv4 address.
 	Incompatible with '--inetd' option.
++
+If the address is an absolute path in the filesystem then git-daemon
+listens on a UNIX domain socket at the given path.  This can be used
+to support authenticated redirection from linkgit:git-upload-pack[1]
+or linkgit:git-receive-pack[1].  Child processes (including hooks)
+can obtain the authenticated username from the `GIT_REMOTE_USER`
+environment variable.
 
 --port=n::
 	Listen on an alternative port.  Incompatible with '--inetd' option.
diff --git a/Makefile b/Makefile
index a0c0998..6f8f3ad 100644
--- a/Makefile
+++ b/Makefile
@@ -295,7 +295,8 @@ LIB_H = \
 	run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
 	tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \
 	utf8.h reflog-walk.h patch-ids.h attr.h decorate.h progress.h \
-	mailmap.h remote.h parse-options.h transport.h diffcore.h hash.h
+	mailmap.h remote.h parse-options.h transport.h diffcore.h hash.h \
+	daemon-redirect.h
 
 DIFF_OBJS = \
 	diff.o diff-lib.o diffcore-break.o diffcore-order.o \
@@ -309,6 +310,7 @@ LIB_OBJS = \
 	lockfile.o \
 	patch-ids.o \
 	object.o pack-check.o pack-write.o patch-delta.o path.o pkt-line.o \
+	daemon-redirect.o \
 	sideband.o reachable.o reflog-walk.o \
 	quote.o read-cache.o refs.o run-command.o dir.o object-refs.o \
 	server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
@@ -417,6 +419,7 @@ ifeq ($(uname_S),Darwin)
 	endif
 	NO_STRLCPY = YesPlease
 	NO_MEMMEM = YesPlease
+	HAVE_GETPEEREID = YesPlease
 endif
 ifeq ($(uname_S),SunOS)
 	NEEDS_SOCKET = YesPlease
@@ -426,6 +429,7 @@ ifeq ($(uname_S),SunOS)
 	NO_MEMMEM = YesPlease
 	NO_HSTRERROR = YesPlease
 	NO_MKDTEMP = YesPlease
+	HAVE_STREAMS_CONNLD = YesPlease
 	ifeq ($(uname_R),5.8)
 		NEEDS_LIBICONV = YesPlease
 		NO_UNSETENV = YesPlease
@@ -463,6 +467,7 @@ endif
 ifeq ($(uname_S),FreeBSD)
 	NEEDS_LIBICONV = YesPlease
 	NO_MEMMEM = YesPlease
+	HAVE_GETPEEREID = YesPlease
 	BASIC_CFLAGS += -I/usr/local/include
 	BASIC_LDFLAGS += -L/usr/local/lib
 endif
@@ -470,6 +475,7 @@ ifeq ($(uname_S),OpenBSD)
 	NO_STRCASESTR = YesPlease
 	NO_MEMMEM = YesPlease
 	NEEDS_LIBICONV = YesPlease
+	HAVE_GETPEEREID = YesPlease
 	BASIC_CFLAGS += -I/usr/local/include
 	BASIC_LDFLAGS += -L/usr/local/lib
 endif
@@ -659,6 +665,12 @@ else
 	BASIC_CFLAGS += -Dsockaddr_storage=sockaddr_in6
 endif
 endif
+ifdef HAVE_STREAMS_CONNLD
+	BASIC_CFLAGS += -DHAVE_STREAMS_CONNLD
+endif
+ifdef HAVE_GETPEEREID
+	BASIC_CFLAGS += -DHAVE_GETPEEREID
+endif
 ifdef NO_INET_NTOP
 	LIB_OBJS += compat/inet_ntop.o
 endif
diff --git a/builtin-upload-archive.c b/builtin-upload-archive.c
index 48ae09e..5599e3b 100644
--- a/builtin-upload-archive.c
+++ b/builtin-upload-archive.c
@@ -5,6 +5,7 @@
 #include "builtin.h"
 #include "archive.h"
 #include "pkt-line.h"
+#include "daemon-redirect.h"
 #include "sideband.h"
 
 static const char upload_archive_usage[] =
@@ -35,6 +36,8 @@ static int run_upload_archive(int argc, const char **argv, const char *prefix)
 
 	strcpy(buf, argv[1]); /* enter-repo smudges its argument */
 
+	if (redirect_execute("upload-archive", buf))
+		return 0;
 	if (!enter_repo(buf, 0))
 		die("not a git archive");
 
diff --git a/daemon-redirect.c b/daemon-redirect.c
new file mode 100644
index 0000000..8474ddd
--- /dev/null
+++ b/daemon-redirect.c
@@ -0,0 +1,282 @@
+#include "daemon-redirect.h"
+#include "pkt-line.h"
+
+static int redirect_connect(const char *path);
+
+#ifdef HAVE_STREAMS_CONNLD
+
+int redirect_listen(const char *path)
+{
+	int fd_pair[2];
+
+	if (pipe(fd_pair) || ioctl(fd_pair[0], I_PUSH, "connld"))
+		die("redirect listen failed: %s", strerror(errno));
+	ioctl(fd_pair[0], I_CANPUT, 0);
+
+	unlink(path);
+	if (close(creat(path, 0666)) || fattach(fd_pair[0], path))
+		die("redirect listen failed on %s: %s", path, strerror(errno));
+	close(fd_pair[0]); /* can i really do that? */
+	return fd_pair[1];
+}
+
+int redirect_accept(int srv_fd, uid_t *cli_uid)
+{
+	struct strrecvfd rfd;
+
+	if (ioctl(srv_fd, I_RECVFD, &rfd))
+		return -1;
+	*cli_uid = rfd.uid;
+	return rfd.fd;
+}
+
+static int redirect_connect(const char *path)
+{
+	int cli_fd = open(path, O_RDWR);
+	if (cli_fd < 0)
+		die("redirect connect failed on %s: %s", path, strerror(errno));
+	return cli_fd;
+}
+
+#else /* ! HAVE_STREAMS_CONNLD */
+
+int redirect_listen(const char *path)
+{
+	int srv_fd;
+	struct sockaddr_un addr;
+
+	if (strlen(path) > sizeof(addr.sun_path))
+		die("redirect listen path too long");
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sun_family = AF_UNIX;
+	strcpy(addr.sun_path, path);
+	unlink(path);
+
+	srv_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+	if (srv_fd < 0)
+		die("redirect listen failed: %s", strerror(errno));
+	if (bind(srv_fd, (struct sockaddr*)&addr, sizeof(addr)))
+		die("redirect listen failed on %s: %s", path, strerror(errno));
+	if (listen(srv_fd, 10))
+		die("redirect listen failed: %s", strerror(errno));
+
+	return srv_fd;
+}
+
+static int peer_uid(int cli_fd, uid_t *cli_uid);
+
+#if defined(HAVE_GETPEEREID) /* OpenBSD / FreeBSD / Darwin */
+static int peer_uid(int cli_fd, uid_t *cli_uid)
+{
+	gid_t cli_gid;
+	return getpeereid(cli_fd, cli_uid, &cli_gid);
+}
+#elif defined(SO_PEERCRED) /* Linux */
+static int peer_uid(int cli_fd, uid_t *cli_uid)
+{
+	struct ucred creds;
+	int len = sizeof(creds);
+	if (getsockopt(cli_fd, SOL_SOCKET, SO_PEERCRED, &creds, &len))
+		return -1;
+	*cli_uid = creds.uid;
+}
+#else /* Not supported */
+static int peer_uid(int cli_fd, uid_t *cli_uid)
+{
+	error("UNIX peer authentication not compiled in");
+	errno = ENOSYS;
+	return -1;
+}
+#endif
+
+int redirect_accept(int srv_fd, uid_t *cli_uid)
+{
+	int cli_fd;
+	struct sockaddr_un ss;
+	unsigned int sslen = sizeof(ss);
+
+	cli_fd = accept(srv_fd, (struct sockaddr*)&ss, &sslen);
+	if (cli_fd < 0)
+		return -1;
+
+	if (peer_uid(cli_fd, cli_uid)) {
+		error("redirect accept creds failed: %s", strerror(errno));
+		close(cli_fd);
+		errno = ECONNABORTED;
+		return -1;
+	}
+
+	return cli_fd;
+}
+
+static int redirect_connect(const char *path)
+{
+	int cli_fd;
+	struct sockaddr_un addr;
+
+	if (strlen(path) > sizeof(addr.sun_path))
+		die("redirect socket path too long");
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sun_family = AF_UNIX;
+	strcpy(addr.sun_path, path);
+
+	cli_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+	if (cli_fd < 0)
+		die("redirect connect failed: %s", strerror(errno));
+	if (connect(cli_fd, (struct sockaddr*)&addr, sizeof(addr)))
+		die("redirect connect failed on %s: %s", path, strerror(errno));
+	return cli_fd;
+}
+
+#endif
+
+struct fd_copy_buffer {
+	int live;
+	int in_fd;
+	int out_fd;
+	size_t avail;
+	size_t off;
+	char buf[4096];
+};
+
+static int buf_poll(struct pollfd *p, struct fd_copy_buffer *b)
+{
+	if (!b->live)
+		return 0;
+
+	if (b->avail) {
+		p->fd = b->out_fd;
+		p->events = POLLOUT;
+	} else {
+		p->fd = b->in_fd;
+		p->events = POLLIN;
+	}
+	return 1;
+}
+
+static int buf_copy(struct pollfd *p, struct fd_copy_buffer *b)
+{
+	if (!b->live)
+		return 0;
+
+	if (p->revents & POLLIN) {
+		b->avail = read(b->in_fd, b->buf, sizeof(b->buf));
+		b->off = 0;
+		if (!b->avail) {
+			b->live = 0;
+			close(b->in_fd);
+			shutdown(b->out_fd, 1);
+		} else if (b->avail < 0)
+			die("input failure: %s", strerror(errno));
+	}
+
+	if (p->revents & POLLOUT) {
+		ssize_t r = write(b->out_fd, b->buf + b->off, b->avail);
+		if (r <= 0)
+			die("output failure: %s", strerror(errno));
+		b->avail -= r;
+		b->off += r;
+	}
+
+	return 1;
+}
+
+static void execute(
+	const char *service,
+	const char *sock_name,
+	const char *repo_name)
+{
+	int cli_fd;
+	struct pollfd pfd[2];
+	struct fd_copy_buffer in_buf, out_buf;
+
+	cli_fd = redirect_connect(sock_name);
+	packet_write(cli_fd, "git-%s /%s", service, repo_name);
+
+	in_buf.live = 1;
+	in_buf.in_fd = 0;
+	in_buf.out_fd = cli_fd;
+	in_buf.avail = 0;
+
+	out_buf.live = 1;
+	out_buf.in_fd = cli_fd;
+	out_buf.out_fd = 1;
+	out_buf.avail = 0;
+
+	while (out_buf.live) {
+		int cnt = 0;
+
+		cnt += buf_poll(&pfd[cnt], &in_buf);
+		cnt += buf_poll(&pfd[cnt], &out_buf);
+
+		if (poll(pfd, cnt, -1) < 0) {
+			if (errno != EINTR) {
+				error("poll failed, resuming: %s", strerror(errno));
+				sleep(1);
+			}
+			continue;
+		}
+
+		cnt = 0;
+		cnt += buf_copy(&pfd[cnt], &in_buf);
+		cnt += buf_copy(&pfd[cnt], &out_buf);
+	}
+
+	close(cli_fd);
+}
+
+int redirect_execute(const char *service, const char *path)
+{
+	struct stat sb;
+	char *sock_name, *s, *repo_name;
+
+	/* Exists and is a directory?  Assume the caller is
+	 * the named service and can directly access this
+	 * location itself, without redirect sockets.
+	 */
+	if (!stat(path, &sb) && S_ISDIR(sb.st_mode))
+		return 0;
+	s = strrchr(path, '/');
+	if (!s)
+		return 0;
+
+	sock_name = xstrdup(path);
+	s = sock_name + (s - path);
+	*s = 0;
+	repo_name = s + 1;
+
+	for (;;) {
+		if (!stat(sock_name, &sb)) {
+			if (S_ISDIR(sb.st_mode))
+				break;
+			if (S_ISSOCK(sb.st_mode)) {
+				execute(service, sock_name, repo_name);
+				free(sock_name);
+				return 1;
+			}
+		}
+
+		s = strrchr(sock_name, '/');
+		if (!s || s == sock_name)
+			break;
+
+		*s = 0;
+		*(--repo_name) = '/';
+		repo_name = s + 1;
+	}
+
+	/* Assume redirection shouldn't be applied and that the
+	 * path is also completely incorrect.  Allow the caller
+	 * to handle this error condition as before.
+	 */
+	free(sock_name);
+	return 0;
+}
+
+void redirect_unlisten(int srv_fd, const char *path)
+{
+	close(srv_fd);
+	unlink(path);
+}
diff --git a/daemon-redirect.h b/daemon-redirect.h
new file mode 100644
index 0000000..5d85e10
--- /dev/null
+++ b/daemon-redirect.h
@@ -0,0 +1,12 @@
+#ifndef DAEMON_REDIRECT_H
+#define DAEMON_REDIRECT_H
+
+#include "git-compat-util.h"
+
+extern int redirect_listen(const char *path);
+extern int redirect_accept(int, uid_t *);
+extern void redirect_unlisten(int, const char *);
+
+extern int redirect_execute(const char *, const char *);
+
+#endif
diff --git a/daemon.c b/daemon.c
index 41a60af..6c4897d 100644
--- a/daemon.c
+++ b/daemon.c
@@ -1,4 +1,5 @@
 #include "cache.h"
+#include "daemon-redirect.h"
 #include "pkt-line.h"
 #include "exec_cmd.h"
 #include "interpolate.h"
@@ -16,6 +17,7 @@
 static int log_syslog;
 static int verbose;
 static int reuseaddr;
+static char *listen_addr;
 
 static const char daemon_usage[] =
 "git-daemon [--verbose] [--syslog] [--export-all]\n"
@@ -25,7 +27,7 @@ static const char daemon_usage[] =
 "           [--interpolated-path=path]\n"
 "           [--reuseaddr] [--detach] [--pid-file=file]\n"
 "           [--[enable|disable|allow-override|forbid-override]=service]\n"
-"           [--inetd | [--listen=host_or_ipaddr] [--port=n]\n"
+"           [--inetd | [--listen=host_or_ipaddr_or_path] [--port=n]\n"
 "                      [--user=user [--group=group]]\n"
 "           [directory...]";
 
@@ -318,13 +320,24 @@ static int git_daemon_config(const char *var, const char *value)
 	return 0;
 }
 
-static int run_service(struct interp *itable, struct daemon_service *service)
+static int run_service(struct interp *itable, struct daemon_service *service, uid_t uid)
 {
 	const char *path;
+	struct passwd *pw = getpwuid(uid);
 	int enabled = service->enabled;
+	char *pw_name;
 
-	loginfo("Request %s for '%s'",
+	if (pw)
+		pw_name = xstrdup(pw->pw_name);
+	else {
+		pw_name = xmalloc(64);
+		snprintf(pw_name, 64, "uid %u", uid);
+	}
+	setenv("GIT_REMOTE_USER", pw_name, 1);
+
+	loginfo("Request %s for %s in '%s'",
 		service->name,
+		pw_name,
 		itable[INTERP_SLOT_DIR].value);
 
 	if (!enabled && !service->overridable) {
@@ -530,7 +543,7 @@ static void fill_in_extra_table_entries(struct interp *itable)
 }

-static int execute(struct sockaddr *addr)
+static int execute(struct sockaddr *addr, uid_t uid)
 {
 	static char line[1000];
 	int pktlen, len, i;
@@ -595,7 +608,7 @@ static int execute(struct sockaddr *addr)
 			 */
 			interp_set_entry(interp_table,
 					 INTERP_SLOT_DIR, line + namelen + 5);
-			return run_service(interp_table, s);
+			return run_service(interp_table, s, uid);
 		}
 	}
 
@@ -636,7 +649,10 @@ static void add_child(int idx, pid_t pid, struct sockaddr *addr, int addrlen)
 {
 	live_child[idx].pid = pid;
 	live_child[idx].addrlen = addrlen;
-	memcpy(&live_child[idx].address, addr, addrlen);
+	if (addr)
+		memcpy(&live_child[idx].address, addr, addrlen);
+	else
+		memset(&live_child[idx].address, 0, sizeof(live_child[idx].address));
 }
 
 /*
@@ -726,7 +742,7 @@ static void check_max_connections(void)
 	}
 }
 
-static void handle(int incoming, struct sockaddr *addr, int addrlen)
+static void handle(int incoming, struct sockaddr *addr, int addrlen, uid_t uid)
 {
 	pid_t pid = fork();
 
@@ -749,7 +765,7 @@ static void handle(int incoming, struct sockaddr *addr, int addrlen)
 	dup2(incoming, 1);
 	close(incoming);
 
-	exit(execute(addr));
+	exit(execute(addr, uid));
 }
 
 static void child_handler(int signo)
@@ -790,7 +806,7 @@ static int set_reuse_addr(int sockfd)
 
 #ifndef NO_IPV6
 
-static int socksetup(char *listen_addr, int listen_port, int **socklist_p)
+static int socksetup(int listen_port, int **socklist_p)
 {
 	int socknum = 0, *socklist = NULL;
 	int maxfd = -1;
@@ -864,7 +880,7 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p)
 
 #else /* NO_IPV6 */
 
-static int socksetup(char *listen_addr, int listen_port, int **socklist_p)
+static int socksetup(int listen_port, int **socklist_p)
 {
 	struct sockaddr_in sin;
 	int sockfd;
@@ -912,8 +928,15 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p)
 
 #endif
 
+static volatile int end_daemon;
+static void term_handler(int signo)
+{
+	end_daemon = 1;
+}
+
 static int service_loop(int socknum, int *socklist)
 {
+	uid_t uid = getuid();
 	struct pollfd *pfd;
 	int i;
 
@@ -924,9 +947,7 @@ static int service_loop(int socknum, int *socklist)
 		pfd[i].events = POLLIN;
 	}
 
-	signal(SIGCHLD, child_handler);
-
-	for (;;) {
+	while (!end_daemon) {
 		int i;
 
 		if (poll(pfd, socknum, -1) < 0) {
@@ -950,13 +971,36 @@ static int service_loop(int socknum, int *socklist)
 					case ECONNABORTED:
 						continue;
 					default:
-						die("accept returned %s", strerror(errno));
+						error("accept returned %s", strerror(errno));
+						return 1;
 					}
 				}
-				handle(incoming, (struct sockaddr *)&ss, sslen);
+				handle(incoming, (struct sockaddr *)&ss, sslen, uid);
 			}
 		}
 	}
+	return 0;
+}
+
+static int redirect_loop(int srv_fd)
+{
+	while (!end_daemon) {
+		uid_t uid;
+		int incoming = redirect_accept(srv_fd, &uid);
+		if (incoming < 0) {
+			switch (errno) {
+			case EAGAIN:
+			case EINTR:
+			case ECONNABORTED:
+				continue;
+			default:
+				error("accept returned %s", strerror(errno));
+				return 1;
+			}
+		}
+		handle(incoming, NULL, 0, uid);
+	}
+	return 0;
 }
 
 /* if any standard file descriptor is missing open it to /dev/null */
@@ -998,27 +1042,51 @@ static void store_pid(const char *path)
 		die("failed to write pid file %s: %s", path, strerror(errno));
 }
 
-static int serve(char *listen_addr, int listen_port, struct passwd *pass, gid_t gid)
+static int serve(int listen_port, struct passwd *pass, gid_t gid)
 {
-	int socknum, *socklist;
-
-	socknum = socksetup(listen_addr, listen_port, &socklist);
-	if (socknum == 0)
-		die("unable to allocate any listen sockets on host %s port %u",
-		    listen_addr, listen_port);
+	int socknum, *socklist, status;
+	int is_redirect = is_absolute_path(listen_addr);
+	struct sigaction sa;
+
+	if (is_redirect) {
+		socknum = 1;
+		socklist = xmalloc(sizeof(int));
+		socklist[0] = redirect_listen(listen_addr);
+	} else {
+		socknum = socksetup(listen_port, &socklist);
+		if (socknum == 0)
+			die("unable to allocate any listen sockets"
+				" on host %s port %u",
+				listen_addr, listen_port);
+	}
 
 	if (pass && gid &&
 	    (initgroups(pass->pw_name, gid) || setgid (gid) ||
 	     setuid(pass->pw_uid)))
 		die("cannot drop privileges");
 
-	return service_loop(socknum, socklist);
+	signal(SIGCHLD, child_handler);
+
+	memset(&sa, 0, sizeof(sa));
+	sa.sa_handler = term_handler;
+	sigemptyset(&sa.sa_mask);
+	sigaction(SIGINT, &sa, NULL);
+	sigaction(SIGTERM, &sa, NULL);
+
+	if (is_redirect) {
+		status = redirect_loop(socklist[0]);
+		redirect_unlisten(socklist[0], listen_addr);
+	} else {
+		status = service_loop(socknum, socklist);
+		while (--socknum >= 0)
+			close(socklist[socknum]);
+	}
+	return status;
 }
 
 int main(int argc, char **argv)
 {
 	int listen_port = 0;
-	char *listen_addr = NULL;
 	int inetd_mode = 0;
 	const char *pid_file = NULL, *user_name = NULL, *group_name = NULL;
 	int detach = 0;
@@ -1038,9 +1106,13 @@ int main(int argc, char **argv)
 		if (!prefixcmp(arg, "--listen=")) {
 		    char *p = arg + 9;
 		    char *ph = listen_addr = xmalloc(strlen(arg + 9) + 1);
-		    while (*p)
-			*ph++ = tolower(*p++);
-		    *ph = 0;
+		    if (is_absolute_path(p))
+				strcpy(ph, p);
+		    else {
+				while (*p)
+					*ph++ = tolower(*p++);
+				*ph = 0;
+		    }
 		    continue;
 		}
 		if (!prefixcmp(arg, "--port=")) {
@@ -1154,6 +1226,8 @@ int main(int argc, char **argv)
 
 	if (inetd_mode && (listen_port || listen_addr))
 		die("--listen= and --port= are incompatible with --inetd");
+	else if (listen_port && listen_addr && is_absolute_path(listen_addr))
+		die("--listen path and --port are incompatible");
 	else if (listen_port == 0)
 		listen_port = DEFAULT_GIT_PORT;
 
@@ -1194,7 +1268,7 @@ int main(int argc, char **argv)
 		if (getpeername(0, peer, &slen))
 			peer = NULL;
 
-		return execute(peer);
+		return execute(peer, getuid());
 	}
 
 	if (detach)
@@ -1205,5 +1279,5 @@ int main(int argc, char **argv)
 	if (pid_file)
 		store_pid(pid_file);
 
-	return serve(listen_addr, listen_port, pass, gid);
+	return serve(listen_port, pass, gid);
 }
diff --git a/git-compat-util.h b/git-compat-util.h
index b6ef544..892b7f9 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -74,6 +74,7 @@
 #include <netinet/in.h>
 #include <netinet/tcp.h>
 #include <arpa/inet.h>
+#include <sys/un.h>
 #include <netdb.h>
 #include <pwd.h>
 #include <inttypes.h>
diff --git a/receive-pack.c b/receive-pack.c
index 3267495..6970979 100644
--- a/receive-pack.c
+++ b/receive-pack.c
@@ -2,6 +2,7 @@
 #include "pack.h"
 #include "refs.h"
 #include "pkt-line.h"
+#include "daemon-redirect.h"
 #include "run-command.h"
 #include "exec_cmd.h"
 #include "commit.h"
@@ -469,6 +470,8 @@ int main(int argc, char **argv)
 	if (!dir)
 		usage(receive_pack_usage);
 
+	if (redirect_execute("receive-pack", dir))
+		return 0;
 	if (!enter_repo(dir, 0))
 		die("'%s': unable to chdir or not a git archive", dir);
 
diff --git a/upload-pack.c b/upload-pack.c
index 7e04311..8102126 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "refs.h"
 #include "pkt-line.h"
+#include "daemon-redirect.h"
 #include "sideband.h"
 #include "tag.h"
 #include "object.h"
@@ -622,6 +623,8 @@ int main(int argc, char **argv)
 		usage(upload_pack_usage);
 	dir = argv[i];
 
+	if (redirect_execute("upload-pack", dir))
+		return 0;
 	if (!enter_repo(dir, strict))
 		die("'%s': unable to chdir or not a git archive", dir);
 	if (is_repository_shallow())
-- 
1.5.4.rc4.1142.gf5a97