git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH] daemon: --access-hook option

From
Junio C Hamano <gitster@pobox.com>
Date
Aug 15, 2012, 05:12 UTC
Message-ID
<7vobmc7n80.fsf_-_@alter.siamese.dyndns.org>
In-Reply-To
<7vpq6t9v5s.fsf@alter.siamese.dyndns.org>

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.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 * This time, minimally tested, with a documentation update.
 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 31b28fc..c3ba4d7 100644
--- a/Documentation/git-daemon.txt
+++ b/Documentation/git-daemon.txt
@@ -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>...]
 
@@ -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
diff --git a/daemon.c b/daemon.c
index ab21e66..4602b46 100644
--- a/daemon.c
+++ b/daemon.c
@@ -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>...]";
@@ -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;
@@ -304,6 +370,13 @@ static int run_service(char *dir, struct daemon_service *service)
 	}
 
 	/*
+	 * 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;
-- 
1.7.12.rc2.85.g1de7134
Previous: Junio C HamanoNext: Shawn Pearce
Message 7 of 9 in “Implement ACL module architecture and sample MySQL ACL module”
  1. Implement ACL module architecture and sample MySQL ACL moduleMichal Novotny, Aug 14, 2012
  2. Junio C HamanoAug 14, 2012
  3. Shawn PearceAug 14, 2012
  4. Junio C HamanoAug 14, 2012
  5. Shawn PearceAug 14, 2012
  6. Junio C HamanoAug 14, 2012
  7. daemon: --access-hook optionJunio C Hamano, Aug 15, 2012
  8. Shawn PearceAug 15, 2012
  9. Michal NovotnyAug 21, 2012

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.