Added mark store/find to fast-import.
[gitweb.git] / daemon.c
index ac4c94bc709350471d66180d427a7e5df91c6235..012936f3bd76d9f9648ba9498b4126c7d6861e5c 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -9,24 +9,37 @@
 #include <syslog.h>
 #include "pkt-line.h"
 #include "cache.h"
+#include "exec_cmd.h"
 
 static int log_syslog;
 static int verbose;
+static int reuseaddr;
 
 static const char daemon_usage[] =
 "git-daemon [--verbose] [--syslog] [--inetd | --port=n] [--export-all]\n"
-"           [--timeout=n] [--init-timeout=n] [--strict-paths] [directory...]";
+"           [--timeout=n] [--init-timeout=n] [--strict-paths]\n"
+"           [--base-path=path] [--user-path | --user-path=path]\n"
+"           [--reuseaddr] [--detach] [--pid-file=file] [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;
+
+/* 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 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)
 {
@@ -82,9 +95,104 @@ 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;
+
+       /* 
+        * This resurrects the belts and suspenders paranoia check by HPA
+        * done in <435560F7.4080006@zytor.com> thread, now enter_repo()
+        * does not do getcwd() based path canonicalizations.
+        *
+        * sl becomes true immediately after seeing '/' and continues to
+        * be true as long as dots continue after that without intervening
+        * non-dot character.
+        */
+       if (!p || (*p != '/' && *p != '~'))
+               return -1;
+       sl = 1; ndot = 0;
+       p++;
+
+       while (1) {
+               char ch = *p++;
+               if (sl) {
+                       if (ch == '.')
+                               ndot++;
+                       else if (ch == '/') {
+                               if (ndot < 3)
+                                       /* reject //, /./ and /../ */
+                                       return -1;
+                               ndot = 0;
+                       }
+                       else if (ch == 0) {
+                               if (0 < ndot && ndot < 3)
+                                       /* reject /.$ and /..$ */
+                                       return -1;
+                               return 0;
+                       }
+                       else
+                               sl = ndot = 0;
+               }
+               else if (ch == 0)
+                       return 0;
+               else if (ch == '/') {
+                       sl = 1;
+                       ndot = 0;
+               }
+       }
+}
+
 static char *path_ok(char *dir)
 {
-       char *path = enter_repo(dir, strict_paths);
+       static char rpath[PATH_MAX];
+       char *path;
+
+       if (avoid_alias(dir)) {
+               logerror("'%s': aliased", dir);
+               return NULL;
+       }
+
+       if (*dir == '~') {
+               if (!user_path) {
+                       logerror("'%s': User-path not allowed", dir);
+                       return NULL;
+               }
+               if (*user_path) {
+                       /* Got either "~alice" or "~alice/foo";
+                        * rewrite them to "~alice/%s" or
+                        * "~alice/%s/foo".
+                        */
+                       int namlen, restlen = strlen(dir);
+                       char *slash = strchr(dir, '/');
+                       if (!slash)
+                               slash = dir + restlen;
+                       namlen = slash - dir;
+                       restlen -= namlen;
+                       loginfo("userpath <%s>, request <%s>, namlen %d, restlen %d, slash <%s>", user_path, dir, namlen, restlen, slash);
+                       snprintf(rpath, PATH_MAX, "%.*s/%s%.*s",
+                                namlen, dir, user_path, restlen, slash);
+                       dir = rpath;
+               }
+       }
+       else if (base_path) {
+               if (*dir != '/') {
+                       /* Allow only absolute */
+                       logerror("'%s': Non-absolute path denied (base-path active)", dir);
+                       return NULL;
+               }
+               else {
+                       snprintf(rpath, PATH_MAX, "%s%s", base_path, dir);
+                       dir = rpath;
+               }
+       }
+
+       path = enter_repo(dir, strict_paths);
 
        if (!path) {
                logerror("'%s': unable to chdir or not a git archive", dir);
@@ -92,25 +200,23 @@ static char *path_ok(char *dir)
        }
 
        if ( ok_paths && *ok_paths ) {
-               char **pp = NULL;
-               int dirlen = strlen(dir);
+               char **pp;
                int pathlen = strlen(path);
 
+               /* The validation is done on the paths after enter_repo
+                * appends optional {.git,.git/.git} and friends, but 
+                * it does not use getcwd().  So if your /pub is
+                * a symlink to /mnt/pub, you can whitelist /pub and
+                * do not have to say /mnt/pub.
+                * Do not say /pub/.
+                */
                for ( pp = ok_paths ; *pp ; pp++ ) {
                        int len = strlen(*pp);
-                       /* because of symlinks we must match both what the
-                        * user passed and the canonicalized path, otherwise
-                        * the user can send a string matching either a whitelist
-                        * entry or an actual directory exactly and still not
-                        * get through */
-                       if (len <= pathlen && !memcmp(*pp, path, len)) {
-                               if (path[len] == '\0' || (!strict_paths && path[len] == '/'))
-                                       return path;
-                       }
-                       if (len <= dirlen && !memcmp(*pp, dir, len)) {
-                               if (dir[len] == '\0' || (!strict_paths && dir[len] == '/'))
-                                       return path;
-                       }
+                       if (len <= pathlen &&
+                           !memcmp(*pp, path, len) &&
+                           (path[len] == '\0' ||
+                            (!strict_paths && path[len] == '/')))
+                               return path;
                }
        }
        else {
@@ -160,19 +266,47 @@ static int upload(char *dir)
        snprintf(timeout_buf, sizeof timeout_buf, "--timeout=%u", timeout);
 
        /* git-upload-pack only ever reads stuff, so this is safe */
-       execlp("git-upload-pack", "git-upload-pack", "--strict", timeout_buf, path, NULL);
+       execl_git_cmd("upload-pack", "--strict", timeout_buf, ".", NULL);
        return -1;
 }
 
-static int execute(void)
+static int execute(struct sockaddr *addr)
 {
        static char line[1000];
-       int len;
+       int pktlen, len;
+
+       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;
 
@@ -199,12 +333,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;
@@ -309,8 +443,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;
@@ -331,26 +463,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)
@@ -365,7 +478,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)
@@ -379,6 +492,16 @@ static void child_handler(int signo)
        }
 }
 
