#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"
"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)
{
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;
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;
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 */
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 pktlen, 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);
pktlen = packet_read_line(0, line, sizeof(line));
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;
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;
static void handle(int incoming, struct sockaddr *addr, int addrlen)
{
pid_t pid = fork();
- char addrbuf[256] = "";
- int port = -1;
if (pid) {
unsigned idx;
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)
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)
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)
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)
}
}
-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;
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);
}
{
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];
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;
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);
}