diff options
Diffstat (limited to 'daemon.c')
-rw-r--r-- | daemon.c | 155 |
1 files changed, 142 insertions, 13 deletions
@@ -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(); |