+static int set_reuse_addr(int sockfd)
+{
+       int on = 1;
+
+       if (!reuseaddr)
+               return 0;
+       return setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
+                         &on, sizeof(on));
+}
+
 #ifndef NO_IPV6
 
 static int socksetup(int port, int **socklist_p)
@@ -423,6 +546,11 @@ static int socksetup(int port, int **socklist_p)
                }
 #endif
 
+               if (set_reuse_addr(sockfd)) {
+                       close(sockfd);
+                       continue;
+               }
+
                if (bind(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
                        close(sockfd);
                        continue;       /* not fatal */
@@ -465,13 +593,24 @@ static int socksetup(int port, int **socklist_p)
        sin.sin_addr.s_addr = htonl(INADDR_ANY);
        sin.sin_port = htons(port);
 
+       if (set_reuse_addr(sockfd)) {
+               close(sockfd);
+               return 0;
+       }
+
        if ( bind(sockfd, (struct sockaddr *)&sin, sizeof sin) < 0 ) {
                close(sockfd);
                return 0;
        }
 
+       if (listen(sockfd, 5) < 0) {
+               close(sockfd);
+               return 0;
+       }
+
        *socklist_p = xmalloc(sizeof(int));
        **socklist_p = sockfd;
+       return 1;
 }
 
 #endif
@@ -523,6 +662,45 @@ static int service_loop(int socknum, int *socklist)
        }
 }
 
+/* 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)
 {
        int socknum, *socklist;
@@ -538,8 +716,15 @@ int main(int argc, char **argv)
 {
        int port = DEFAULT_GIT_PORT;
        int inetd_mode = 0;
+       const char *pid_file = NULL;
+       int detach = 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];
 
@@ -581,6 +766,31 @@ int main(int argc, char **argv)
                        strict_paths = 1;
                        continue;
                }
+               if (!strncmp(arg, "--base-path=", 12)) {
+                       base_path = arg+12;
+                       continue;
+               }
+               if (!strcmp(arg, "--reuseaddr")) {
+                       reuseaddr = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--user-path")) {
+                       user_path = "";
+                       continue;
+               }
+               if (!strncmp(arg, "--user-path=", 12)) {
+                       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 (!strcmp(arg, "--")) {
                        ok_paths = &argv[i+1];
                        break;
@@ -592,21 +802,34 @@ int main(int argc, char **argv)
                usage(daemon_usage);
        }
 
-       if (log_syslog)
+       if (log_syslog) {
                openlog("git-daemon", 0, LOG_DAEMON);
-
-       if (strict_paths && (!ok_paths || !*ok_paths)) {
-               if (!inetd_mode)
-                       die("git-daemon: option --strict-paths requires a whitelist");
-
-               logerror("option --strict-paths requires a whitelist");
-               exit (1);
+               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);
        }
 
+       if (detach)
+               daemonize();
+       else
+               sanitize_stdfds();
+
+       if (pid_file)
+               store_pid(pid_file);
+
        return serve(port);
 }