{"thread":{"id":"11758","subject":"[RFC] Secure central repositories by UNIX socket authentication","startedAt":"2008-01-27T10:39:35Z","lastAt":"2008-01-29T03:11:05Z","messageCount":16,"participants":["Shawn O. Pearce","Johannes Schindelin","Junio C Hamano","Dmitry Potapov","Asheesh Laroia"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"66671","messageId":"20080127103934.GA2735@spearce.org","threadId":"11758","inReplyTo":null,"subject":"[RFC] Secure central repositories by UNIX socket authentication","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-01-27T10:39:35Z","receivedAt":"2008-01-27T10:39:35Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"This isn't anywhere near ready for application, but I'm floating\nit out there to see what people think.  Its a cool new feature that\nwill certainly *not* be in 1.5.4.  :-)\n\nIn a central repository configuration users may not have access\nto write new objects into a Git repository, or to edit the refs,\nespecially if the repository is being protected by an update hook\n(e.g. contrib/hooks/updated-paranoid).\n\nThis change allows any repository owner to setup a git-daemon\nthat other users on the same host can connect through to perform\nupload-pack or receive-pack.  It also allows the update hook to\nauthorize the user via $GIT_REMOTE_USER.  For example:\n\n    ## Owner (not jdoe)\n    ##\n  cat >foo.git/hooks/update <<'EOF'\n  #!/bin/sh\n  test -z \"$GIT_REMOTE_USER\" || exit\n  case \"$GIT_REMOTE_USER\" in\n  jdoe)     exit 0;;\n  spearce)  exit 0;;\n  *)        exit 1\n  esac\n  EOF\n  chmod u+x foo.git/hooks/update\n  chmod 700 foo.git\n\n  git daemon \\\n      --export-all \\\n      --enable=receive-pack \\\n      --base=`pwd` \\\n      --listen=/tmp/shawn-git\n\n    ## Other User\n    ##\n  git push jdoe@server:/tmp/shawn-git/foo.git master\n\nallows user jdoe to push into foo.git, even though it is restricted.\nThis works by:\n\n  *) git-push executes\n     \"ssh jdoe@server git-receive-pack /tmp/shawn-git/foo.git\"\n\n  *) git-receive-pack realizes its actually a socket, connects\n     to /tmp/shawn-git and asks for service \"git-receive-pack\"\n     on directory \"/foo.git\"\n\n  *) git-daemon authenticates the user via the kernel's secure\n     peer id call and sets GIT_REMOTE_USER with their username.\n\n  *) git-daemon starts another git-receive-pack on foo.git, using\n     its normal access control and path resolving rules.\n\n  *) The first git-receive-pack acts as a shuttle to copy\n     data between the two peers.\n\n  *) The second git-receive-pack runs the update hook, which has\n     GIT_REMOTE_USER defined from git-daemon.\n\n\nThere's a few small problems with this, which is partly why this\nisn't ready for application yet:\n\n1) Error messages from the pre-receive or update hooks don't make it\n   back to the client as send-pack and receive-pack don't implement\n   the sideband protocol.  This is harder than it sounds as the\n   pkt-line protocol they currently use for status can't be easily\n   layered into a sideband protocol.\n\n2) There is currently no \"authorization\" hook for upload-pack (aka\n   the fetch server) like we have for receive-pack (aka the push\n   server) so we cannot make use of $GIT_REMOTE_USER to perform\n   user authorization.  Currently Git relies on UNIX filesystem\n   access, but this daemon approach bypasses those.  There was a\n   conversation on #git tonight about adding a hook that filters\n   the list of allowed refs during fetch, for exactly this reason.\n\n3) The system call to get the peer UID via the kernel is not POSIX\n   so its implemented differently on basically every OS we support,\n   if it is even implemented there.  The patch below should provide\n   support for Solaris 8/9/10, FreeBSD, Darwin/Mac OS X, and Linux.\n   But is thus far only tested on Mac OS 10.4.\n\n---\n\n Documentation/git-daemon.txt |   11 ++-\n Makefile                     |   14 ++-\n builtin-upload-archive.c     |    3 +\n daemon-redirect.c            |  282 ++++++++++++++++++++++++++++++++++++++++++\n daemon-redirect.h            |   12 ++\n daemon.c                     |  132 +++++++++++++++-----\n git-compat-util.h            |    1 +\n receive-pack.c               |    3 +\n upload-pack.c                |    3 +\n 9 files changed, 429 insertions(+), 32 deletions(-)\n create mode 100644 daemon-redirect.c\n create mode 100644 daemon-redirect.h\n\ndiff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt\nindex fd83bc7..1f9d9a1 100644\n--- a/Documentation/git-daemon.txt\n+++ b/Documentation/git-daemon.txt\n@@ -15,7 +15,7 @@ SYNOPSIS\n              [--reuseaddr] [--detach] [--pid-file=file]\n              [--enable=service] [--disable=service]\n \t     [--allow-override=service] [--forbid-override=service]\n-\t     [--inetd | [--listen=host_or_ipaddr] [--port=n] [--user=user [--group=group]]\n+\t     [--inetd | [--listen=host_or_ipaddr_or_path] [--port=n] [--user=user [--group=group]]\n \t     [directory...]\n \n DESCRIPTION\n@@ -79,12 +79,19 @@ OPTIONS\n \tHave the server run as an inetd service. Implies --syslog.\n \tIncompatible with --port, --listen, --user and --group options.\n \n---listen=host_or_ipaddr::\n+--listen=host_or_ipaddr_or_path::\n \tListen on an a specific IP address or hostname.  IP addresses can\n \tbe either an IPv4 address or an IPV6 address if supported.  If IPv6\n \tis not supported, then --listen=hostname is also not supported and\n \t--listen must be given an IPv4 address.\n \tIncompatible with '--inetd' option.\n++\n+If the address is an absolute path in the filesystem then git-daemon\n+listens on a UNIX domain socket at the given path.  This can be used\n+to support authenticated redirection from linkgit:git-upload-pack[1]\n+or linkgit:git-receive-pack[1].  Child processes (including hooks)\n+can obtain the authenticated username from the `GIT_REMOTE_USER`\n+environment variable.\n \n --port=n::\n \tListen on an alternative port.  Incompatible with '--inetd' option.\ndiff --git a/Makefile b/Makefile\nindex a0c0998..6f8f3ad 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -295,7 +295,8 @@ LIB_H = \\\n \trun-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \\\n \ttree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \\\n \tutf8.h reflog-walk.h patch-ids.h attr.h decorate.h progress.h \\\n-\tmailmap.h remote.h parse-options.h transport.h diffcore.h hash.h\n+\tmailmap.h remote.h parse-options.h transport.h diffcore.h hash.h \\\n+\tdaemon-redirect.h\n \n DIFF_OBJS = \\\n \tdiff.o diff-lib.o diffcore-break.o diffcore-order.o \\\n@@ -309,6 +310,7 @@ LIB_OBJS = \\\n \tlockfile.o \\\n \tpatch-ids.o \\\n \tobject.o pack-check.o pack-write.o patch-delta.o path.o pkt-line.o \\\n+\tdaemon-redirect.o \\\n \tsideband.o reachable.o reflog-walk.o \\\n \tquote.o read-cache.o refs.o run-command.o dir.o object-refs.o \\\n \tserver-info.o setup.o sha1_file.o sha1_name.o strbuf.o \\\n@@ -417,6 +419,7 @@ ifeq ($(uname_S),Darwin)\n \tendif\n \tNO_STRLCPY = YesPlease\n \tNO_MEMMEM = YesPlease\n+\tHAVE_GETPEEREID = YesPlease\n endif\n ifeq ($(uname_S),SunOS)\n \tNEEDS_SOCKET = YesPlease\n@@ -426,6 +429,7 @@ ifeq ($(uname_S),SunOS)\n \tNO_MEMMEM = YesPlease\n \tNO_HSTRERROR = YesPlease\n \tNO_MKDTEMP = YesPlease\n+\tHAVE_STREAMS_CONNLD = YesPlease\n \tifeq ($(uname_R),5.8)\n \t\tNEEDS_LIBICONV = YesPlease\n \t\tNO_UNSETENV = YesPlease\n@@ -463,6 +467,7 @@ endif\n ifeq ($(uname_S),FreeBSD)\n \tNEEDS_LIBICONV = YesPlease\n \tNO_MEMMEM = YesPlease\n+\tHAVE_GETPEEREID = YesPlease\n \tBASIC_CFLAGS += -I/usr/local/include\n \tBASIC_LDFLAGS += -L/usr/local/lib\n endif\n@@ -470,6 +475,7 @@ ifeq ($(uname_S),OpenBSD)\n \tNO_STRCASESTR = YesPlease\n \tNO_MEMMEM = YesPlease\n \tNEEDS_LIBICONV = YesPlease\n+\tHAVE_GETPEEREID = YesPlease\n \tBASIC_CFLAGS += -I/usr/local/include\n \tBASIC_LDFLAGS += -L/usr/local/lib\n endif\n@@ -659,6 +665,12 @@ else\n \tBASIC_CFLAGS += -Dsockaddr_storage=sockaddr_in6\n endif\n endif\n+ifdef HAVE_STREAMS_CONNLD\n+\tBASIC_CFLAGS += -DHAVE_STREAMS_CONNLD\n+endif\n+ifdef HAVE_GETPEEREID\n+\tBASIC_CFLAGS += -DHAVE_GETPEEREID\n+endif\n ifdef NO_INET_NTOP\n \tLIB_OBJS += compat/inet_ntop.o\n endif\ndiff --git a/builtin-upload-archive.c b/builtin-upload-archive.c\nindex 48ae09e..5599e3b 100644\n--- a/builtin-upload-archive.c\n+++ b/builtin-upload-archive.c\n@@ -5,6 +5,7 @@\n #include \"builtin.h\"\n #include \"archive.h\"\n #include \"pkt-line.h\"\n+#include \"daemon-redirect.h\"\n #include \"sideband.h\"\n \n static const char upload_archive_usage[] =\n@@ -35,6 +36,8 @@ static int run_upload_archive(int argc, const char **argv, const char *prefix)\n \n \tstrcpy(buf, argv[1]); /* enter-repo smudges its argument */\n \n+\tif (redirect_execute(\"upload-archive\", buf))\n+\t\treturn 0;\n \tif (!enter_repo(buf, 0))\n \t\tdie(\"not a git archive\");\n \ndiff --git a/daemon-redirect.c b/daemon-redirect.c\nnew file mode 100644\nindex 0000000..8474ddd\n--- /dev/null\n+++ b/daemon-redirect.c\n@@ -0,0 +1,282 @@\n+#include \"daemon-redirect.h\"\n+#include \"pkt-line.h\"\n+\n+static int redirect_connect(const char *path);\n+\n+#ifdef HAVE_STREAMS_CONNLD\n+\n+int redirect_listen(const char *path)\n+{\n+\tint fd_pair[2];\n+\n+\tif (pipe(fd_pair) || ioctl(fd_pair[0], I_PUSH, \"connld\"))\n+\t\tdie(\"redirect listen failed: %s\", strerror(errno));\n+\tioctl(fd_pair[0], I_CANPUT, 0);\n+\n+\tunlink(path);\n+\tif (close(creat(path, 0666)) || fattach(fd_pair[0], path))\n+\t\tdie(\"redirect listen failed on %s: %s\", path, strerror(errno));\n+\tclose(fd_pair[0]); /* can i really do that? */\n+\treturn fd_pair[1];\n+}\n+\n+int redirect_accept(int srv_fd, uid_t *cli_uid)\n+{\n+\tstruct strrecvfd rfd;\n+\n+\tif (ioctl(srv_fd, I_RECVFD, &rfd))\n+\t\treturn -1;\n+\t*cli_uid = rfd.uid;\n+\treturn rfd.fd;\n+}\n+\n+static int redirect_connect(const char *path)\n+{\n+\tint cli_fd = open(path, O_RDWR);\n+\tif (cli_fd < 0)\n+\t\tdie(\"redirect connect failed on %s: %s\", path, strerror(errno));\n+\treturn cli_fd;\n+}\n+\n+#else /* ! HAVE_STREAMS_CONNLD */\n+\n+int redirect_listen(const char *path)\n+{\n+\tint srv_fd;\n+\tstruct sockaddr_un addr;\n+\n+\tif (strlen(path) > sizeof(addr.sun_path))\n+\t\tdie(\"redirect listen path too long\");\n+\n+\tmemset(&addr, 0, sizeof(addr));\n+\taddr.sun_family = AF_UNIX;\n+\tstrcpy(addr.sun_path, path);\n+\tunlink(path);\n+\n+\tsrv_fd = socket(AF_UNIX, SOCK_STREAM, 0);\n+\tif (srv_fd < 0)\n+\t\tdie(\"redirect listen failed: %s\", strerror(errno));\n+\tif (bind(srv_fd, (struct sockaddr*)&addr, sizeof(addr)))\n+\t\tdie(\"redirect listen failed on %s: %s\", path, strerror(errno));\n+\tif (listen(srv_fd, 10))\n+\t\tdie(\"redirect listen failed: %s\", strerror(errno));\n+\n+\treturn srv_fd;\n+}\n+\n+static int peer_uid(int cli_fd, uid_t *cli_uid);\n+\n+#if defined(HAVE_GETPEEREID) /* OpenBSD / FreeBSD / Darwin */\n+static int peer_uid(int cli_fd, uid_t *cli_uid)\n+{\n+\tgid_t cli_gid;\n+\treturn getpeereid(cli_fd, cli_uid, &cli_gid);\n+}\n+#elif defined(SO_PEERCRED) /* Linux */\n+static int peer_uid(int cli_fd, uid_t *cli_uid)\n+{\n+\tstruct ucred creds;\n+\tint len = sizeof(creds);\n+\tif (getsockopt(cli_fd, SOL_SOCKET, SO_PEERCRED, &creds, &len))\n+\t\treturn -1;\n+\t*cli_uid = creds.uid;\n+}\n+#else /* Not supported */\n+static int peer_uid(int cli_fd, uid_t *cli_uid)\n+{\n+\terror(\"UNIX peer authentication not compiled in\");\n+\terrno = ENOSYS;\n+\treturn -1;\n+}\n+#endif\n+\n+int redirect_accept(int srv_fd, uid_t *cli_uid)\n+{\n+\tint cli_fd;\n+\tstruct sockaddr_un ss;\n+\tunsigned int sslen = sizeof(ss);\n+\n+\tcli_fd = accept(srv_fd, (struct sockaddr*)&ss, &sslen);\n+\tif (cli_fd < 0)\n+\t\treturn -1;\n+\n+\tif (peer_uid(cli_fd, cli_uid)) {\n+\t\terror(\"redirect accept creds failed: %s\", strerror(errno));\n+\t\tclose(cli_fd);\n+\t\terrno = ECONNABORTED;\n+\t\treturn -1;\n+\t}\n+\n+\treturn cli_fd;\n+}\n+\n+static int redirect_connect(const char *path)\n+{\n+\tint cli_fd;\n+\tstruct sockaddr_un addr;\n+\n+\tif (strlen(path) > sizeof(addr.sun_path))\n+\t\tdie(\"redirect socket path too long\");\n+\n+\tmemset(&addr, 0, sizeof(addr));\n+\taddr.sun_family = AF_UNIX;\n+\tstrcpy(addr.sun_path, path);\n+\n+\tcli_fd = socket(AF_UNIX, SOCK_STREAM, 0);\n+\tif (cli_fd < 0)\n+\t\tdie(\"redirect connect failed: %s\", strerror(errno));\n+\tif (connect(cli_fd, (struct sockaddr*)&addr, sizeof(addr)))\n+\t\tdie(\"redirect connect failed on %s: %s\", path, strerror(errno));\n+\treturn cli_fd;\n+}\n+\n+#endif\n+\n+struct fd_copy_buffer {\n+\tint live;\n+\tint in_fd;\n+\tint out_fd;\n+\tsize_t avail;\n+\tsize_t off;\n+\tchar buf[4096];\n+};\n+\n+static int buf_poll(struct pollfd *p, struct fd_copy_buffer *b)\n+{\n+\tif (!b->live)\n+\t\treturn 0;\n+\n+\tif (b->avail) {\n+\t\tp->fd = b->out_fd;\n+\t\tp->events = POLLOUT;\n+\t} else {\n+\t\tp->fd = b->in_fd;\n+\t\tp->events = POLLIN;\n+\t}\n+\treturn 1;\n+}\n+\n+static int buf_copy(struct pollfd *p, struct fd_copy_buffer *b)\n+{\n+\tif (!b->live)\n+\t\treturn 0;\n+\n+\tif (p->revents & POLLIN) {\n+\t\tb->avail = read(b->in_fd, b->buf, sizeof(b->buf));\n+\t\tb->off = 0;\n+\t\tif (!b->avail) {\n+\t\t\tb->live = 0;\n+\t\t\tclose(b->in_fd);\n+\t\t\tshutdown(b->out_fd, 1);\n+\t\t} else if (b->avail < 0)\n+\t\t\tdie(\"input failure: %s\", strerror(errno));\n+\t}\n+\n+\tif (p->revents & POLLOUT) {\n+\t\tssize_t r = write(b->out_fd, b->buf + b->off, b->avail);\n+\t\tif (r <= 0)\n+\t\t\tdie(\"output failure: %s\", strerror(errno));\n+\t\tb->avail -= r;\n+\t\tb->off += r;\n+\t}\n+\n+\treturn 1;\n+}\n+\n+static void execute(\n+\tconst char *service,\n+\tconst char *sock_name,\n+\tconst char *repo_name)\n+{\n+\tint cli_fd;\n+\tstruct pollfd pfd[2];\n+\tstruct fd_copy_buffer in_buf, out_buf;\n+\n+\tcli_fd = redirect_connect(sock_name);\n+\tpacket_write(cli_fd, \"git-%s /%s\", service, repo_name);\n+\n+\tin_buf.live = 1;\n+\tin_buf.in_fd = 0;\n+\tin_buf.out_fd = cli_fd;\n+\tin_buf.avail = 0;\n+\n+\tout_buf.live = 1;\n+\tout_buf.in_fd = cli_fd;\n+\tout_buf.out_fd = 1;\n+\tout_buf.avail = 0;\n+\n+\twhile (out_buf.live) {\n+\t\tint cnt = 0;\n+\n+\t\tcnt += buf_poll(&pfd[cnt], &in_buf);\n+\t\tcnt += buf_poll(&pfd[cnt], &out_buf);\n+\n+\t\tif (poll(pfd, cnt, -1) < 0) {\n+\t\t\tif (errno != EINTR) {\n+\t\t\t\terror(\"poll failed, resuming: %s\", strerror(errno));\n+\t\t\t\tsleep(1);\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tcnt = 0;\n+\t\tcnt += buf_copy(&pfd[cnt], &in_buf);\n+\t\tcnt += buf_copy(&pfd[cnt], &out_buf);\n+\t}\n+\n+\tclose(cli_fd);\n+}\n+\n+int redirect_execute(const char *service, const char *path)\n+{\n+\tstruct stat sb;\n+\tchar *sock_name, *s, *repo_name;\n+\n+\t/* Exists and is a directory?  Assume the caller is\n+\t * the named service and can directly access this\n+\t * location itself, without redirect sockets.\n+\t */\n+\tif (!stat(path, &sb) && S_ISDIR(sb.st_mode))\n+\t\treturn 0;\n+\ts = strrchr(path, '/');\n+\tif (!s)\n+\t\treturn 0;\n+\n+\tsock_name = xstrdup(path);\n+\ts = sock_name + (s - path);\n+\t*s = 0;\n+\trepo_name = s + 1;\n+\n+\tfor (;;) {\n+\t\tif (!stat(sock_name, &sb)) {\n+\t\t\tif (S_ISDIR(sb.st_mode))\n+\t\t\t\tbreak;\n+\t\t\tif (S_ISSOCK(sb.st_mode)) {\n+\t\t\t\texecute(service, sock_name, repo_name);\n+\t\t\t\tfree(sock_name);\n+\t\t\t\treturn 1;\n+\t\t\t}\n+\t\t}\n+\n+\t\ts = strrchr(sock_name, '/');\n+\t\tif (!s || s == sock_name)\n+\t\t\tbreak;\n+\n+\t\t*s = 0;\n+\t\t*(--repo_name) = '/';\n+\t\trepo_name = s + 1;\n+\t}\n+\n+\t/* Assume redirection shouldn't be applied and that the\n+\t * path is also completely incorrect.  Allow the caller\n+\t * to handle this error condition as before.\n+\t */\n+\tfree(sock_name);\n+\treturn 0;\n+}\n+\n+void redirect_unlisten(int srv_fd, const char *path)\n+{\n+\tclose(srv_fd);\n+\tunlink(path);\n+}\ndiff --git a/daemon-redirect.h b/daemon-redirect.h\nnew file mode 100644\nindex 0000000..5d85e10\n--- /dev/null\n+++ b/daemon-redirect.h\n@@ -0,0 +1,12 @@\n+#ifndef DAEMON_REDIRECT_H\n+#define DAEMON_REDIRECT_H\n+\n+#include \"git-compat-util.h\"\n+\n+extern int redirect_listen(const char *path);\n+extern int redirect_accept(int, uid_t *);\n+extern void redirect_unlisten(int, const char *);\n+\n+extern int redirect_execute(const char *, const char *);\n+\n+#endif\ndiff --git a/daemon.c b/daemon.c\nindex 41a60af..6c4897d 100644\n--- a/daemon.c\n+++ b/daemon.c\n@@ -1,4 +1,5 @@\n #include \"cache.h\"\n+#include \"daemon-redirect.h\"\n #include \"pkt-line.h\"\n #include \"exec_cmd.h\"\n #include \"interpolate.h\"\n@@ -16,6 +17,7 @@\n static int log_syslog;\n static int verbose;\n static int reuseaddr;\n+static char *listen_addr;\n \n static const char daemon_usage[] =\n \"git-daemon [--verbose] [--syslog] [--export-all]\\n\"\n@@ -25,7 +27,7 @@ static const char daemon_usage[] =\n \"           [--interpolated-path=path]\\n\"\n \"           [--reuseaddr] [--detach] [--pid-file=file]\\n\"\n \"           [--[enable|disable|allow-override|forbid-override]=service]\\n\"\n-\"           [--inetd | [--listen=host_or_ipaddr] [--port=n]\\n\"\n+\"           [--inetd | [--listen=host_or_ipaddr_or_path] [--port=n]\\n\"\n \"                      [--user=user [--group=group]]\\n\"\n \"           [directory...]\";\n \n@@ -318,13 +320,24 @@ static int git_daemon_config(const char *var, const char *value)\n \treturn 0;\n }\n \n-static int run_service(struct interp *itable, struct daemon_service *service)\n+static int run_service(struct interp *itable, struct daemon_service *service, uid_t uid)\n {\n \tconst char *path;\n+\tstruct passwd *pw = getpwuid(uid);\n \tint enabled = service->enabled;\n+\tchar *pw_name;\n \n-\tloginfo(\"Request %s for '%s'\",\n+\tif (pw)\n+\t\tpw_name = xstrdup(pw->pw_name);\n+\telse {\n+\t\tpw_name = xmalloc(64);\n+\t\tsnprintf(pw_name, 64, \"uid %u\", uid);\n+\t}\n+\tsetenv(\"GIT_REMOTE_USER\", pw_name, 1);\n+\n+\tloginfo(\"Request %s for %s in '%s'\",\n \t\tservice->name,\n+\t\tpw_name,\n \t\titable[INTERP_SLOT_DIR].value);\n \n \tif (!enabled && !service->overridable) {\n@@ -530,7 +543,7 @@ static void fill_in_extra_table_entries(struct interp *itable)\n }\n \n \n-static int execute(struct sockaddr *addr)\n+static int execute(struct sockaddr *addr, uid_t uid)\n {\n \tstatic char line[1000];\n \tint pktlen, len, i;\n@@ -595,7 +608,7 @@ static int execute(struct sockaddr *addr)\n \t\t\t */\n \t\t\tinterp_set_entry(interp_table,\n \t\t\t\t\t INTERP_SLOT_DIR, line + namelen + 5);\n-\t\t\treturn run_service(interp_table, s);\n+\t\t\treturn run_service(interp_table, s, uid);\n \t\t}\n \t}\n \n@@ -636,7 +649,10 @@ static void add_child(int idx, pid_t pid, struct sockaddr *addr, int addrlen)\n {\n \tlive_child[idx].pid = pid;\n \tlive_child[idx].addrlen = addrlen;\n-\tmemcpy(&live_child[idx].address, addr, addrlen);\n+\tif (addr)\n+\t\tmemcpy(&live_child[idx].address, addr, addrlen);\n+\telse\n+\t\tmemset(&live_child[idx].address, 0, sizeof(live_child[idx].address));\n }\n \n /*\n@@ -726,7 +742,7 @@ static void check_max_connections(void)\n \t}\n }\n \n-static void handle(int incoming, struct sockaddr *addr, int addrlen)\n+static void handle(int incoming, struct sockaddr *addr, int addrlen, uid_t uid)\n {\n \tpid_t pid = fork();\n \n@@ -749,7 +765,7 @@ static void handle(int incoming, struct sockaddr *addr, int addrlen)\n \tdup2(incoming, 1);\n \tclose(incoming);\n \n-\texit(execute(addr));\n+\texit(execute(addr, uid));\n }\n \n static void child_handler(int signo)\n@@ -790,7 +806,7 @@ static int set_reuse_addr(int sockfd)\n \n #ifndef NO_IPV6\n \n-static int socksetup(char *listen_addr, int listen_port, int **socklist_p)\n+static int socksetup(int listen_port, int **socklist_p)\n {\n \tint socknum = 0, *socklist = NULL;\n \tint maxfd = -1;\n@@ -864,7 +880,7 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p)\n \n #else /* NO_IPV6 */\n \n-static int socksetup(char *listen_addr, int listen_port, int **socklist_p)\n+static int socksetup(int listen_port, int **socklist_p)\n {\n \tstruct sockaddr_in sin;\n \tint sockfd;\n@@ -912,8 +928,15 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p)\n \n #endif\n \n+static volatile int end_daemon;\n+static void term_handler(int signo)\n+{\n+\tend_daemon = 1;\n+}\n+\n static int service_loop(int socknum, int *socklist)\n {\n+\tuid_t uid = getuid();\n \tstruct pollfd *pfd;\n \tint i;\n \n@@ -924,9 +947,7 @@ static int service_loop(int socknum, int *socklist)\n \t\tpfd[i].events = POLLIN;\n \t}\n \n-\tsignal(SIGCHLD, child_handler);\n-\n-\tfor (;;) {\n+\twhile (!end_daemon) {\n \t\tint i;\n \n \t\tif (poll(pfd, socknum, -1) < 0) {\n@@ -950,13 +971,36 @@ static int service_loop(int socknum, int *socklist)\n \t\t\t\t\tcase ECONNABORTED:\n \t\t\t\t\t\tcontinue;\n \t\t\t\t\tdefault:\n-\t\t\t\t\t\tdie(\"accept returned %s\", strerror(errno));\n+\t\t\t\t\t\terror(\"accept returned %s\", strerror(errno));\n+\t\t\t\t\t\treturn 1;\n \t\t\t\t\t}\n \t\t\t\t}\n-\t\t\t\thandle(incoming, (struct sockaddr *)&ss, sslen);\n+\t\t\t\thandle(incoming, (struct sockaddr *)&ss, sslen, uid);\n \t\t\t}\n \t\t}\n \t}\n+\treturn 0;\n+}\n+\n+static int redirect_loop(int srv_fd)\n+{\n+\twhile (!end_daemon) {\n+\t\tuid_t uid;\n+\t\tint incoming = redirect_accept(srv_fd, &uid);\n+\t\tif (incoming < 0) {\n+\t\t\tswitch (errno) {\n+\t\t\tcase EAGAIN:\n+\t\t\tcase EINTR:\n+\t\t\tcase ECONNABORTED:\n+\t\t\t\tcontinue;\n+\t\t\tdefault:\n+\t\t\t\terror(\"accept returned %s\", strerror(errno));\n+\t\t\t\treturn 1;\n+\t\t\t}\n+\t\t}\n+\t\thandle(incoming, NULL, 0, uid);\n+\t}\n+\treturn 0;\n }\n \n /* if any standard file descriptor is missing open it to /dev/null */\n@@ -998,27 +1042,51 @@ static void store_pid(const char *path)\n \t\tdie(\"failed to write pid file %s: %s\", path, strerror(errno));\n }\n \n-static int serve(char *listen_addr, int listen_port, struct passwd *pass, gid_t gid)\n+static int serve(int listen_port, struct passwd *pass, gid_t gid)\n {\n-\tint socknum, *socklist;\n-\n-\tsocknum = socksetup(listen_addr, listen_port, &socklist);\n-\tif (socknum == 0)\n-\t\tdie(\"unable to allocate any listen sockets on host %s port %u\",\n-\t\t    listen_addr, listen_port);\n+\tint socknum, *socklist, status;\n+\tint is_redirect = is_absolute_path(listen_addr);\n+\tstruct sigaction sa;\n+\n+\tif (is_redirect) {\n+\t\tsocknum = 1;\n+\t\tsocklist = xmalloc(sizeof(int));\n+\t\tsocklist[0] = redirect_listen(listen_addr);\n+\t} else {\n+\t\tsocknum = socksetup(listen_port, &socklist);\n+\t\tif (socknum == 0)\n+\t\t\tdie(\"unable to allocate any listen sockets\"\n+\t\t\t\t\" on host %s port %u\",\n+\t\t\t\tlisten_addr, listen_port);\n+\t}\n \n \tif (pass && gid &&\n \t    (initgroups(pass->pw_name, gid) || setgid (gid) ||\n \t     setuid(pass->pw_uid)))\n \t\tdie(\"cannot drop privileges\");\n \n-\treturn service_loop(socknum, socklist);\n+\tsignal(SIGCHLD, child_handler);\n+\n+\tmemset(&sa, 0, sizeof(sa));\n+\tsa.sa_handler = term_handler;\n+\tsigemptyset(&sa.sa_mask);\n+\tsigaction(SIGINT, &sa, NULL);\n+\tsigaction(SIGTERM, &sa, NULL);\n+\n+\tif (is_redirect) {\n+\t\tstatus = redirect_loop(socklist[0]);\n+\t\tredirect_unlisten(socklist[0], listen_addr);\n+\t} else {\n+\t\tstatus = service_loop(socknum, socklist);\n+\t\twhile (--socknum >= 0)\n+\t\t\tclose(socklist[socknum]);\n+\t}\n+\treturn status;\n }\n \n int main(int argc, char **argv)\n {\n \tint listen_port = 0;\n-\tchar *listen_addr = NULL;\n \tint inetd_mode = 0;\n \tconst char *pid_file = NULL, *user_name = NULL, *group_name = NULL;\n \tint detach = 0;\n@@ -1038,9 +1106,13 @@ int main(int argc, char **argv)\n \t\tif (!prefixcmp(arg, \"--listen=\")) {\n \t\t    char *p = arg + 9;\n \t\t    char *ph = listen_addr = xmalloc(strlen(arg + 9) + 1);\n-\t\t    while (*p)\n-\t\t\t*ph++ = tolower(*p++);\n-\t\t    *ph = 0;\n+\t\t    if (is_absolute_path(p))\n+\t\t\t\tstrcpy(ph, p);\n+\t\t    else {\n+\t\t\t\twhile (*p)\n+\t\t\t\t\t*ph++ = tolower(*p++);\n+\t\t\t\t*ph = 0;\n+\t\t    }\n \t\t    continue;\n \t\t}\n \t\tif (!prefixcmp(arg, \"--port=\")) {\n@@ -1154,6 +1226,8 @@ int main(int argc, char **argv)\n \n \tif (inetd_mode && (listen_port || listen_addr))\n \t\tdie(\"--listen= and --port= are incompatible with --inetd\");\n+\telse if (listen_port && listen_addr && is_absolute_path(listen_addr))\n+\t\tdie(\"--listen path and --port are incompatible\");\n \telse if (listen_port == 0)\n \t\tlisten_port = DEFAULT_GIT_PORT;\n \n@@ -1194,7 +1268,7 @@ int main(int argc, char **argv)\n \t\tif (getpeername(0, peer, &slen))\n \t\t\tpeer = NULL;\n \n-\t\treturn execute(peer);\n+\t\treturn execute(peer, getuid());\n \t}\n \n \tif (detach)\n@@ -1205,5 +1279,5 @@ int main(int argc, char **argv)\n \tif (pid_file)\n \t\tstore_pid(pid_file);\n \n-\treturn serve(listen_addr, listen_port, pass, gid);\n+\treturn serve(listen_port, pass, gid);\n }\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex b6ef544..892b7f9 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -74,6 +74,7 @@\n #include <netinet/in.h>\n #include <netinet/tcp.h>\n #include <arpa/inet.h>\n+#include <sys/un.h>\n #include <netdb.h>\n #include <pwd.h>\n #include <inttypes.h>\ndiff --git a/receive-pack.c b/receive-pack.c\nindex 3267495..6970979 100644\n--- a/receive-pack.c\n+++ b/receive-pack.c\n@@ -2,6 +2,7 @@\n #include \"pack.h\"\n #include \"refs.h\"\n #include \"pkt-line.h\"\n+#include \"daemon-redirect.h\"\n #include \"run-command.h\"\n #include \"exec_cmd.h\"\n #include \"commit.h\"\n@@ -469,6 +470,8 @@ int main(int argc, char **argv)\n \tif (!dir)\n \t\tusage(receive_pack_usage);\n \n+\tif (redirect_execute(\"receive-pack\", dir))\n+\t\treturn 0;\n \tif (!enter_repo(dir, 0))\n \t\tdie(\"'%s': unable to chdir or not a git archive\", dir);\n \ndiff --git a/upload-pack.c b/upload-pack.c\nindex 7e04311..8102126 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -1,6 +1,7 @@\n #include \"cache.h\"\n #include \"refs.h\"\n #include \"pkt-line.h\"\n+#include \"daemon-redirect.h\"\n #include \"sideband.h\"\n #include \"tag.h\"\n #include \"object.h\"\n@@ -622,6 +623,8 @@ int main(int argc, char **argv)\n \t\tusage(upload_pack_usage);\n \tdir = argv[i];\n \n+\tif (redirect_execute(\"upload-pack\", dir))\n+\t\treturn 0;\n \tif (!enter_repo(dir, strict))\n \t\tdie(\"'%s': unable to chdir or not a git archive\", dir);\n \tif (is_repository_shallow())\n-- \n1.5.4.rc4.1142.gf5a97\n"},{"id":"66672","messageId":"alpine.LSU.1.00.0801271402330.23907@racer.site","threadId":"11758","inReplyTo":"20080127103934.GA2735@spearce.org","subject":"Re: [RFC] Secure central repositories by UNIX socket authentication","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2008-01-27T14:04:26Z","receivedAt":"2008-01-27T14:04:26Z","isPatch":false,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sun, 27 Jan 2008, Shawn O. Pearce wrote:\n\n>     ## Owner (not jdoe)\n>     ##\n>   cat >foo.git/hooks/update <<'EOF'\n>   #!/bin/sh\n>   test -z \"$GIT_REMOTE_USER\" || exit\n>   case \"$GIT_REMOTE_USER\" in\n>   jdoe)     exit 0;;\n>   spearce)  exit 0;;\n>   *)        exit 1\n>   esac\n>   EOF\n>   chmod u+x foo.git/hooks/update\n>   chmod 700 foo.git\n> \n>   git daemon \\\n>       --export-all \\\n>       --enable=receive-pack \\\n>       --base=`pwd` \\\n>       --listen=/tmp/shawn-git\n> \n>     ## Other User\n>     ##\n>   git push jdoe@server:/tmp/shawn-git/foo.git master\n\nI probably miss something, but if you already go through SSH, the $USER is \nset appropriately, no?\n\nSo you could do without git-daemon entirely, and replace the \nGIT_REMOTE_USER variable in the hook by USER.\n\nCiao,\nDscho\n"},{"id":"66680","messageId":"20080127173212.GW24004@spearce.org","threadId":"11758","inReplyTo":"alpine.LSU.1.00.0801271402330.23907@racer.site","subject":"Re: [RFC] Secure central repositories by UNIX socket authentication","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-01-27T17:32:13Z","receivedAt":"2008-01-27T17:32:13Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:\n> On Sun, 27 Jan 2008, Shawn O. Pearce wrote:\n> \n> >     ## Owner (not jdoe)\n> >     ##\n> >   cat >foo.git/hooks/update <<'EOF'\n> >   #!/bin/sh\n> >   test -z \"$GIT_REMOTE_USER\" || exit\n> >   case \"$GIT_REMOTE_USER\" in\n> >   jdoe)     exit 0;;\n> >   spearce)  exit 0;;\n> >   *)        exit 1\n> >   esac\n> >   EOF\n> >   chmod u+x foo.git/hooks/update\n> >   chmod 700 foo.git\n> > \n> >   git daemon \\\n> >       --export-all \\\n> >       --enable=receive-pack \\\n> >       --base=`pwd` \\\n> >       --listen=/tmp/shawn-git\n> > \n> >     ## Other User\n> >     ##\n> >   git push jdoe@server:/tmp/shawn-git/foo.git master\n> \n> I probably miss something, but if you already go through SSH, the $USER is \n> set appropriately, no?\n\nSure, $USER is set.  For \"jdoe\".  But due to the \"chmod 700 foo.git\"\nabove jdoe isn't actually allowed access to the repository directory.\nSo it doesn't matter what $USER is set to, jdoe cannot get to the\nfiles of the repository.\n \n> So you could do without git-daemon entirely, and replace the \n> GIT_REMOTE_USER variable in the hook by USER.\n\nNo, you can't.\n\nIf you give write access to a repository such that a user can push into\nit, there is little point in using the update hook to control access to\nthe repository.  Why?  Because anyone with write access can just use the\nstandard Git commands (e.g. `git --git-dir=foo.git branch -D next`)\nto maniplate the repository in ways that the update hook wouldn't like.\n\nSo if you want to give out write access, but have it be limited\nto what `git receive-pack` will permit (especially when coupled\nwith an update hook like contrib/hooks/update-paranoid) you need\nto limit what a user can do to *only* executing git-receive-pack,\nbut you also need to allow that receive-pack to actually have write\npermission on the repository.\n\nSo you come down to four options:\n\n1) Make git-receive-pack setuid to the repository owner.\n   This is uh, ugly.\n\n2) Use the SSH key feature to have remote users login as\n   the repository owner, but use the authorized_keys file\n   to force them to only execute git-shell.\n   This is uh, ugly, especially with 50+ users.\n\n3) Export git-daemon over TCP and --enable=receive-pack.\n   This doesn't get us any authentication.  Sure the\n   user is limited to what the update hook allows, but\n   the update hook has no way to trust who the remote\n   peer is.  You might as well not bother.\n\n4) Add full user authentication to git-daemon and then do #3.\n   The user authentication can provide data down into the update\n   hook, such as by setting the $GIT_REMOTE_USER environment\n   variable.  That's basically this change, except I'm using bog\n   standard SSH to perform the authentication for me.\n\nIf we don't do something like this then I think we need to teach\ngit-daemon how to accept SSL connections, and then once you give it\nSSL you need to implement peer authentication by certificates, and\nthen have git_connect() in connect.c also implement setting up SSL\nconnections, and doing peer authentication with certificates.  Ick.\n\nRelying on SSH is the better approach in my opinion.  It comes with\na set of well trusted servers (OpenSSH) and user client tools that\nmany users and administrators already understand (e.g. ssh-agent).\n\nSomeone on #git yesterday mentioned that a problem half-deferred\n(user authentication) is a problem half-solved.  I've half-deferred\nthe authentication problem to SSH, much as we already have it\ndeferred to SSH...  :-)\n\n-- \nShawn.\n"},{"id":"66684","messageId":"alpine.LSU.1.00.0801271841230.23907@racer.site","threadId":"11758","inReplyTo":"20080127173212.GW24004@spearce.org","subject":"Re: [RFC] Secure central repositories by UNIX socket authentication","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2008-01-27T18:51:06Z","receivedAt":"2008-01-27T18:51:06Z","isPatch":false,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sun, 27 Jan 2008, Shawn O. Pearce wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:\n> > On Sun, 27 Jan 2008, Shawn O. Pearce wrote:\n> > \n> > >     ## Owner (not jdoe)\n> > >     ##\n> > >   cat >foo.git/hooks/update <<'EOF'\n> > >   #!/bin/sh\n> > >   test -z \"$GIT_REMOTE_USER\" || exit\n> > >   case \"$GIT_REMOTE_USER\" in\n> > >   jdoe)     exit 0;;\n> > >   spearce)  exit 0;;\n> > >   *)        exit 1\n> > >   esac\n> > >   EOF\n> > >   chmod u+x foo.git/hooks/update\n> > >   chmod 700 foo.git\n> > > \n> > >   git daemon \\\n> > >       --export-all \\\n> > >       --enable=receive-pack \\\n> > >       --base=`pwd` \\\n> > >       --listen=/tmp/shawn-git\n> > > \n> > >     ## Other User\n> > >     ##\n> > >   git push jdoe@server:/tmp/shawn-git/foo.git master\n> > \n> > I probably miss something, but if you already go through SSH, the \n> > $USER is set appropriately, no?\n> \n> Sure, $USER is set.  For \"jdoe\".  But due to the \"chmod 700 foo.git\" \n> above jdoe isn't actually allowed access to the repository directory. So \n> it doesn't matter what $USER is set to, jdoe cannot get to the files of \n> the repository.\n\nAh, that's what I missed.  I thought you already used git-shell, and did \nnot really read the chmod part.\n\n> So if you want to give out write access, but have it be limited to what \n> `git receive-pack` will permit (especially when coupled with an update \n> hook like contrib/hooks/update-paranoid) you need to limit what a user \n> can do to *only* executing git-receive-pack, but you also need to allow \n> that receive-pack to actually have write permission on the repository.\n> \n> So you come down to four options:\n> \n> 1) Make git-receive-pack setuid to the repository owner.\n>    This is uh, ugly.\n\nConcur.\n\n> 2) Use the SSH key feature to have remote users login as\n>    the repository owner, but use the authorized_keys file\n>    to force them to only execute git-shell.\n>    This is uh, ugly, especially with 50+ users.\n\nSlight variation: do not permit other users access to your machine, except \nvia git-shell.  Then you don't need chmod 0700.\n\n> 3) Export git-daemon over TCP and --enable=receive-pack.\n>    This doesn't get us any authentication.  Sure the\n>    user is limited to what the update hook allows, but\n>    the update hook has no way to trust who the remote\n>    peer is.  You might as well not bother.\n\nRight.  --enable=receive-pack was meant for environments where you have to \ntrust everybody anyway (think \"Visual\" SourceSafe, or PVCS with \nrepositories on network shares).\n\n> 4) Add full user authentication to git-daemon and then do #3.\n>    The user authentication can provide data down into the update\n>    hook, such as by setting the $GIT_REMOTE_USER environment\n>    variable.  That's basically this change, except I'm using bog\n>    standard SSH to perform the authentication for me.\n\nAFAIR the plan was to keep git-daemon as simple and stupid as possible; in \nparticular _not_ to add any authentication.\n\n> If we don't do something like this then I think we need to teach \n> git-daemon how to accept SSL connections, and then once you give it SSL \n> you need to implement peer authentication by certificates, and then have \n> git_connect() in connect.c also implement setting up SSL connections, \n> and doing peer authentication with certificates.  Ick.\n\nI never tried anything like this, but it should not be _that_ difficult, \nshould it?  Of course, I should read up on it before I say something about \nit first ;-)\n\nCiao,\nDscho\n"},{"id":"66695","messageId":"7vsl0ix4gh.fsf@gitster.siamese.dyndns.org","threadId":"11758","inReplyTo":"20080127103934.GA2735@spearce.org","subject":"Re: [RFC] Secure central repositories by UNIX socket authentication","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-01-27T22:56:46Z","receivedAt":"2008-01-27T22:56:46Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Shawn O. Pearce\" <spearce@spearce.org> writes:\n\n> This isn't anywhere near ready for application, but I'm floating\n> it out there to see what people think.  Its a cool new feature that\n> will certainly *not* be in 1.5.4.  :-)\n>\n> In a central repository configuration users may not have access\n> to write new objects into a Git repository, or to edit the refs,\n> especially if the repository is being protected by an update hook\n> (e.g. contrib/hooks/updated-paranoid).\n\nSorry, but I am puzzled about what this assumption is trying to\nachieve here.\n\nIf the configuration is based on central repository model,\nwouldn't the users who are participating in the project have\nwrite access to the repository by being in the project's group\nand the repository initialized with core.sharedrepository=true?\n\n> This change allows any repository owner to setup a git-daemon\n> that other users on the same host can connect through to perform\n> upload-pack or receive-pack.\n\nMy reading of this is that it creates a backdoor for people who\ncannot (either \"are not allowed to\", or \"cannot be bothered to\")\ncreate and maintain project specific access control by the\ntraditional means of filesystem access control based on user\ngroups, by allowing others to run controlled stuff under the\nrepository owner's uid.  In addition to having to worry about\nthe in-repo data properly being protected from people outside\nthe group, you now need to worry about the access through that\nbackdoor does not extend outside of the repository.  E.g. the\nrepository owner's $HOME that is outside the repository would be\nwritable that owner, but is not meant to be accessible by\nproject participants.  If you allow others to \"run as\" you, the\nonly thing that forbids that process running as you from\naccessing $HOME is an additional audit of git-daemon and the\nprograms it spawns.\n\nIn short, my initial reaction to this is not very supportive,\nnot because of the way it is coded but because of its security\ndesign.\n\nBut I may probably have misread the intention.\n"},{"id":"66697","messageId":"20080128001655.GY24004@spearce.org","threadId":"11758","inReplyTo":"7vsl0ix4gh.fsf@gitster.siamese.dyndns.org","subject":"git-daemon is insecure? (was: [RFC] Secure central repositories)","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-01-28T00:16:55Z","receivedAt":"2008-01-28T00:16:55Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Junio C Hamano <gitster@pobox.com> wrote:\n> \"Shawn O. Pearce\" <spearce@spearce.org> writes:\n> > This change allows any repository owner to setup a git-daemon\n> > that other users on the same host can connect through to perform\n> > upload-pack or receive-pack.\n> \n> My reading of this is that it creates a backdoor for people who\n[...]\n> In addition to having to worry about\n> the in-repo data properly being protected from people outside\n> the group, you now need to worry about the access through that\n> backdoor does not extend outside of the repository.  E.g. the\n> repository owner's $HOME that is outside the repository would be\n> writable that owner, but is not meant to be accessible by\n> project participants.  If you allow others to \"run as\" you, the\n> only thing that forbids that process running as you from\n> accessing $HOME is an additional audit of git-daemon and the\n> programs it spawns.\n\nSo you are partially suggesting that git-daemon isn't thought to\nbe secure, and that anything readable by the user that git-daemon\nis running as is fully exposed to the public Internet.  So the\naccess control attempts relating to --base-path or the check for\ngit-daemon-export-ok shouldn't really be trusted or relied upon.\n\nIf that really is the case, perhaps git-daemon should be audited\nand hardened further.  Last I checked, we encouraged people to run\nit to offer anonymous access to repositories, and the documentation\nsuggests there are publishing access controls that actually work.\nIf those controls cannot be trusted then we shouldn't encourage\nrunning git-daemon on untrusted networks.\n\n\nWith regards to this patch, yes, you can export your entire $HOME\nand maybe expose things you shouldn't or didn't want to.  But even\nwithout git installed you could do this:\n\n\tcp /bin/bash /tmp/be-like-mike\n\tchown $USER /tmp/be-like-mike\n\tchmod 777 /tmp/be-like-mike\n\tchmod u+s /tmp/be-like-mike\n\twall \"try out /tmp/be-like-mike today\"\n\nbut why would anyone do something that foolish?  UNIX provides the\ntools to do this, because there are cases where it can be useful,\nbut really, you have to be nuts to export all of $HOME.\n\n-- \nShawn.\n"},{"id":"66700","messageId":"20080128004722.GZ24004@spearce.org","threadId":"11758","inReplyTo":"7vsl0ix4gh.fsf@gitster.siamese.dyndns.org","subject":"Re: [RFC] Secure central repositories by UNIX socket authentication","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-01-28T00:47:22Z","receivedAt":"2008-01-28T00:47:22Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Junio C Hamano <gitster@pobox.com> wrote:\n> \"Shawn O. Pearce\" <spearce@spearce.org> writes:\n> \n> > This isn't anywhere near ready for application, but I'm floating\n> > it out there to see what people think.  Its a cool new feature that\n> > will certainly *not* be in 1.5.4.  :-)\n> >\n> > In a central repository configuration users may not have access\n> > to write new objects into a Git repository, or to edit the refs,\n> > especially if the repository is being protected by an update hook\n> > (e.g. contrib/hooks/updated-paranoid).\n> \n> Sorry, but I am puzzled about what this assumption is trying to\n> achieve here.\n> \n> If the configuration is based on central repository model,\n> wouldn't the users who are participating in the project have\n> write access to the repository by being in the project's group\n> and the repository initialized with core.sharedrepository=true?\n\nHmm.  core.sharedrepository is sometimes a bad solution.\n\ncore.sharedrepository means I need to give write access to both the\nrefs database and the object database to all members of the project.\nSome of whom may not be able to be trusted with tools like \"rm\",\nbut who need real shell access to that system anyway.  And sometimes\nmanagement won't allow users to have two accounts on the same system\n(one that is fixed to git-shell, and one that has a real shell)\nbecause the world would implode if a user was given two different\naccounts for two different access purposes.  I have no idea why that\nwould happen, but someone paid 3x what I earn has figured that out.\n\nLast I checked how UNIX filesystem access controls work, Git's\ncore.sharedrepository cannot possibly prevent a user from doing\nsomething like this:\n\n\tcd $repo_path\n\tgit log\n\t... go to lunch ...\n\trm -rf *\n\tgit log\n\t... bitch about how crappy git is ...\n\nNow any \"real\" version control and SQL database system allows the\nadministrator to restrict access to the database to avoid such\nmistakes.  CVS has pserver; SVN can use Apache HTTPd or its own\nserver; Perforce has its own server.  PostgreSQL, Oracle, Informix,\nDB2, even MySQL don't allow users to directly read or modify the\ndatabase files but instead ask them to go through authenticated\nsocket based interfaces.\n\nUnder a DSCM one would say the central model is insane, and that\nevery user should have their own repository, with write access\nlimited to only that user.  Obviously this is the model that\nkernel.org uses for kernel development, and that git itself uses\nfor git development.\n\n\nThe problem is the purely distributed model falls apart when you have\n50 \"Aunt Tillies\" making changes to the same 30 files at around the\nsame time.  Its bad enough that they have to have a local clone and\npush and fetch to share their changes.  Trying to explain that you\nneed to fetch+merge from Jane right now and Bob 3 minutes later,\nthen back to Jane to get the changes you are working on in parallel\nis sheer chaos.  Eyes gloss over and management declares \"Git is\ncrap; it cannot possibly be used in the enterprise\".\n\nHow do you setup 50 URLs into all 50 user's .git/config?  When a new\nuser joins the project how do you get their URL into all existing\nuser's trees?  Its total chaos in the cube farm as they shout back\nand forth \"Did you get Bob's changes?  Jane's?  Oh, maybe you didn't\nget Sally's too and that's why you aren't seeing X in there\".\n\nAt day-job I manage two completely different workflows, but both\nare based upon Git.\n\nReal developers who hack out program source code use a model much\nlike kernel.org.  Code is developed on topic branches, code is\nreviewed on topic branches at the individual change level, and code\nis merged from a developer's topic branch by a maintainer into a\nmaster branch.  For convience sake we store all topic branches in a\nsingle central repository, so everyone just has to have the \"origin\"\nURL in their local repositories.  We could deal with individual\ndeveloper repos like kernel.org does.  We choose not to simply\nbecause we also have to handle the next case, and its easier to\nnot have individual developer repos.\n\nAunt Tillies (who far out number real developers) edit small text\nfiles through a fancy GUI tool.  These folks don't really care\nabout versions, topic branches, and really don't want to know.\nAll they know is they have to edit \"Foo.data_file\" but to do so\nthey need the changes just made to \"Bar.data_file\" 5 minutes ago.\nUsually they don't even know if Bob, Sally, Jane or Nick made that\nchange, they just know they need it.  And their collective changes\n(from all 50 Aunt Tillies) all have to somehow wind up in the same\nGit branch at the end of the day so a real developer/maintainer\ncan pull it into a product build.\n\n\nI've lived through the daily fires of these workflows over the past\nyear and a half.  Its mostly settled out to something that works very\nwell for us, but its heavily based upon this concept of a central,\nshared repository.  And to keep the auditors and management happy\nI cannot allow \"rm -rf *\" to be executed by a user who happens to\nhave push access to that same repository.\n\nWe're not the only Git user that has a shared repository.  Doesn't\nX.org use a shared repository model?  I'm guessing that since they\nare an open source project they have less concerns about the \"rm\n-rf *\" case.  Wasn't the receive-pack service added to git-daemon\nto allow users to push into a repository, but not actually have\nwrite access to its filesystem?  Obviously someone else other than\njust me wants to safeguard the repository.\n\n-- \nShawn.\n"},{"id":"66701","messageId":"20080128005454.GA24004@spearce.org","threadId":"11758","inReplyTo":"alpine.LSU.1.00.0801271841230.23907@racer.site","subject":"Re: [RFC] Secure central repositories by UNIX socket authentication","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-01-28T00:54:54Z","receivedAt":"2008-01-28T00:54:54Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:\n> On Sun, 27 Jan 2008, Shawn O. Pearce wrote:\n> > \n> > Sure, $USER is set.  For \"jdoe\".  But due to the \"chmod 700 foo.git\" \n> > above jdoe isn't actually allowed access to the repository directory. So \n> > it doesn't matter what $USER is set to, jdoe cannot get to the files of \n> > the repository.\n> \n> Ah, that's what I missed.  I thought you already used git-shell, and did \n> not really read the chmod part.\n\nNo, I'm not using git-shell.  I'm actually currently using a setuid\ngit-receive-pack, which we've both agreed is horribly ugly.  I want\nto get away from that mess.\n \n> > 2) Use the SSH key feature to have remote users login as\n> >    the repository owner, but use the authorized_keys file\n> >    to force them to only execute git-shell.\n> >    This is uh, ugly, especially with 50+ users.\n> \n> Slight variation: do not permit other users access to your machine, except \n> via git-shell.  Then you don't need chmod 0700.\n\nThis isn't an option.  At least 10% of the users need a real shell\non this system, but cannot be trusted to not directly edit the\nrepository.  I'm also not able to get them different user accounts\n(one for git-shell, one for normal shell) because giving the same\nhuman two different user accounts on the same UNIX system will\ncause the world to explode.  At least according to some management\npeople who get paid 3x what I get paid.\n\nOf course, note those same people have also said that a SAMBA server\ncannot run on a system unless it is a SAMBA server.  Catch-22.\nYou cannot run SAMBA unless you are already running SAMBA.  :-\\\n\n> > 4) Add full user authentication to git-daemon and then do #3.\n> >    The user authentication can provide data down into the update\n> >    hook, such as by setting the $GIT_REMOTE_USER environment\n> >    variable.  That's basically this change, except I'm using bog\n> >    standard SSH to perform the authentication for me.\n> \n> AFAIR the plan was to keep git-daemon as simple and stupid as possible; in \n> particular _not_ to add any authentication.\n\nYup.  I think its smart.  Defer authentication off to the standard OS\ntools, so we don't have to deal with it in git itself.\n\nYet I'm offering a patch for comment that adds some level of\nauthentication to git-daemon.  At least it still just relies on\nUNIX uids and doesn't actually try to link to PAM.  :-)\n \n-- \nShawn.\n"},{"id":"66705","messageId":"7vk5luwt6q.fsf@gitster.siamese.dyndns.org","threadId":"11758","inReplyTo":"20080128001655.GY24004@spearce.org","subject":"Re: git-daemon is insecure?","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-01-28T03:00:13Z","receivedAt":"2008-01-28T03:00:13Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Shawn O. Pearce\" <spearce@spearce.org> writes:\n\n> With regards to this patch, yes, you can export your entire $HOME\n> and maybe expose things you shouldn't or didn't want to.\n\nThat was not what I meant.  git-daemon running as nobody.project\nwill allow read access to project group's files, and the\nwhitelisting and --base-path are ways to limit it to files that\nare in the repository.  But the process still has the power to\nread files outside that can be read nobody user or project\ngroup, the only thing needed is for git-daemon and whatever it\nspawn to have bugs.\n\nBut the point is that \"power to read files outside\" is still\nlimited to nobody.project, even if there are such bugs to allow\nit escape the whitelist/base-path jail.  It won't extend to\nanybody's $HOME.\n\nIf you run git-daemon as spearce.spearce, you cannot rely on\nthat built-in limitation.\n"},{"id":"66706","messageId":"20080128032040.GB24004@spearce.org","threadId":"11758","inReplyTo":"7vk5luwt6q.fsf@gitster.siamese.dyndns.org","subject":"Re: git-daemon is insecure?","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-01-28T03:20:40Z","receivedAt":"2008-01-28T03:20:40Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Junio C Hamano <gitster@pobox.com> wrote:\n> \"Shawn O. Pearce\" <spearce@spearce.org> writes:\n> \n> > With regards to this patch, yes, you can export your entire $HOME\n> > and maybe expose things you shouldn't or didn't want to.\n> \n> That was not what I meant.  git-daemon running as nobody.project\n> will allow read access to project group's files, and the\n> whitelisting and --base-path are ways to limit it to files that\n> are in the repository.  But the process still has the power to\n> read files outside that can be read nobody user or project\n> group, the only thing needed is for git-daemon and whatever it\n> spawn to have bugs.\n> \n> But the point is that \"power to read files outside\" is still\n> limited to nobody.project, even if there are such bugs to allow\n> it escape the whitelist/base-path jail.  It won't extend to\n> anybody's $HOME.\n> \n> If you run git-daemon as spearce.spearce, you cannot rely on\n> that built-in limitation.\n \nSure.  Which is why I was planning on running git-daemon as\ngitadmin.gitadmin, with all central repos owned by gitadmin,\nand basically nothing else at all.\n\nI can just as easily start lighthttpd on $HOME.  Or Apache.\nBoth are insane.\n\n-- \nShawn.\n"},{"id":"66714","messageId":"7vabmqwgvt.fsf@gitster.siamese.dyndns.org","threadId":"11758","inReplyTo":"20080128004722.GZ24004@spearce.org","subject":"Re: [RFC] Secure central repositories by UNIX socket authentication","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-01-28T07:25:58Z","receivedAt":"2008-01-28T07:25:58Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Shawn O. Pearce\" <spearce@spearce.org> writes:\n\n> Hmm.  core.sharedrepository is sometimes a bad solution.\n>\n> core.sharedrepository means I need to give write access to both the\n> refs database and the object database to all members of the project.\n> Some of whom may not be able to be trusted with tools like \"rm\",\n> but who need real shell access to that system anyway.  And sometimes\n> management won't allow users to have two accounts on the same system\n> (one that is fixed to git-shell, and one that has a real shell)\n> because the world would implode if a user was given two different\n> accounts for two different access purposes.\n\nOk, that was the motiviation I did not get from your original\nmessage.  It begins to make sense somewhat.\n\nAnother approach to do the same I can think of, without having\nto add 50 new accounts for 50 users, would be to collect a ssh\nkey from each of these 50 users, and have 1 line per user in the\nauthorized_keys file of gitadmin.gitadmin user (who owns the\nrepository with the paranoia hook that decides the authorization\naspect of the repository).  The authentication would come from\nthe environment=\"Name=value\" option in the authorized_keys file.\nEach of your aunt tillies can push or fetch over ssh using the\nkey she has in the gitadmin.gitadmin's authorized_keys file.\n\nI suspect the \"hackiness\" factor from the aesthetics viewpoint\nis probably about the same, but this would work with the current\ncode without patches, no?\n"},{"id":"66717","messageId":"20080128075125.GC24004@spearce.org","threadId":"11758","inReplyTo":"7vabmqwgvt.fsf@gitster.siamese.dyndns.org","subject":"Re: [RFC] Secure central repositories by UNIX socket authentication","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-01-28T07:51:25Z","receivedAt":"2008-01-28T07:51:25Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Junio C Hamano <gitster@pobox.com> wrote:\n> \"Shawn O. Pearce\" <spearce@spearce.org> writes:\n> \n> > Hmm.  core.sharedrepository is sometimes a bad solution.\n> >\n> > core.sharedrepository means I need to give write access to both the\n> > refs database and the object database to all members of the project.\n> > Some of whom may not be able to be trusted with tools like \"rm\",\n> > but who need real shell access to that system anyway.  And sometimes\n> > management won't allow users to have two accounts on the same system\n> > (one that is fixed to git-shell, and one that has a real shell)\n> > because the world would implode if a user was given two different\n> > accounts for two different access purposes.\n> \n> Ok, that was the motiviation I did not get from your original\n> message.  It begins to make sense somewhat.\n> \n> Another approach to do the same I can think of, without having\n> to add 50 new accounts for 50 users, would be to collect a ssh\n> key from each of these 50 users, and have 1 line per user in the\n> authorized_keys file of gitadmin.gitadmin user (who owns the\n> repository with the paranoia hook that decides the authorization\n> aspect of the repository).  The authentication would come from\n> the environment=\"Name=value\" option in the authorized_keys file.\n> Each of your aunt tillies can push or fetch over ssh using the\n> key she has in the gitadmin.gitadmin's authorized_keys file.\n\nYea.  The downside to this is we have to maintain that\nauthorized_keys file.  Today each user can generate their\nown SSH key and upload to their own authorized_keys file.\n\nI've had enough cases of users losing their SSH key and\nneeding to recreate it that I'd rather not have to manage\na 50 user long authorized_keys file.\n\nI'm also not sure of what the performance implication is to SSH for\nauthentication with 50 public keys in a single file.  Does it slow\ndown as its running a check against each key, or is there something\nsmarter to filter the keys?  From the description of the format in\nthe OpenSSH manpage it doesn't look like its possible other than\nto probe every key in turn for every authentication attempt.\n\nIn other words, the authorized_keys is a nice feature, but it seems\nlike its more useful for a remote backup job logging in as root, or a\n\"vacation mode\" where a coworker is permitted to execute a specific\ncommand while you are away.  But maybe I'm missing something.\n \n> I suspect the \"hackiness\" factor from the aesthetics viewpoint\n> is probably about the same, but this would work with the current\n> code without patches, no?\n\nYes, it would, obviously.  But it has the downside of needing to\nmanage a single common authorized_keys file.  Which is something\nI think I'd like to avoid.\n\nIt also doesn't do anything about upload-pack, as that has no hook\nto perform authorization.  Of course just adding some sort of ref\nfiltering hook to upload-pack is still a smaller patch than adding\nall this UNIX socket redirection and an upload-pack hook.  :)\n\n-- \nShawn.\n"},{"id":"66718","messageId":"20080128075642.GD24004@spearce.org","threadId":"11758","inReplyTo":"7vabmqwgvt.fsf@gitster.siamese.dyndns.org","subject":"Re: [RFC] Secure central repositories by UNIX socket authentication","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-01-28T07:56:42Z","receivedAt":"2008-01-28T07:56:42Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Junio C Hamano <gitster@pobox.com> wrote:\n> \"Shawn O. Pearce\" <spearce@spearce.org> writes:\n> \n> > Hmm.  core.sharedrepository is sometimes a bad solution.\n> >\n> > core.sharedrepository means I need to give write access to both the\n> > refs database and the object database to all members of the project.\n> > Some of whom may not be able to be trusted with tools like \"rm\",\n> > but who need real shell access to that system anyway.  And sometimes\n> > management won't allow users to have two accounts on the same system\n> > (one that is fixed to git-shell, and one that has a real shell)\n> > because the world would implode if a user was given two different\n> > accounts for two different access purposes.\n...\n> Another approach to do the same I can think of, without having\n> to add 50 new accounts for 50 users, would be to collect a ssh\n> key from each of these 50 users,\n\nAlso, our network of servers and desktops is actually now managed\nentirely through Active Directory.  So all possible users already\nhave accounts on every server.  Passwords are synchronized across\nthe entire network.\n\nIn other words, all of the management overheads associated with\nuser accounts has already been paid.  Centralized SSH/SSL/PGP key\nmanagement is only adding an additional burden.  For my day-job\nanyway.\n\n-- \nShawn.\n"},{"id":"66720","messageId":"20080128081427.GP26664@dpotapov.dyndns.org","threadId":"11758","inReplyTo":"20080127173212.GW24004@spearce.org","subject":"Re: [RFC] Secure central repositories by UNIX socket authentication","fromName":"Dmitry Potapov","fromEmail":"dpotapov@gmail.com","sentAt":"2008-01-28T08:14:27Z","receivedAt":"2008-01-28T08:14:27Z","isPatch":false,"sender":{"key":"dpotapov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6568595?v=4"},"body":"On Sun, Jan 27, 2008 at 12:32:13PM -0500, Shawn O. Pearce wrote:\n> \n> So you come down to four options:\n\n<snip>\n\nHow about gitosis? It requires only one extra user (usually called git),\nwhich is the owner of all repos. This user has git-shell as its login\nshell. All users are authorized by their ssh keys. The configuration and\nkyes are stored in the special repo called gitosis-admin. You can define\nwhat users to what repositories have read or write access. This is done\nby adding a user to one or more groups defined in gitosis configuration.\nYou can have as much groups as you want. The default configuration looks\nlike this:\n\n[group gitosis-admin]\nwritable = gitosis-admin\nmembers = your-name\n\nIt defines the gitosis-admin group, member of which can write to the\ngitosis-admin repo, and you are member of that group.\n\nWRRNING: I have not used gitosis myself, but it looks worthy of a try.\n\nDmitry\n"},{"id":"66749","messageId":"alpine.DEB.1.00.0801280922160.3774@alchemy.localdomain","threadId":"11758","inReplyTo":"20080128075125.GC24004@spearce.org","subject":"Re: [RFC] Secure central repositories by UNIX socket authentication","fromName":"Asheesh Laroia","fromEmail":"asheesh@asheesh.org","sentAt":"2008-01-28T14:23:51Z","receivedAt":"2008-01-28T14:23:51Z","isPatch":false,"sender":{"key":"asheesh@asheesh.org","avatar":"https://avatars.githubusercontent.com/u/25457?v=4"},"body":"On Mon, 28 Jan 2008, Shawn O. Pearce wrote:\n\n> Junio C Hamano <gitster@pobox.com> wrote:\n>> \"Shawn O. Pearce\" <spearce@spearce.org> writes:\n>>\n>>> Hmm.  core.sharedrepository is sometimes a bad solution.\n>>>\n>>> core.sharedrepository means I need to give write access to both the \n>>> refs database and the object database to all members of the project. \n>>> Some of whom may not be able to be trusted with tools like \"rm\", but \n>>> who need real shell access to that system anyway.  And sometimes \n>>> management won't allow users to have two accounts on the same system \n>>> (one that is fixed to git-shell, and one that has a real shell) \n>>> because the world would implode if a user was given two different \n>>> accounts for two different access purposes.\n>>\n>> Ok, that was the motiviation I did not get from your original message. \n>> It begins to make sense somewhat.\n>>\n>> Another approach to do the same I can think of, without having to add \n>> 50 new accounts for 50 users, would be to collect a ssh key from each \n>> of these 50 users, and have 1 line per user in the authorized_keys file \n>> of gitadmin.gitadmin user (who owns the repository with the paranoia \n>> hook that decides the authorization aspect of the repository).  The \n>> authentication would come from the environment=\"Name=value\" option in \n>> the authorized_keys file. Each of your aunt tillies can push or fetch \n>> over ssh using the key she has in the gitadmin.gitadmin's \n>> authorized_keys file.\n>\n> Yea.  The downside to this is we have to maintain that authorized_keys \n> file.  Today each user can generate their own SSH key and upload to \n> their own authorized_keys file.\n>\n> I've had enough cases of users losing their SSH key and needing to \n> recreate it that I'd rather not have to manage a 50 user long \n> authorized_keys file.\n\nFor what it's worth, if you haven't seen gitosis yet, you might want to \ntake a look - at least it makes managing the keys easy. \nhttp://scie.nti.st/2007/11/14/hosting-git-repositories-the-easy-and-secure-way \nhas a nice tutorial.\n\n-- Asheesh.\n\n-- \nHe who despairs over an event is a coward, but he who holds hopes for\nthe human condition is a fool.\n \t\t-- Albert Camus\n"},{"id":"66793","messageId":"20080129031105.GI24004@spearce.org","threadId":"11758","inReplyTo":"alpine.DEB.1.00.0801280922160.3774@alchemy.localdomain","subject":"Re: [RFC] Secure central repositories by UNIX socket authentication","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-01-29T03:11:05Z","receivedAt":"2008-01-29T03:11:05Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Asheesh Laroia <asheesh@asheesh.org> wrote:\n> On Mon, 28 Jan 2008, Shawn O. Pearce wrote:\n> >\n> >I've had enough cases of users losing their SSH key and needing to \n> >recreate it that I'd rather not have to manage a 50 user long \n> >authorized_keys file.\n> \n> For what it's worth, if you haven't seen gitosis yet, you might want to \n> take a look - at least it makes managing the keys easy. \n> http://scie.nti.st/2007/11/14/hosting-git-repositories-the-easy-and-secure-way \n> has a nice tutorial.\n\nYea, I've looked at it before.  There's a few reasons I don't\nuse gitosis, although it does look to be an excellent chunk of\nGit automation:\n\n* Its access controls aren't as powerful\n\n  Frankly the contrib/hooks/update-paranoid script is a lot more\n  powerful then gitosis is, in terms of how it controls what\n  branches a user can modify, and even what files they can change\n  on a particular branch.  And yes, I really do have rulesets that\n  bend that hook to its limits.\n\n* It uses the OpenSSH authorized_keys file format\n\n  I'm required to use the F-Secure SSH commerical server at\n  day-job, because its \"more trusthworthy\" than the portable OpenSSH\n  distribution.  It uses a different syntax for the authorized keys,\n  but can do essentially the same restricted command concept.\n\n* If its in git, I prefer raw repository access\n\n  gitosis yanks stuff out into normal files to access it at runtime,\n  e.g. its configuration file.  I've had bad experiences with CVS not\n  properly updating its admin files when changes are made to them.\n  The update-paranoid hook I use actually cats the objects right\n  out of the admin ODB on demand, ensuring its always evaluating\n  the most recent version of the access rules.\n\n* Its Python based.\n\n  I don't grok Python, and would rather not learn to.  So hacking\n  on gitosis isn't something that I would be doing.  Ditto with\n  all of my day-job cohorts.  We use Perl, Bourne shell, and Java,\n  with some tiny amount of Tk thrown about (though I'd say I'm\n  probably the only one there that even remotely groks Tcl/Tk).\n\nBut thanks for the pointer.\n\nNow if others corrected all of the above in gitosis (except the\nlast item of course, I don't expect it to be rewritten in one of\nmy preferred languages) I'd reconsider using it, because inventing\nwheels sucks.\n\n-- \nShawn.\n"}]}