push: require force for refs under refs/tags/
[gitweb.git] / daemon.c
index 17028b6de8c9285c7aa2c2865b84153519633e4a..4602b46a5c39e1d501143ab4e95b55aff5c8f23b 100644 (file)
--- a/daemon.c
+++ b/daemon.c
 #define NI_MAXSERV 32
 #endif
 
+#ifdef NO_INITGROUPS
+#define initgroups(x, y) (0) /* nothing */
+#endif
+
 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"
@@ -25,6 +30,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>...]";
@@ -104,11 +110,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;
@@ -243,6 +249,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;
@@ -253,11 +332,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.
@@ -273,7 +352,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) {
@@ -287,9 +366,16 @@ 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.
@@ -656,7 +742,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 = { 0 };
+       struct child_process cld = { NULL };
        char addrbuf[300] = "REMOTE_ADDR=", portbuf[300];
        char *env[] = { addrbuf, portbuf, NULL };
 
@@ -730,6 +816,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)
@@ -776,15 +885,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 */
                }
@@ -831,16 +947,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;
        }
@@ -940,6 +1063,62 @@ static void sanitize_stdfds(void)
                close(fd);
 }
 
+#ifdef NO_POSIX_GOODIES
+
+struct credentials;
+
+static void drop_privileges(struct credentials *cred)
+{
+       /* nothing */
+}
+
+static void daemonize(void)
+{
+       die("--detach not supported on this platform");
+}
+
+static struct credentials *prepare_credentials(const char *user_name,
+    const char *group_name)
+{
+       die("--user not supported on this platform");
+}
+
+#else
+
+struct credentials {
+       struct passwd *pass;
+       gid_t gid;
+};
+
+static void drop_privileges(struct credentials *cred)
+{
+       if (cred && (initgroups(cred->pass->pw_name, cred->gid) ||
+           setgid (cred->gid) || setuid(cred->pass->pw_uid)))
+               die("cannot drop privileges");
+}
+
+static struct credentials *prepare_credentials(const char *user_name,
+    const char *group_name)
+{
+       static struct credentials c;
+
+       c.pass = getpwnam(user_name);
+       if (!c.pass)
+               die("user not found - %s", user_name);
+
+       if (!group_name)
+               c.gid = c.pass->pw_gid;
+       else {
+               struct group *group = getgrnam(group_name);
+               if (!group)
+                       die("group not found - %s", group_name);
+
+               c.gid = group->gr_gid;
+       }
+
+       return &c;
+}
+
 static void daemonize(void)
 {
        switch (fork()) {
@@ -957,6 +1136,7 @@ static void daemonize(void)
        close(2);
        sanitize_stdfds();
 }
+#endif
 
 static void store_pid(const char *path)
 {
@@ -967,7 +1147,8 @@ static void store_pid(const char *path)
                die_errno("failed to write pid file '%s'", path);
 }
 
-static int serve(struct string_list *listen_addr, int listen_port, struct passwd *pass, gid_t gid)
+static int serve(struct string_list *listen_addr, int listen_port,
+    struct credentials *cred)
 {
        struct socketlist socklist = { NULL, 0, 0 };
 
@@ -976,10 +1157,9 @@ static int serve(struct string_list *listen_addr, int listen_port, struct passwd
                die("unable to allocate any listen sockets on port %u",
                    listen_port);
 
-       if (pass && gid &&
-           (initgroups(pass->pw_name, gid) || setgid (gid) ||
-            setuid(pass->pw_uid)))
-               die("cannot drop privileges");
+       drop_privileges(cred);
+
+       loginfo("Ready to rumble");
 
        return service_loop(&socklist);
 }
@@ -991,11 +1171,11 @@ int main(int argc, char **argv)
        int serve_mode = 0, 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;
+       struct credentials *cred = NULL;
        int i;
 
+       git_setup_gettext();
+
        git_extract_argv0_path(argv[0]);
 
        for (i = 1; i < argc; i++) {
@@ -1035,6 +1215,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;
@@ -1110,6 +1294,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;
@@ -1139,21 +1331,8 @@ int main(int argc, char **argv)
        if (group_name && !user_name)
                die("--group supplied without --user");
 
-       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 (user_name)
+               cred = prepare_credentials(user_name, group_name);
 
        if (strict_paths && (!ok_paths || !*ok_paths))
                die("option --strict-paths requires a whitelist");
@@ -1170,10 +1349,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();
 
@@ -1182,10 +1359,11 @@ int main(int argc, char **argv)
 
        /* prepare argv for serving-processes */
        cld_argv = xmalloc(sizeof (char *) * (argc + 2));
-       for (i = 0; i < argc; ++i)
-               cld_argv[i] = argv[i];
-       cld_argv[argc] = "--serve";
+       cld_argv[0] = argv[0];  /* git-daemon */
+       cld_argv[1] = "--serve";
+       for (i = 1; i < argc; ++i)
+               cld_argv[i+1] = argv[i];
        cld_argv[argc+1] = NULL;
 
-       return serve(&listen_addr, listen_port, pass, gid);
+       return serve(&listen_addr, listen_port, cred);
 }