http-fetch: fix alternates handling.
[gitweb.git] / daemon.c
index 776749e3432fca916981d73d04a1fa19ed3d88b8..b14d8083bb14bd28a824c4417712842b1f70d829 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -7,6 +7,8 @@
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <syslog.h>
+#include <pwd.h>
+#include <grp.h>
 #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 for '%s'", dir);
+       loginfo("Request %s for '%s'", service->name, 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,20 +325,80 @@ static int upload(char *dir)
        return -1;
 }
 
-static int execute(void)
+static struct daemon_service daemon_service[] = {
+       { "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 len;
+       int pktlen, len, i;
+
+       if (addr) {
+               char addrbuf[256] = "";
+               int port = -1;
+
+               if (addr->sa_family == AF_INET) {
+                       struct sockaddr_in *sin_addr = (void *) addr;
+                       inet_ntop(addr->sa_family, &sin_addr->sin_addr, addrbuf, sizeof(addrbuf));
+                       port = sin_addr->sin_port;
+#ifndef NO_IPV6
+               } else if (addr && addr->sa_family == AF_INET6) {
+                       struct sockaddr_in6 *sin6_addr = (void *) addr;
+
+                       char *buf = addrbuf;
+                       *buf++ = '['; *buf = '\0'; /* stpcpy() is cool */
+                       inet_ntop(AF_INET6, &sin6_addr->sin6_addr, buf, sizeof(addrbuf) - 1);
+                       strcat(buf, "]");
+
+                       port = sin6_addr->sin6_port;
+#endif
+               }
+               loginfo("Connection from %s:%d", addrbuf, port);
+       }
 
        alarm(init_timeout ? init_timeout : timeout);
-       len = packet_read_line(0, line, sizeof(line));
+       pktlen = packet_read_line(0, line, sizeof(line));
        alarm(0);
 
+       len = strlen(line);
+       if (pktlen != len)
+               loginfo("Extended attributes (%d bytes) exist <%.*s>",
+                       (int) pktlen - len,
+                       (int) pktlen - len, line + len + 1);
        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;
@@ -299,12 +420,12 @@ static int execute(void)
 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;
@@ -409,8 +530,6 @@ static void check_max_connections(void)
 static void handle(int incoming, struct sockaddr *addr, int addrlen)
 {
        pid_t pid = fork();
-       char addrbuf[256] = "";
-       int port = -1;
 
        if (pid) {
                unsigned idx;
@@ -431,26 +550,7 @@ static void handle(int incoming, struct sockaddr *addr, int addrlen)
        dup2(incoming, 1);
        close(incoming);
 
-       if (addr->sa_family == AF_INET) {
-               struct sockaddr_in *sin_addr = (void *) addr;
-               inet_ntop(AF_INET, &sin_addr->sin_addr, addrbuf, sizeof(addrbuf));
-               port = sin_addr->sin_port;
-
-#ifndef NO_IPV6
-       } else if (addr->sa_family == AF_INET6) {
-               struct sockaddr_in6 *sin6_addr = (void *) addr;
-
-               char *buf = addrbuf;
-               *buf++ = '['; *buf = '\0'; /* stpcpy() is cool */
-               inet_ntop(AF_INET6, &sin6_addr->sin6_addr, buf, sizeof(addrbuf) - 1);
-               strcat(buf, "]");
-
-               port = sin6_addr->sin6_port;
-#endif
-       }
-       loginfo("Connection from %s:%d", addrbuf, port);
-
-       exit(execute());
+       exit(execute(addr));
 }
 
 static void child_handler(int signo)
@@ -465,7 +565,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)
@@ -513,7 +613,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)
@@ -547,11 +646,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)
@@ -649,7 +744,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;
 
@@ -657,6 +791,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);
 }
 
@@ -664,8 +803,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];
 
@@ -723,6 +872,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;
@@ -734,21 +916,56 @@ 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 (group_name && !user_name)
+               die("--group supplied without --user");
 
-       if (strict_paths && (!ok_paths || !*ok_paths)) {
-               if (!inetd_mode)
-                       die("git-daemon: option --strict-paths requires a whitelist");
+       if (user_name) {
+               pass = getpwnam(user_name);
+               if (!pass)
+                       die("user not found - %s", user_name);
 
-               logerror("option --strict-paths requires a whitelist");
-               exit (1);
+               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) {
-               fclose(stderr); //FIXME: workaround
-               return execute();
+               struct sockaddr_storage ss;
+               struct sockaddr *peer = (struct sockaddr *)&ss;
+               socklen_t slen = sizeof(ss);
+
+               freopen("/dev/null", "w", stderr);
+
+               if (getpeername(0, peer, &slen))
+                       peer = NULL;
+
+               return execute(peer);
        }
 
-       return serve(port);
+       if (detach)
+               daemonize();
+       else
+               sanitize_stdfds();
+
+       if (pid_file)
+               store_pid(pid_file);
+
+       return serve(port, pass, gid);
 }