Skip to content

Commit 19801d6

Browse files
committed
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
2 parents 200282f + 93741e4 commit 19801d6

File tree

2 files changed

+93
-0
lines changed

2 files changed

+93
-0
lines changed

Documentation/git-daemon.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ SYNOPSIS
1616
[--reuseaddr] [--detach] [--pid-file=<file>]
1717
[--enable=<service>] [--disable=<service>]
1818
[--allow-override=<service>] [--forbid-override=<service>]
19+
[--access-hook=<path>]
1920
[--inetd | [--listen=<host_or_ipaddr>] [--port=<n>] [--user=<user> [--group=<group>]]
2021
[<directory>...]
2122

@@ -171,6 +172,21 @@ the facility of inet daemon to achieve the same before spawning
171172
errors are not enabled, all errors report "access denied" to the
172173
client. The default is --no-informative-errors.
173174

175+
--access-hook=<path>::
176+
Every time a client connects, first run an external command
177+
specified by the <path> with service name (e.g. "upload-pack"),
178+
path to the repository, hostname (%H), canonical hostname
179+
(%CH), ip address (%IP), and tcp port (%P) as its command line
180+
arguments. The external command can decide to decline the
181+
service by exiting with a non-zero status (or to allow it by
182+
exiting with a zero status). It can also look at the $REMOTE_ADDR
183+
and $REMOTE_PORT environment variables to learn about the
184+
requestor when making this decision.
185+
+
186+
The external command can optionally write a single line to its
187+
standard output to be sent to the requestor as an error message when
188+
it declines the service.
189+
174190
<directory>::
175191
A directory to add to the whitelist of allowed directories. Unless
176192
--strict-paths is specified this will also include subdirectories

daemon.c

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ static const char daemon_usage[] =
3030
" [--interpolated-path=<path>]\n"
3131
" [--reuseaddr] [--pid-file=<file>]\n"
3232
" [--(enable|disable|allow-override|forbid-override)=<service>]\n"
33+
" [--access-hook=<path>]\n"
3334
" [--inetd | [--listen=<host_or_ipaddr>] [--port=<n>]\n"
3435
" [--detach] [--user=<user> [--group=<group>]]\n"
3536
" [<directory>...]";
@@ -256,6 +257,71 @@ static int daemon_error(const char *dir, const char *msg)
256257
return -1;
257258
}
258259

260+
static char *access_hook;
261+
262+
static int run_access_hook(struct daemon_service *service, const char *dir, const char *path)
263+
{
264+
struct child_process child;
265+
struct strbuf buf = STRBUF_INIT;
266+
const char *argv[8];
267+
const char **arg = argv;
268+
char *eol;
269+
int seen_errors = 0;
270+
271+
#define STRARG(x) ((x) ? (x) : "")
272+
*arg++ = access_hook;
273+
*arg++ = service->name;
274+
*arg++ = path;
275+
*arg++ = STRARG(hostname);
276+
*arg++ = STRARG(canon_hostname);
277+
*arg++ = STRARG(ip_address);
278+
*arg++ = STRARG(tcp_port);
279+
*arg = NULL;
280+
#undef STRARG
281+
282+
memset(&child, 0, sizeof(child));
283+
child.use_shell = 1;
284+
child.argv = argv;
285+
child.no_stdin = 1;
286+
child.no_stderr = 1;
287+
child.out = -1;
288+
if (start_command(&child)) {
289+
logerror("daemon access hook '%s' failed to start",
290+
access_hook);
291+
goto error_return;
292+
}
293+
if (strbuf_read(&buf, child.out, 0) < 0) {
294+
logerror("failed to read from pipe to daemon access hook '%s'",
295+
access_hook);
296+
strbuf_reset(&buf);
297+
seen_errors = 1;
298+
}
299+
if (close(child.out) < 0) {
300+
logerror("failed to close pipe to daemon access hook '%s'",
301+
access_hook);
302+
seen_errors = 1;
303+
}
304+
if (finish_command(&child))
305+
seen_errors = 1;
306+
307+
if (!seen_errors) {
308+
strbuf_release(&buf);
309+
return 0;
310+
}
311+
312+
error_return:
313+
strbuf_ltrim(&buf);
314+
if (!buf.len)
315+
strbuf_addstr(&buf, "service rejected");
316+
eol = strchr(buf.buf, '\n');
317+
if (eol)
318+
*eol = '\0';
319+
errno = EACCES;
320+
daemon_error(dir, buf.buf);
321+
strbuf_release(&buf);
322+
return -1;
323+
}
324+
259325
static int run_service(char *dir, struct daemon_service *service)
260326
{
261327
const char *path;
@@ -303,6 +369,13 @@ static int run_service(char *dir, struct daemon_service *service)
303369
return daemon_error(dir, "service not enabled");
304370
}
305371

372+
/*
373+
* Optionally, a hook can choose to deny access to the
374+
* repository depending on the phase of the moon.
375+
*/
376+
if (access_hook && run_access_hook(service, dir, path))
377+
return -1;
378+
306379
/*
307380
* We'll ignore SIGTERM from now on, we have a
308381
* good client.
@@ -1142,6 +1215,10 @@ int main(int argc, char **argv)
11421215
export_all_trees = 1;
11431216
continue;
11441217
}
1218+
if (!prefixcmp(arg, "--access-hook=")) {
1219+
access_hook = arg + 14;
1220+
continue;
1221+
}
11451222
if (!prefixcmp(arg, "--timeout=")) {
11461223
timeout = atoi(arg+10);
11471224
continue;

0 commit comments

Comments
 (0)