Merge branch 'jc/daemon-access-hook'

Allow an external command to tell git-daemon to decline service
based on the client address, repository path, etc.

* jc/daemon-access-hook:
  daemon: --access-hook option
This commit is contained in:
Junio C Hamano 2012-09-03 15:54:03 -07:00
commit 19801d6a27
2 changed files with 93 additions and 0 deletions

View file

@ -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>...]
@ -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

View file

@ -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>...]";
@ -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;
@ -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.
@ -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;