untracked cache: save to an index extension
[gitweb.git] / daemon.c
index e6b51ed9981f2e4084504a2a092a3f28fc7d4c8a..c3edd960ec5bf686b66fa55dcfea712e455fe772 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -61,6 +61,22 @@ static char *canon_hostname;
 static char *ip_address;
 static char *tcp_port;
 
+static int hostname_lookup_done;
+
+static void lookup_hostname(void);
+
+static const char *get_canon_hostname(void)
+{
+       lookup_hostname();
+       return canon_hostname;
+}
+
+static const char *get_ip_address(void)
+{
+       lookup_hostname();
+       return ip_address;
+}
+
 static void logreport(int priority, const char *err, va_list params)
 {
        if (log_syslog) {
@@ -106,6 +122,46 @@ static void NORETURN daemon_die(const char *err, va_list params)
        exit(1);
 }
 
+static void strbuf_addstr_or_null(struct strbuf *sb, const char *s)
+{
+       if (s)
+               strbuf_addstr(sb, s);
+}
+
+struct expand_path_context {
+       const char *directory;
+};
+
+static size_t expand_path(struct strbuf *sb, const char *placeholder, void *ctx)
+{
+       struct expand_path_context *context = ctx;
+
+       switch (placeholder[0]) {
+       case 'H':
+               strbuf_addstr_or_null(sb, hostname);
+               return 1;
+       case 'C':
+               if (placeholder[1] == 'H') {
+                       strbuf_addstr_or_null(sb, get_canon_hostname());
+                       return 2;
+               }
+               break;
+       case 'I':
+               if (placeholder[1] == 'P') {
+                       strbuf_addstr_or_null(sb, get_ip_address());
+                       return 2;
+               }
+               break;
+       case 'P':
+               strbuf_addstr_or_null(sb, tcp_port);
+               return 1;
+       case 'D':
+               strbuf_addstr(sb, context->directory);
+               return 1;
+       }
+       return 0;
+}
+
 static const char *path_ok(const char *directory)
 {
        static char rpath[PATH_MAX];
@@ -144,14 +200,10 @@ static const char *path_ok(const char *directory)
        }
        else if (interpolated_path && saw_extended_args) {
                struct strbuf expanded_path = STRBUF_INIT;
-               struct strbuf_expand_dict_entry dict[6];
-
-               dict[0].placeholder = "H"; dict[0].value = hostname;
-               dict[1].placeholder = "CH"; dict[1].value = canon_hostname;
-               dict[2].placeholder = "IP"; dict[2].value = ip_address;
-               dict[3].placeholder = "P"; dict[3].value = tcp_port;
-               dict[4].placeholder = "D"; dict[4].value = directory;
-               dict[5].placeholder = NULL; dict[5].value = NULL;
+               struct expand_path_context context;
+
+               context.directory = directory;
+
                if (*dir != '/') {
                        /* Allow only absolute */
                        logerror("'%s': Non-absolute path denied (interpolated-path active)", dir);
@@ -159,7 +211,7 @@ static const char *path_ok(const char *directory)
                }
 
                strbuf_expand(&expanded_path, interpolated_path,
-                               strbuf_expand_dict_cb, &dict);
+                             expand_path, &context);
                strlcpy(interp_path, expanded_path.buf, PATH_MAX);
                strbuf_release(&expanded_path);
                loginfo("Interpolated dir '%s'", interp_path);
@@ -230,23 +282,6 @@ struct daemon_service {
        int overridable;
 };
 
-static struct daemon_service *service_looking_at;
-static int service_enabled;
-
-static int git_daemon_config(const char *var, const char *value, void *cb)
-{
-       const char *service;
-
-       if (skip_prefix(var, "daemon.", &service) &&
-           !strcmp(service, 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 daemon_error(const char *dir, const char *msg)
 {
        if (!informative_errors)
@@ -259,7 +294,7 @@ static const char *access_hook;
 
 static int run_access_hook(struct daemon_service *service, const char *dir, const char *path)
 {
-       struct child_process child;
+       struct child_process child = CHILD_PROCESS_INIT;
        struct strbuf buf = STRBUF_INIT;
        const char *argv[8];
        const char **arg = argv;
@@ -271,13 +306,12 @@ static int run_access_hook(struct daemon_service *service, const char *dir, cons
        *arg++ = service->name;
        *arg++ = path;
        *arg++ = STRARG(hostname);
-       *arg++ = STRARG(canon_hostname);
-       *arg++ = STRARG(ip_address);
+       *arg++ = STRARG(get_canon_hostname());
+       *arg++ = STRARG(get_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;
@@ -324,6 +358,7 @@ static int run_service(const char *dir, struct daemon_service *service)
 {
        const char *path;
        int enabled = service->enabled;
+       struct strbuf var = STRBUF_INIT;
 
        loginfo("Request %s for '%s'", service->name, dir);
 
@@ -354,11 +389,9 @@ static int run_service(const char *dir, struct daemon_service *service)
        }
 
        if (service->overridable) {
-               service_looking_at = service;
-               service_enabled = -1;
-               git_config(git_daemon_config, NULL);
-               if (0 <= service_enabled)
-                       enabled = service_enabled;
+               strbuf_addf(&var, "daemon.%s", service->config_name);
+               git_config_get_bool(var.buf, &enabled);
+               strbuf_release(&var);
        }
        if (!enabled) {
                logerror("'%s': service not enabled for '%s'",
@@ -406,9 +439,8 @@ static void copy_to_log(int fd)
 
 static int run_service_command(const char **argv)
 {
-       struct child_process cld;
+       struct child_process cld = CHILD_PROCESS_INIT;
 
-       memset(&cld, 0, sizeof(cld));
        cld.argv = argv;
        cld.git_cmd = 1;
        cld.err = -1;
@@ -504,6 +536,45 @@ static void parse_host_and_port(char *hostport, char **host,
        }
 }
 
+/*
+ * Sanitize a string from the client so that it's OK to be inserted into a
+ * filesystem path. Specifically, we disallow slashes, runs of "..", and
+ * trailing and leading dots, which means that the client cannot escape
+ * our base path via ".." traversal.
+ */
+static void sanitize_client_strbuf(struct strbuf *out, const char *in)
+{
+       for (; *in; in++) {
+               if (*in == '/')
+                       continue;
+               if (*in == '.' && (!out->len || out->buf[out->len - 1] == '.'))
+                       continue;
+               strbuf_addch(out, *in);
+       }
+
+       while (out->len && out->buf[out->len - 1] == '.')
+               strbuf_setlen(out, out->len - 1);
+}
+
+static char *sanitize_client(const char *in)
+{
+       struct strbuf out = STRBUF_INIT;
+       sanitize_client_strbuf(&out, in);
+       return strbuf_detach(&out, NULL);
+}
+
+/*
+ * Like sanitize_client, but we also perform any canonicalization
+ * to make life easier on the admin.
+ */
+static char *canonicalize_client(const char *in)
+{
+       struct strbuf out = STRBUF_INIT;
+       sanitize_client_strbuf(&out, in);
+       strbuf_tolower(&out);
+       return strbuf_detach(&out, NULL);
+}
+
 /*
  * Read the host as supplied by the client connection.
  */
@@ -525,10 +596,11 @@ static void parse_host_arg(char *extra_args, int buflen)
                                parse_host_and_port(val, &host, &port);
                                if (port) {
                                        free(tcp_port);
-                                       tcp_port = xstrdup(port);
+                                       tcp_port = sanitize_client(port);
                                }
                                free(hostname);
-                               hostname = xstrdup_tolower(host);
+                               hostname = canonicalize_client(host);
+                               hostname_lookup_done = 0;
                        }
 
                        /* On to the next one */
@@ -537,11 +609,14 @@ static void parse_host_arg(char *extra_args, int buflen)
                if (extra_args < end && *extra_args)
                        die("Invalid request");
        }
+}
 
-       /*
-        * Locate canonical hostname and its IP address.
-        */
-       if (hostname) {
+/*
+ * Locate canonical hostname and its IP address.
+ */
+static void lookup_hostname(void)
+{
+       if (!hostname_lookup_done && hostname) {
 #ifndef NO_IPV6
                struct addrinfo hints;
                struct addrinfo *ai;
@@ -561,8 +636,9 @@ static void parse_host_arg(char *extra_args, int buflen)
                        ip_address = xstrdup(addrbuf);
 
                        free(canon_hostname);
-                       canon_hostname = xstrdup(ai->ai_canonname ?
-                                                ai->ai_canonname : ip_address);
+                       canon_hostname = ai->ai_canonname ?
+                               sanitize_client(ai->ai_canonname) :
+                               xstrdup(ip_address);
 
                        freeaddrinfo(ai);
                }
@@ -573,21 +649,23 @@ static void parse_host_arg(char *extra_args, int buflen)
                static char addrbuf[HOST_NAME_MAX + 1];
 
                hent = gethostbyname(hostname);
+               if (hent) {
+                       ap = hent->h_addr_list;
+                       memset(&sa, 0, sizeof sa);
+                       sa.sin_family = hent->h_addrtype;
+                       sa.sin_port = htons(0);
+                       memcpy(&sa.sin_addr, *ap, hent->h_length);
+
+                       inet_ntop(hent->h_addrtype, &sa.sin_addr,
+                                 addrbuf, sizeof(addrbuf));
 
-               ap = hent->h_addr_list;
-               memset(&sa, 0, sizeof sa);
-               sa.sin_family = hent->h_addrtype;
-               sa.sin_port = htons(0);
-               memcpy(&sa.sin_addr, *ap, hent->h_length);
-
-               inet_ntop(hent->h_addrtype, &sa.sin_addr,
-                         addrbuf, sizeof(addrbuf));
-
-               free(canon_hostname);
-               canon_hostname = xstrdup(hent->h_name);
-               free(ip_address);
-               ip_address = xstrdup(addrbuf);
+                       free(canon_hostname);
+                       canon_hostname = sanitize_client(hent->h_name);
+                       free(ip_address);
+                       ip_address = xstrdup(addrbuf);
+               }
 #endif
+               hostname_lookup_done = 1;
        }
 }
 
@@ -733,7 +811,7 @@ static void check_dead_children(void)
 static char **cld_argv;
 static void handle(int incoming, struct sockaddr *addr, socklen_t addrlen)
 {
-       struct child_process cld = { NULL };
+       struct child_process cld = CHILD_PROCESS_INIT;
        char addrbuf[300] = "REMOTE_ADDR=", portbuf[300];
        char *env[] = { addrbuf, portbuf, NULL };
 
@@ -834,7 +912,6 @@ static const char *ip2str(int family, struct sockaddr *sin, socklen_t len)
 static int setup_named_sock(char *listen_addr, int listen_port, struct socketlist *socklist)
 {
        int socknum = 0;
-       int maxfd = -1;
        char pbuf[NI_MAXSERV];
        struct addrinfo hints, *ai0, *ai;
        int gai;
@@ -902,9 +979,6 @@ static int setup_named_sock(char *listen_addr, int listen_port, struct socketlis
                ALLOC_GROW(socklist->list, socklist->nr + 1, socklist->alloc);
                socklist->list[socklist->nr++] = sockfd;
                socknum++;
-
-               if (maxfd < sockfd)
-                       maxfd = sockfd;
        }
 
        freeaddrinfo(ai0);
@@ -943,7 +1017,7 @@ static int setup_named_sock(char *listen_addr, int listen_port, struct socketlis
        }
 
        if ( bind(sockfd, (struct sockaddr *)&sin, sizeof sin) < 0 ) {
-               logerror("Could not listen to %s: %s",
+               logerror("Could not bind to %s: %s",
                         ip2str(AF_INET, (struct sockaddr *)&sin, sizeof(sin)),
                         strerror(errno));
                close(sockfd);