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>] [--reuseaddr] [--detach] [--pid-file=<file>]
[--enable=<service>] [--disable=<service>] [--enable=<service>] [--disable=<service>]
[--allow-override=<service>] [--forbid-override=<service>] [--allow-override=<service>] [--forbid-override=<service>]
[--access-hook=<path>]
[--inetd | [--listen=<host_or_ipaddr>] [--port=<n>] [--user=<user> [--group=<group>]] [--inetd | [--listen=<host_or_ipaddr>] [--port=<n>] [--user=<user> [--group=<group>]]
[<directory>...] [<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 errors are not enabled, all errors report "access denied" to the
client. The default is --no-informative-errors. 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>:: <directory>::
A directory to add to the whitelist of allowed directories. Unless A directory to add to the whitelist of allowed directories. Unless
--strict-paths is specified this will also include subdirectories --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" " [--interpolated-path=<path>]\n"
" [--reuseaddr] [--pid-file=<file>]\n" " [--reuseaddr] [--pid-file=<file>]\n"
" [--(enable|disable|allow-override|forbid-override)=<service>]\n" " [--(enable|disable|allow-override|forbid-override)=<service>]\n"
" [--access-hook=<path>]\n"
" [--inetd | [--listen=<host_or_ipaddr>] [--port=<n>]\n" " [--inetd | [--listen=<host_or_ipaddr>] [--port=<n>]\n"
" [--detach] [--user=<user> [--group=<group>]]\n" " [--detach] [--user=<user> [--group=<group>]]\n"
" [<directory>...]"; " [<directory>...]";
Expand Down Expand Up @@ -256,6 +257,71 @@ static int daemon_error(const char *dir, const char *msg)
return -1; 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) static int run_service(char *dir, struct daemon_service *service)
{ {
const char *path; 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"); 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 * We'll ignore SIGTERM from now on, we have a
* good client. * good client.
Expand Down Expand Up @@ -1142,6 +1215,10 @@ int main(int argc, char **argv)
export_all_trees = 1; export_all_trees = 1;
continue; continue;
} }
if (!prefixcmp(arg, "--access-hook=")) {
access_hook = arg + 14;
continue;
}
if (!prefixcmp(arg, "--timeout=")) { if (!prefixcmp(arg, "--timeout=")) {
timeout = atoi(arg+10); timeout = atoi(arg+10);
continue; continue;
Expand Down

0 comments on commit 93741e4

Please sign in to comment.