summaryrefslogtreecommitdiff
path: root/daemon.c
diff options
context:
space:
mode:
Diffstat (limited to 'daemon.c')
-rw-r--r--daemon.c155
1 files changed, 142 insertions, 13 deletions
diff --git a/daemon.c b/daemon.c
index 4c8346d5a1..df8c0ab058 100644
--- a/daemon.c
+++ b/daemon.c
@@ -9,10 +9,6 @@
#define HOST_NAME_MAX 256
#endif
-#ifndef NI_MAXSERV
-#define NI_MAXSERV 32
-#endif
-
#ifdef NO_INITGROUPS
#define initgroups(x, y) (0) /* nothing */
#endif
@@ -20,6 +16,7 @@
static int log_syslog;
static int verbose;
static int reuseaddr;
+static int informative_errors;
static const char daemon_usage[] =
"git daemon [--verbose] [--syslog] [--export-all]\n"
@@ -29,6 +26,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>...]";
@@ -108,11 +106,11 @@ static void NORETURN daemon_die(const char *err, va_list params)
exit(1);
}
-static char *path_ok(char *directory)
+static const char *path_ok(char *directory)
{
static char rpath[PATH_MAX];
static char interp_path[PATH_MAX];
- char *path;
+ const char *path;
char *dir;
dir = directory;
@@ -247,6 +245,79 @@ static int git_daemon_config(const char *var, const char *value, void *cb)
return 0;
}
+static int daemon_error(const char *dir, const char *msg)
+{
+ if (!informative_errors)
+ msg = "access denied or repository not exported";
+ packet_write(1, "ERR %s: %s", msg, dir);
+ 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;
@@ -257,11 +328,11 @@ static int run_service(char *dir, struct daemon_service *service)
if (!enabled && !service->overridable) {
logerror("'%s': service not enabled.", service->name);
errno = EACCES;
- return -1;
+ return daemon_error(dir, "service not enabled");
}
if (!(path = path_ok(dir)))
- return -1;
+ return daemon_error(dir, "no such repository");
/*
* Security on the cheap.
@@ -277,7 +348,7 @@ static int run_service(char *dir, struct daemon_service *service)
if (!export_all_trees && access("git-daemon-export-ok", F_OK)) {
logerror("'%s': repository not exported.", path);
errno = EACCES;
- return -1;
+ return daemon_error(dir, "repository not exported");
}
if (service->overridable) {
@@ -291,10 +362,17 @@ static int run_service(char *dir, struct daemon_service *service)
logerror("'%s': service not enabled for '%s'",
service->name, path);
errno = EACCES;
- return -1;
+ 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.
*/
@@ -734,6 +812,29 @@ struct socketlist {
size_t alloc;
};
+static const char *ip2str(int family, struct sockaddr *sin, socklen_t len)
+{
+#ifdef NO_IPV6
+ static char ip[INET_ADDRSTRLEN];
+#else
+ static char ip[INET6_ADDRSTRLEN];
+#endif
+
+ switch (family) {
+#ifndef NO_IPV6
+ case AF_INET6:
+ inet_ntop(family, &((struct sockaddr_in6*)sin)->sin6_addr, ip, len);
+ break;
+#endif
+ case AF_INET:
+ inet_ntop(family, &((struct sockaddr_in*)sin)->sin_addr, ip, len);
+ break;
+ default:
+ strcpy(ip, "<unknown>");
+ }
+ return ip;
+}
+
#ifndef NO_IPV6
static int setup_named_sock(char *listen_addr, int listen_port, struct socketlist *socklist)
@@ -780,15 +881,22 @@ static int setup_named_sock(char *listen_addr, int listen_port, struct socketlis
#endif
if (set_reuse_addr(sockfd)) {
+ logerror("Could not set SO_REUSEADDR: %s", strerror(errno));
close(sockfd);
continue;
}
if (bind(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
+ logerror("Could not bind to %s: %s",
+ ip2str(ai->ai_family, ai->ai_addr, ai->ai_addrlen),
+ strerror(errno));
close(sockfd);
continue; /* not fatal */
}
if (listen(sockfd, 5) < 0) {
+ logerror("Could not listen to %s: %s",
+ ip2str(ai->ai_family, ai->ai_addr, ai->ai_addrlen),
+ strerror(errno));
close(sockfd);
continue; /* not fatal */
}
@@ -835,16 +943,23 @@ static int setup_named_sock(char *listen_addr, int listen_port, struct socketlis
return 0;
if (set_reuse_addr(sockfd)) {
+ logerror("Could not set SO_REUSEADDR: %s", strerror(errno));
close(sockfd);
return 0;
}
if ( bind(sockfd, (struct sockaddr *)&sin, sizeof sin) < 0 ) {
+ logerror("Could not listen to %s: %s",
+ ip2str(AF_INET, (struct sockaddr *)&sin, sizeof(sin)),
+ strerror(errno));
close(sockfd);
return 0;
}
if (listen(sockfd, 5) < 0) {
+ logerror("Could not listen to %s: %s",
+ ip2str(AF_INET, (struct sockaddr *)&sin, sizeof(sin)),
+ strerror(errno));
close(sockfd);
return 0;
}
@@ -1040,6 +1155,8 @@ static int serve(struct string_list *listen_addr, int listen_port,
drop_privileges(cred);
+ loginfo("Ready to rumble");
+
return service_loop(&socklist);
}
@@ -1053,6 +1170,8 @@ int main(int argc, char **argv)
struct credentials *cred = NULL;
int i;
+ git_setup_gettext();
+
git_extract_argv0_path(argv[0]);
for (i = 1; i < argc; i++) {
@@ -1092,6 +1211,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;
@@ -1167,6 +1290,14 @@ int main(int argc, char **argv)
make_service_overridable(arg + 18, 0);
continue;
}
+ if (!prefixcmp(arg, "--informative-errors")) {
+ informative_errors = 1;
+ continue;
+ }
+ if (!prefixcmp(arg, "--no-informative-errors")) {
+ informative_errors = 0;
+ continue;
+ }
if (!strcmp(arg, "--")) {
ok_paths = &argv[i+1];
break;
@@ -1214,10 +1345,8 @@ int main(int argc, char **argv)
if (inetd_mode || serve_mode)
return execute();
- if (detach) {
+ if (detach)
daemonize();
- loginfo("Ready to rumble");
- }
else
sanitize_stdfds();