Skip to content

Commit

Permalink
daemon: --access-hook option
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
gitster committed Aug 15, 2012
1 parent 86faaf9 commit 93741e4
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 0 deletions.
16 changes: 16 additions & 0 deletions Documentation/git-daemon.txt
Expand Up @@ -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>...]

Expand Down Expand Up @@ -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
Expand Down
77 changes: 77 additions & 0 deletions daemon.c
Expand Up @@ -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>...]";
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 93741e4

Please sign in to comment.