daemon: --access-hook option
authorJunio C Hamano <gitster@pobox.com>
Tue, 14 Aug 2012 18:37:51 +0000 (11:37 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 15 Aug 2012 18:01:55 +0000 (11:01 -0700)
The --access-hook option to "git daemon" specifies an external
command to be run every time a client connects, with

 - service name (e.g. "upload-pack", etc.),
 - path to the repository,
 - hostname (%H),
 - canonical hostname (%CH),
 - ip address (%IP),
 - tcp port (%P)

as its command line arguments.  The external command can decide to
decline the service by exiting with a non-zero status (or to allow it
by exiting with a zero status).  It can also look at the $REMOTE_ADDR
and $REMOTE_PORT environment variables to learn about the requestor
when making this decision.

The external command can optionally write a single line to its
standard output to be sent to the requestor as an error message when
it declines the service.

Acked-by: Shawn O. Pearce <spearce@spearce.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git-daemon.txt
daemon.c

index 31b28fc29fc6e01e8505f2179b08082cf734377c..c3ba4d79769ddea71fa48cf2674a8ebe094e35c0 100644 (file)
@@ -16,6 +16,7 @@ SYNOPSIS
             [--reuseaddr] [--detach] [--pid-file=<file>]
             [--enable=<service>] [--disable=<service>]
             [--allow-override=<service>] [--forbid-override=<service>]
+            [--access-hook=<path>]
             [--inetd | [--listen=<host_or_ipaddr>] [--port=<n>] [--user=<user> [--group=<group>]]
             [<directory>...]
 
@@ -171,6 +172,21 @@ the facility of inet daemon to achieve the same before spawning
        errors are not enabled, all errors report "access denied" to the
        client. The default is --no-informative-errors.
 
+--access-hook=<path>::
+       Every time a client connects, first run an external command
+       specified by the <path> with service name (e.g. "upload-pack"),
+       path to the repository, hostname (%H), canonical hostname
+       (%CH), ip address (%IP), and tcp port (%P) as its command line
+       arguments. The external command can decide to decline the
+       service by exiting with a non-zero status (or to allow it by
+       exiting with a zero status).  It can also look at the $REMOTE_ADDR
+       and $REMOTE_PORT environment variables to learn about the
+       requestor when making this decision.
++
+The external command can optionally write a single line to its
+standard output to be sent to the requestor as an error message when
+it declines the service.
+
 <directory>::
        A directory to add to the whitelist of allowed directories. Unless
        --strict-paths is specified this will also include subdirectories
index ab21e66b2fb69015c3085761b344b11053c67f4a..4602b46a5c39e1d501143ab4e95b55aff5c8f23b 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -30,6 +30,7 @@ static const char daemon_usage[] =
 "           [--interpolated-path=<path>]\n"
 "           [--reuseaddr] [--pid-file=<file>]\n"
 "           [--(enable|disable|allow-override|forbid-override)=<service>]\n"
+"           [--access-hook=<path>]\n"
 "           [--inetd | [--listen=<host_or_ipaddr>] [--port=<n>]\n"
 "                      [--detach] [--user=<user> [--group=<group>]]\n"
 "           [<directory>...]";
@@ -256,6 +257,71 @@ static int daemon_error(const char *dir, const char *msg)
        return -1;
 }
 
+static char *access_hook;
+
+static int run_access_hook(struct daemon_service *service, const char *dir, const char *path)
+{
+       struct child_process child;
+       struct strbuf buf = STRBUF_INIT;
+       const char *argv[8];
+       const char **arg = argv;
+       char *eol;
+       int seen_errors = 0;
+
+#define STRARG(x) ((x) ? (x) : "")
+       *arg++ = access_hook;
+       *arg++ = service->name;
+       *arg++ = path;
+       *arg++ = STRARG(hostname);
+       *arg++ = STRARG(canon_hostname);
+       *arg++ = STRARG(ip_address);
+       *arg++ = STRARG(tcp_port);
+       *arg = NULL;
+#undef STRARG
+
+       memset(&child, 0, sizeof(child));
+       child.use_shell = 1;
+       child.argv = argv;
+       child.no_stdin = 1;
+       child.no_stderr = 1;
+       child.out = -1;
+       if (start_command(&child)) {
+               logerror("daemon access hook '%s' failed to start",
+                        access_hook);
+               goto error_return;
+       }
+       if (strbuf_read(&buf, child.out, 0) < 0) {
+               logerror("failed to read from pipe to daemon access hook '%s'",
+                        access_hook);
+               strbuf_reset(&buf);
+               seen_errors = 1;
+       }
+       if (close(child.out) < 0) {
+               logerror("failed to close pipe to daemon access hook '%s'",
+                        access_hook);
+               seen_errors = 1;
+       }
+       if (finish_command(&child))
+               seen_errors = 1;
+
+       if (!seen_errors) {
+               strbuf_release(&buf);
+               return 0;
+       }
+
+error_return:
+       strbuf_ltrim(&buf);
+       if (!buf.len)
+               strbuf_addstr(&buf, "service rejected");
+       eol = strchr(buf.buf, '\n');
+       if (eol)
+               *eol = '\0';
+       errno = EACCES;
+       daemon_error(dir, buf.buf);
+       strbuf_release(&buf);
+       return -1;
+}
+
 static int run_service(char *dir, struct daemon_service *service)
 {
        const char *path;
@@ -303,6 +369,13 @@ static int run_service(char *dir, struct daemon_service *service)
                return daemon_error(dir, "service not enabled");
        }
 
+       /*
+        * Optionally, a hook can choose to deny access to the
+        * repository depending on the phase of the moon.
+        */
+       if (access_hook && run_access_hook(service, dir, path))
+               return -1;
+
        /*
         * We'll ignore SIGTERM from now on, we have a
         * good client.
@@ -1142,6 +1215,10 @@ int main(int argc, char **argv)
                        export_all_trees = 1;
                        continue;
                }
+               if (!prefixcmp(arg, "--access-hook=")) {
+                       access_hook = arg + 14;
+                       continue;
+               }
                if (!prefixcmp(arg, "--timeout=")) {
                        timeout = atoi(arg+10);
                        continue;