X-Git-Url: http://git.ipfire.org/?p=thirdparty%2Fgit.git;a=blobdiff_plain;f=daemon.c;h=a2954a0451316197621aed152c194d398aaee624;hp=bdfe80d2e4f4fc13ad45bd35aeeeb3ec71422a4f;hb=39345a216ff37bda9fb7cec85f6de44069f5205d;hpb=e94528a0e99ea54520b8aff016275a7622c55a47 diff --git a/daemon.c b/daemon.c index bdfe80d2e4..a2954a0451 100644 --- a/daemon.c +++ b/daemon.c @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include "pkt-line.h" #include "cache.h" #include "exec_cmd.h" @@ -19,27 +21,29 @@ static const char daemon_usage[] = "git-daemon [--verbose] [--syslog] [--inetd | --port=n] [--export-all]\n" " [--timeout=n] [--init-timeout=n] [--strict-paths]\n" " [--base-path=path] [--user-path | --user-path=path]\n" -" [--reuseaddr] [directory...]"; +" [--reuseaddr] [--detach] [--pid-file=file]\n" +" [--[enable|disable|allow-override|forbid-override]=service]\n" +" [--user=user [[--group=group]] [directory...]"; /* List of acceptable pathname prefixes */ -static char **ok_paths = NULL; -static int strict_paths = 0; +static char **ok_paths; +static int strict_paths; /* If this is set, git-daemon-export-ok is not required */ -static int export_all_trees = 0; +static int export_all_trees; /* Take all paths relative to this one if non-NULL */ -static char *base_path = NULL; +static char *base_path; /* If defined, ~user notation is allowed and the string is inserted * after ~user/. E.g. a request to git://host/~alice/frotz would * go to /home/alice/pub_git/frotz with --user-path=pub_git. */ -static char *user_path = NULL; +static const char *user_path; /* Timeout, and initial timeout */ -static unsigned int timeout = 0; -static unsigned int init_timeout = 0; +static unsigned int timeout; +static unsigned int init_timeout; static void logreport(int priority, const char *err, va_list params) { @@ -95,6 +99,12 @@ static void loginfo(const char *err, ...) va_end(params); } +static void NORETURN daemon_die(const char *err, va_list params) +{ + logreport(LOG_ERR, err, params); + exit(1); +} + static int avoid_alias(char *p) { int sl, ndot; @@ -223,13 +233,42 @@ static char *path_ok(char *dir) return NULL; /* Fallthrough. Deny by default */ } -static int upload(char *dir) +typedef int (*daemon_service_fn)(void); +struct daemon_service { + const char *name; + const char *config_name; + daemon_service_fn fn; + int enabled; + int overridable; +}; + +static struct daemon_service *service_looking_at; +static int service_enabled; + +static int git_daemon_config(const char *var, const char *value) +{ + if (!strncmp(var, "daemon.", 7) && + !strcmp(var + 7, service_looking_at->config_name)) { + service_enabled = git_config_bool(var, value); + return 0; + } + + /* we are not interested in parsing any other configuration here */ + return 0; +} + +static int run_service(char *dir, struct daemon_service *service) { - /* Timeout as string */ - char timeout_buf[64]; const char *path; + int enabled = service->enabled; + + loginfo("Request %s for '%s'", service->name, dir); - loginfo("Request for '%s'", dir); + if (!enabled && !service->overridable) { + logerror("'%s': service not enabled.", service->name); + errno = EACCES; + return -1; + } if (!(path = path_ok(dir))) return -1; @@ -251,12 +290,34 @@ static int upload(char *dir) return -1; } + if (service->overridable) { + service_looking_at = service; + service_enabled = -1; + git_config(git_daemon_config); + if (0 <= service_enabled) + enabled = service_enabled; + } + if (!enabled) { + logerror("'%s': service not enabled for '%s'", + service->name, path); + errno = EACCES; + return -1; + } + /* * We'll ignore SIGTERM from now on, we have a * good client. */ signal(SIGTERM, SIG_IGN); + return service->fn(); +} + +static int upload_pack(void) +{ + /* Timeout as string */ + char timeout_buf[64]; + snprintf(timeout_buf, sizeof timeout_buf, "--timeout=%u", timeout); /* git-upload-pack only ever reads stuff, so this is safe */ @@ -264,10 +325,43 @@ static int upload(char *dir) return -1; } +static int upload_archive(void) +{ + execl_git_cmd("upload-archive", ".", NULL); + return -1; +} + +static struct daemon_service daemon_service[] = { + { "upload-archive", "uploadarch", upload_archive, 0, 1 }, + { "upload-pack", "uploadpack", upload_pack, 1, 1 }, +}; + +static void enable_service(const char *name, int ena) { + int i; + for (i = 0; i < ARRAY_SIZE(daemon_service); i++) { + if (!strcmp(daemon_service[i].name, name)) { + daemon_service[i].enabled = ena; + return; + } + } + die("No such service %s", name); +} + +static void make_service_overridable(const char *name, int ena) { + int i; + for (i = 0; i < ARRAY_SIZE(daemon_service); i++) { + if (!strcmp(daemon_service[i].name, name)) { + daemon_service[i].overridable = ena; + return; + } + } + die("No such service %s", name); +} + static int execute(struct sockaddr *addr) { static char line[1000]; - int pktlen, len; + int pktlen, len, i; if (addr) { char addrbuf[256] = ""; @@ -304,8 +398,14 @@ static int execute(struct sockaddr *addr) if (len && line[len-1] == '\n') line[--len] = 0; - if (!strncmp("git-upload-pack ", line, 16)) - return upload(line+16); + for (i = 0; i < ARRAY_SIZE(daemon_service); i++) { + struct daemon_service *s = &(daemon_service[i]); + int namelen = strlen(s->name); + if (!strncmp("git-", line, 4) && + !strncmp(s->name, line + 4, namelen) && + line[namelen + 4] == ' ') + return run_service(line + namelen + 5, s); + } logerror("Protocol error: '%s'", line); return -1; @@ -327,12 +427,12 @@ static int execute(struct sockaddr *addr) static int max_connections = 25; /* These are updated by the signal handler */ -static volatile unsigned int children_reaped = 0; +static volatile unsigned int children_reaped; static pid_t dead_child[MAX_CHILDREN]; /* These are updated by the main loop */ -static unsigned int children_spawned = 0; -static unsigned int children_deleted = 0; +static unsigned int children_spawned; +static unsigned int children_deleted; static struct child { pid_t pid; @@ -472,7 +572,7 @@ static void child_handler(int signo) children_reaped = reaped + 1; /* XXX: Custom logging, since we don't wanna getpid() */ if (verbose) { - char *dead = ""; + const char *dead = ""; if (!WIFEXITED(status) || WEXITSTATUS(status) > 0) dead = " (with error)"; if (log_syslog) @@ -520,7 +620,6 @@ static int socksetup(int port, int **socklist_p) for (ai = ai0; ai; ai = ai->ai_next) { int sockfd; - int *newlist; sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sockfd < 0) @@ -554,11 +653,7 @@ static int socksetup(int port, int **socklist_p) continue; /* not fatal */ } - newlist = realloc(socklist, sizeof(int) * (socknum + 1)); - if (!newlist) - die("memory allocation failed: %s", strerror(errno)); - - socklist = newlist; + socklist = xrealloc(socklist, sizeof(int) * (socknum + 1)); socklist[socknum++] = sockfd; if (maxfd < sockfd) @@ -656,7 +751,46 @@ static int service_loop(int socknum, int *socklist) } } -static int serve(int port) +/* if any standard file descriptor is missing open it to /dev/null */ +static void sanitize_stdfds(void) +{ + int fd = open("/dev/null", O_RDWR, 0); + while (fd != -1 && fd < 2) + fd = dup(fd); + if (fd == -1) + die("open /dev/null or dup failed: %s", strerror(errno)); + if (fd > 2) + close(fd); +} + +static void daemonize(void) +{ + switch (fork()) { + case 0: + break; + case -1: + die("fork failed: %s", strerror(errno)); + default: + exit(0); + } + if (setsid() == -1) + die("setsid failed: %s", strerror(errno)); + close(0); + close(1); + close(2); + sanitize_stdfds(); +} + +static void store_pid(const char *path) +{ + FILE *f = fopen(path, "w"); + if (!f) + die("cannot open pid file %s: %s", path, strerror(errno)); + fprintf(f, "%d\n", getpid()); + fclose(f); +} + +static int serve(int port, struct passwd *pass, gid_t gid) { int socknum, *socklist; @@ -664,6 +798,11 @@ static int serve(int port) if (socknum == 0) die("unable to allocate any listen sockets on port %u", port); + if (pass && gid && + (initgroups(pass->pw_name, gid) || setgid (gid) || + setuid(pass->pw_uid))) + die("cannot drop privileges"); + return service_loop(socknum, socklist); } @@ -671,8 +810,18 @@ int main(int argc, char **argv) { int port = DEFAULT_GIT_PORT; int inetd_mode = 0; + const char *pid_file = NULL, *user_name = NULL, *group_name = NULL; + int detach = 0; + struct passwd *pass = NULL; + struct group *group; + gid_t gid = 0; int i; + /* Without this we cannot rely on waitpid() to tell + * what happened to our children. + */ + signal(SIGCHLD, SIG_DFL); + for (i = 1; i < argc; i++) { char *arg = argv[i]; @@ -730,6 +879,39 @@ int main(int argc, char **argv) user_path = arg + 12; continue; } + if (!strncmp(arg, "--pid-file=", 11)) { + pid_file = arg + 11; + continue; + } + if (!strcmp(arg, "--detach")) { + detach = 1; + log_syslog = 1; + continue; + } + if (!strncmp(arg, "--user=", 7)) { + user_name = arg + 7; + continue; + } + if (!strncmp(arg, "--group=", 8)) { + group_name = arg + 8; + continue; + } + if (!strncmp(arg, "--enable=", 9)) { + enable_service(arg + 9, 1); + continue; + } + if (!strncmp(arg, "--disable=", 10)) { + enable_service(arg + 10, 0); + continue; + } + if (!strncmp(arg, "--allow-override=", 17)) { + make_service_overridable(arg + 17, 1); + continue; + } + if (!strncmp(arg, "--forbid-override=", 18)) { + make_service_overridable(arg + 18, 0); + continue; + } if (!strcmp(arg, "--")) { ok_paths = &argv[i+1]; break; @@ -741,23 +923,42 @@ int main(int argc, char **argv) usage(daemon_usage); } - if (log_syslog) - openlog("git-daemon", 0, LOG_DAEMON); + if (inetd_mode && (group_name || user_name)) + die("--user and --group are incompatible with --inetd"); - if (strict_paths && (!ok_paths || !*ok_paths)) { - if (!inetd_mode) - die("git-daemon: option --strict-paths requires a whitelist"); + if (group_name && !user_name) + die("--group supplied without --user"); - logerror("option --strict-paths requires a whitelist"); - exit (1); + if (user_name) { + pass = getpwnam(user_name); + if (!pass) + die("user not found - %s", user_name); + + if (!group_name) + gid = pass->pw_gid; + else { + group = getgrnam(group_name); + if (!group) + die("group not found - %s", group_name); + + gid = group->gr_gid; + } } + if (log_syslog) { + openlog("git-daemon", 0, LOG_DAEMON); + set_die_routine(daemon_die); + } + + if (strict_paths && (!ok_paths || !*ok_paths)) + die("option --strict-paths requires a whitelist"); + if (inetd_mode) { struct sockaddr_storage ss; struct sockaddr *peer = (struct sockaddr *)&ss; socklen_t slen = sizeof(ss); - fclose(stderr); //FIXME: workaround + freopen("/dev/null", "w", stderr); if (getpeername(0, peer, &slen)) peer = NULL; @@ -765,5 +966,13 @@ int main(int argc, char **argv) return execute(peer); } - return serve(port); + if (detach) + daemonize(); + else + sanitize_stdfds(); + + if (pid_file) + store_pid(pid_file); + + return serve(port, pass, gid); }