From 93741e4a915fc33a00e6facff88cd27565d10f82 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 14 Aug 2012 11:37:51 -0700 Subject: [PATCH] daemon: --access-hook option 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 Signed-off-by: Junio C Hamano --- Documentation/git-daemon.txt | 16 ++++++++ daemon.c | 77 ++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 31b28fc29..c3ba4d797 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -16,6 +16,7 @@ SYNOPSIS [--reuseaddr] [--detach] [--pid-file=] [--enable=] [--disable=] [--allow-override=] [--forbid-override=] + [--access-hook=] [--inetd | [--listen=] [--port=] [--user= [--group=]] [...] @@ -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=:: + Every time a client connects, first run an external command + specified by the 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. + :: A directory to add to the whitelist of allowed directories. Unless --strict-paths is specified this will also include subdirectories diff --git a/daemon.c b/daemon.c index ab21e66b2..4602b46a5 100644 --- a/daemon.c +++ b/daemon.c @@ -30,6 +30,7 @@ static const char daemon_usage[] = " [--interpolated-path=]\n" " [--reuseaddr] [--pid-file=]\n" " [--(enable|disable|allow-override|forbid-override)=]\n" +" [--access-hook=]\n" " [--inetd | [--listen=] [--port=]\n" " [--detach] [--user= [--group=]]\n" " [...]"; @@ -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; -- 2.26.2