#include "cache.h"
#include "pkt-line.h"
#include "exec_cmd.h"
-
-#include <syslog.h>
+#include "run-command.h"
+#include "strbuf.h"
+#include "string-list.h"
#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 256
#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"
-" [--timeout=n] [--init-timeout=n] [--max-connections=n]\n"
-" [--strict-paths] [--base-path=path] [--base-path-relaxed]\n"
-" [--user-path | --user-path=path]\n"
-" [--interpolated-path=path]\n"
-" [--reuseaddr] [--detach] [--pid-file=file]\n"
-" [--[enable|disable|allow-override|forbid-override]=service]\n"
-" [--inetd | [--listen=host_or_ipaddr] [--port=n]\n"
-" [--user=user [--group=group]]\n"
-" [directory...]";
+" [--timeout=<n>] [--init-timeout=<n>] [--max-connections=<n>]\n"
+" [--strict-paths] [--base-path=<path>] [--base-path-relaxed]\n"
+" [--user-path | --user-path=<path>]\n"
+" [--interpolated-path=<path>]\n"
+" [--reuseaddr] [--pid-file=<file>]\n"
+" [--(enable|disable|allow-override|forbid-override)=<service>]\n"
+" [--inetd | [--listen=<host_or_ipaddr>] [--port=<n>]\n"
+" [--detach] [--user=<user> [--group=<group>]]\n"
+" [<directory>...]";
/* List of acceptable pathname prefixes */
static char **ok_paths;
syslog(priority, "%s", buf);
} else {
/*
- * Since stderr is set to linebuffered mode, the
+ * Since stderr is set to buffered mode, the
* logging of different processes will not overlap
+ * unless they overflow the (rather big) buffers.
*/
fprintf(stderr, "[%"PRIuMAX"] ", (uintmax_t)getpid());
vfprintf(stderr, err, params);
fputc('\n', stderr);
+ fflush(stderr);
}
}
+__attribute__((format (printf, 1, 2)))
static void logerror(const char *err, ...)
{
va_list params;
va_end(params);
}
+__attribute__((format (printf, 1, 2)))
static void loginfo(const char *err, ...)
{
va_list 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 *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;
- if (avoid_alias(dir)) {
+ if (daemon_avoid_alias(dir)) {
logerror("'%s': aliased", dir);
return NULL;
}
}
else if (interpolated_path && saw_extended_args) {
struct strbuf expanded_path = STRBUF_INIT;
- struct strbuf_expand_dict_entry dict[] = {
- { "H", hostname },
- { "CH", canon_hostname },
- { "IP", ip_address },
- { "P", tcp_port },
- { "D", directory },
- { "%", "%" },
- { NULL }
- };
-
+ 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;
if (*dir != '/') {
/* Allow only absolute */
logerror("'%s': Non-absolute path denied (interpolated-path active)", dir);
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 int run_service(char *dir, struct daemon_service *service)
{
const char *path;
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.
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) {
logerror("'%s': service not enabled for '%s'",
service->name, path);
errno = EACCES;
- return -1;
+ return daemon_error(dir, "service not enabled");
}
/*
return service->fn();
}
+static void copy_to_log(int fd)
+{
+ struct strbuf line = STRBUF_INIT;
+ FILE *fp;
+
+ fp = fdopen(fd, "r");
+ if (fp == NULL) {
+ logerror("fdopen of error channel failed");
+ close(fd);
+ return;
+ }
+
+ while (strbuf_getline(&line, fp, '\n') != EOF) {
+ logerror("%s", line.buf);
+ strbuf_setlen(&line, 0);
+ }
+
+ strbuf_release(&line);
+ fclose(fp);
+}
+
+static int run_service_command(const char **argv)
+{
+ struct child_process cld;
+
+ memset(&cld, 0, sizeof(cld));
+ cld.argv = argv;
+ cld.git_cmd = 1;
+ cld.err = -1;
+ if (start_command(&cld))
+ return -1;
+
+ close(0);
+ close(1);
+
+ copy_to_log(cld.err);
+
+ return finish_command(&cld);
+}
+
static int upload_pack(void)
{
/* Timeout as string */
char timeout_buf[64];
+ const char *argv[] = { "upload-pack", "--strict", NULL, ".", NULL };
- snprintf(timeout_buf, sizeof timeout_buf, "--timeout=%u", timeout);
+ argv[2] = timeout_buf;
- /* git-upload-pack only ever reads stuff, so this is safe */
- execl_git_cmd("upload-pack", "--strict", timeout_buf, ".", NULL);
- return -1;
+ snprintf(timeout_buf, sizeof timeout_buf, "--timeout=%u", timeout);
+ return run_service_command(argv);
}
static int upload_archive(void)
{
- execl_git_cmd("upload-archive", ".", NULL);
- return -1;
+ static const char *argv[] = { "upload-archive", ".", NULL };
+ return run_service_command(argv);
}
static int receive_pack(void)
{
- execl_git_cmd("receive-pack", ".", NULL);
- return -1;
+ static const char *argv[] = { "receive-pack", ".", NULL };
+ return run_service_command(argv);
}
static struct daemon_service daemon_service[] = {
return dup;
}
+static void parse_host_and_port(char *hostport, char **host,
+ char **port)
+{
+ if (*hostport == '[') {
+ char *end;
+
+ end = strchr(hostport, ']');
+ if (!end)
+ die("Invalid request ('[' without ']')");
+ *end = '\0';
+ *host = hostport + 1;
+ if (!end[1])
+ *port = NULL;
+ else if (end[1] == ':')
+ *port = end + 2;
+ else
+ die("Garbage after end of host part");
+ } else {
+ *host = hostport;
+ *port = strrchr(hostport, ':');
+ if (*port) {
+ **port = '\0';
+ ++*port;
+ }
+ }
+}
+
/*
- * Separate the "extra args" information as supplied by the client connection.
+ * Read the host as supplied by the client connection.
*/
-static void parse_extra_args(char *extra_args, int buflen)
+static void parse_host_arg(char *extra_args, int buflen)
{
char *val;
int vallen;
char *end = extra_args + buflen;
- while (extra_args < end && *extra_args) {
+ if (extra_args < end && *extra_args) {
saw_extended_args = 1;
if (strncasecmp("host=", extra_args, 5) == 0) {
val = extra_args + 5;
vallen = strlen(val) + 1;
if (*val) {
/* Split <host>:<port> at colon. */
- char *host = val;
- char *port = strrchr(host, ':');
+ char *host;
+ char *port;
+ parse_host_and_port(val, &host, &port);
if (port) {
- *port = 0;
- port++;
free(tcp_port);
tcp_port = xstrdup(port);
}
/* On to the next one */
extra_args = val + vallen;
}
+ if (extra_args < end && *extra_args)
+ die("Invalid request");
}
/*
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_CANONNAME;
- gai = getaddrinfo(hostname, 0, &hints, &ai);
+ gai = getaddrinfo(hostname, NULL, &hints, &ai);
if (!gai) {
struct sockaddr_in *sin_addr = (void *)ai->ai_addr;
}
-static int execute(struct sockaddr *addr)
+static int execute(void)
{
static char line[1000];
int pktlen, len, i;
+ char *addr = getenv("REMOTE_ADDR"), *port = getenv("REMOTE_PORT");
- 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 = ntohs(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 = ntohs(sin6_addr->sin6_port);
-#endif
- }
- loginfo("Connection from %s:%d", addrbuf, port);
- setenv("REMOTE_ADDR", addrbuf, 1);
- }
- else {
- unsetenv("REMOTE_ADDR");
- }
+ if (addr)
+ loginfo("Connection from %s:%s", addr, port);
alarm(init_timeout ? init_timeout : timeout);
pktlen = packet_read_line(0, line, sizeof(line));
hostname = canon_hostname = ip_address = tcp_port = NULL;
if (len != pktlen)
- parse_extra_args(line + len + 1, pktlen - len - 1);
+ parse_host_arg(line + len + 1, pktlen - len - 1);
for (i = 0; i < ARRAY_SIZE(daemon_service); i++) {
struct daemon_service *s = &(daemon_service[i]);
return -1;
}
+static int addrcmp(const struct sockaddr_storage *s1,
+ const struct sockaddr_storage *s2)
+{
+ const struct sockaddr *sa1 = (const struct sockaddr*) s1;
+ const struct sockaddr *sa2 = (const struct sockaddr*) s2;
+
+ if (sa1->sa_family != sa2->sa_family)
+ return sa1->sa_family - sa2->sa_family;
+ if (sa1->sa_family == AF_INET)
+ return memcmp(&((struct sockaddr_in *)s1)->sin_addr,
+ &((struct sockaddr_in *)s2)->sin_addr,
+ sizeof(struct in_addr));
+#ifndef NO_IPV6
+ if (sa1->sa_family == AF_INET6)
+ return memcmp(&((struct sockaddr_in6 *)s1)->sin6_addr,
+ &((struct sockaddr_in6 *)s2)->sin6_addr,
+ sizeof(struct in6_addr));
+#endif
+ return 0;
+}
+
static int max_connections = 32;
static unsigned int live_children;
static struct child {
struct child *next;
- pid_t pid;
+ struct child_process cld;
struct sockaddr_storage address;
} *firstborn;
-static void add_child(pid_t pid, struct sockaddr *addr, int addrlen)
+static void add_child(struct child_process *cld, struct sockaddr *addr, socklen_t addrlen)
{
struct child *newborn, **cradle;
- /*
- * This must be xcalloc() -- we'll compare the whole sockaddr_storage
- * but individual address may be shorter.
- */
newborn = xcalloc(1, sizeof(*newborn));
live_children++;
- newborn->pid = pid;
+ memcpy(&newborn->cld, cld, sizeof(*cld));
memcpy(&newborn->address, addr, addrlen);
for (cradle = &firstborn; *cradle; cradle = &(*cradle)->next)
- if (!memcmp(&(*cradle)->address, &newborn->address,
- sizeof(newborn->address)))
+ if (!addrcmp(&(*cradle)->address, &newborn->address))
break;
newborn->next = *cradle;
*cradle = newborn;
}
-static void remove_child(pid_t pid)
-{
- struct child **cradle, *blanket;
-
- for (cradle = &firstborn; (blanket = *cradle); cradle = &blanket->next)
- if (blanket->pid == pid) {
- *cradle = blanket->next;
- live_children--;
- free(blanket);
- break;
- }
-}
-
/*
* This gets called if the number of connections grows
* past "max_connections".
return;
for (; (next = blanket->next); blanket = next)
- if (!memcmp(&blanket->address, &next->address,
- sizeof(next->address))) {
- kill(blanket->pid, SIGTERM);
+ if (!addrcmp(&blanket->address, &next->address)) {
+ kill(blanket->cld.pid, SIGTERM);
break;
}
}
int status;
pid_t pid;
- while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
- const char *dead = "";
- remove_child(pid);
- if (!WIFEXITED(status) || (WEXITSTATUS(status) > 0))
- dead = " (with error)";
- loginfo("[%"PRIuMAX"] Disconnected%s", (uintmax_t)pid, dead);
- }
+ struct child **cradle, *blanket;
+ for (cradle = &firstborn; (blanket = *cradle);)
+ if ((pid = waitpid(blanket->cld.pid, &status, WNOHANG)) > 1) {
+ const char *dead = "";
+ if (status)
+ dead = " (with error)";
+ loginfo("[%"PRIuMAX"] Disconnected%s", (uintmax_t)pid, dead);
+
+ /* remove the child */
+ *cradle = blanket->next;
+ live_children--;
+ free(blanket);
+ } else
+ cradle = &blanket->next;
}
-static void handle(int incoming, struct sockaddr *addr, int addrlen)
+static char **cld_argv;
+static void handle(int incoming, struct sockaddr *addr, socklen_t addrlen)
{
- pid_t pid;
+ struct child_process cld = { NULL };
+ char addrbuf[300] = "REMOTE_ADDR=", portbuf[300];
+ char *env[] = { addrbuf, portbuf, NULL };
if (max_connections && live_children >= max_connections) {
kill_some_child();
}
}
- if ((pid = fork())) {
- close(incoming);
- if (pid < 0) {
- logerror("Couldn't fork %s", strerror(errno));
- return;
- }
+ if (addr->sa_family == AF_INET) {
+ struct sockaddr_in *sin_addr = (void *) addr;
+ inet_ntop(addr->sa_family, &sin_addr->sin_addr, addrbuf + 12,
+ sizeof(addrbuf) - 12);
+ snprintf(portbuf, sizeof(portbuf), "REMOTE_PORT=%d",
+ ntohs(sin_addr->sin_port));
+#ifndef NO_IPV6
+ } else if (addr && addr->sa_family == AF_INET6) {
+ struct sockaddr_in6 *sin6_addr = (void *) addr;
- add_child(pid, addr, addrlen);
- return;
+ char *buf = addrbuf + 12;
+ *buf++ = '['; *buf = '\0'; /* stpcpy() is cool */
+ inet_ntop(AF_INET6, &sin6_addr->sin6_addr, buf,
+ sizeof(addrbuf) - 13);
+ strcat(buf, "]");
+
+ snprintf(portbuf, sizeof(portbuf), "REMOTE_PORT=%d",
+ ntohs(sin6_addr->sin6_port));
+#endif
}
- dup2(incoming, 0);
- dup2(incoming, 1);
- close(incoming);
+ cld.env = (const char **)env;
+ cld.argv = (const char **)cld_argv;
+ cld.in = incoming;
+ cld.out = dup(incoming);
- exit(execute(addr));
+ if (start_command(&cld))
+ logerror("unable to fork");
+ else
+ add_child(&cld, addr, addrlen);
+ close(incoming);
}
static void child_handler(int signo)
&on, sizeof(on));
}
+struct socketlist {
+ int *list;
+ size_t nr;
+ 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 socksetup(char *listen_addr, int listen_port, int **socklist_p)
+static int setup_named_sock(char *listen_addr, int listen_port, struct socketlist *socklist)
{
- int socknum = 0, *socklist = NULL;
+ int socknum = 0;
int maxfd = -1;
char pbuf[NI_MAXSERV];
struct addrinfo hints, *ai0, *ai;
hints.ai_flags = AI_PASSIVE;
gai = getaddrinfo(listen_addr, pbuf, &hints, &ai0);
- if (gai)
- die("getaddrinfo() failed: %s", gai_strerror(gai));
+ if (gai) {
+ logerror("getaddrinfo() for %s failed: %s", listen_addr, gai_strerror(gai));
+ return 0;
+ }
for (ai = ai0; ai; ai = ai->ai_next) {
int sockfd;
#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 */
}
if (flags >= 0)
fcntl(sockfd, F_SETFD, flags | FD_CLOEXEC);
- socklist = xrealloc(socklist, sizeof(int) * (socknum + 1));
- socklist[socknum++] = sockfd;
+ ALLOC_GROW(socklist->list, socklist->nr + 1, socklist->alloc);
+ socklist->list[socklist->nr++] = sockfd;
+ socknum++;
if (maxfd < sockfd)
maxfd = sockfd;
freeaddrinfo(ai0);
- *socklist_p = socklist;
return socknum;
}
#else /* NO_IPV6 */
-static int socksetup(char *listen_addr, int listen_port, int **socklist_p)
+static int setup_named_sock(char *listen_addr, int listen_port, struct socketlist *socklist)
{
struct sockaddr_in sin;
int sockfd;
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;
}
if (flags >= 0)
fcntl(sockfd, F_SETFD, flags | FD_CLOEXEC);
- *socklist_p = xmalloc(sizeof(int));
- **socklist_p = sockfd;
+ ALLOC_GROW(socklist->list, socklist->nr + 1, socklist->alloc);
+ socklist->list[socklist->nr++] = sockfd;
return 1;
}
#endif
-static int service_loop(int socknum, int *socklist)
+static void socksetup(struct string_list *listen_addr, int listen_port, struct socketlist *socklist)
+{
+ if (!listen_addr->nr)
+ setup_named_sock(NULL, listen_port, socklist);
+ else {
+ int i, socknum;
+ for (i = 0; i < listen_addr->nr; i++) {
+ socknum = setup_named_sock(listen_addr->items[i].string,
+ listen_port, socklist);
+
+ if (socknum == 0)
+ logerror("unable to allocate any listen sockets for host %s on port %u",
+ listen_addr->items[i].string, listen_port);
+ }
+ }
+}
+
+static int service_loop(struct socketlist *socklist)
{
struct pollfd *pfd;
int i;
- pfd = xcalloc(socknum, sizeof(struct pollfd));
+ pfd = xcalloc(socklist->nr, sizeof(struct pollfd));
- for (i = 0; i < socknum; i++) {
- pfd[i].fd = socklist[i];
+ for (i = 0; i < socklist->nr; i++) {
+ pfd[i].fd = socklist->list[i];
pfd[i].events = POLLIN;
}
check_dead_children();
- if (poll(pfd, socknum, -1) < 0) {
+ if (poll(pfd, socklist->nr, -1) < 0) {
if (errno != EINTR) {
logerror("Poll failed, resuming: %s",
strerror(errno));
continue;
}
- for (i = 0; i < socknum; i++) {
+ for (i = 0; i < socklist->nr; i++) {
if (pfd[i].revents & POLLIN) {
- struct sockaddr_storage ss;
- unsigned int sslen = sizeof(ss);
- int incoming = accept(pfd[i].fd, (struct sockaddr *)&ss, &sslen);
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in sai;
+#ifndef NO_IPV6
+ struct sockaddr_in6 sai6;
+#endif
+ } ss;
+ socklen_t sslen = sizeof(ss);
+ int incoming = accept(pfd[i].fd, &ss.sa, &sslen);
if (incoming < 0) {
switch (errno) {
case EAGAIN:
case ECONNABORTED:
continue;
default:
- die("accept returned %s", strerror(errno));
+ die_errno("accept returned");
}
}
- handle(incoming, (struct sockaddr *)&ss, sslen);
+ handle(incoming, &ss.sa, sslen);
}
}
}
while (fd != -1 && fd < 2)
fd = dup(fd);
if (fd == -1)
- die("open /dev/null or dup failed: %s", strerror(errno));
+ die_errno("open /dev/null or dup failed");
if (fd > 2)
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()) {
case 0:
break;
case -1:
- die("fork failed: %s", strerror(errno));
+ die_errno("fork failed");
default:
exit(0);
}
if (setsid() == -1)
- die("setsid failed: %s", strerror(errno));
+ die_errno("setsid failed");
close(0);
close(1);
close(2);
sanitize_stdfds();
}
+#endif
static void store_pid(const char *path)
{
FILE *f = fopen(path, "w");
if (!f)
- die("cannot open pid file %s: %s", path, strerror(errno));
+ die_errno("cannot open pid file '%s'", path);
if (fprintf(f, "%"PRIuMAX"\n", (uintmax_t) getpid()) < 0 || fclose(f) != 0)
- die("failed to write pid file %s: %s", path, strerror(errno));
+ die_errno("failed to write pid file '%s'", path);
}
-static int serve(char *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)
{
- int socknum, *socklist;
+ struct socketlist socklist = { NULL, 0, 0 };
- socknum = socksetup(listen_addr, listen_port, &socklist);
- if (socknum == 0)
- die("unable to allocate any listen sockets on host %s port %u",
- listen_addr, listen_port);
+ socksetup(listen_addr, listen_port, &socklist);
+ if (socklist.nr == 0)
+ 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);
- return service_loop(socknum, socklist);
+ return service_loop(&socklist);
}
int main(int argc, char **argv)
{
int listen_port = 0;
- char *listen_addr = NULL;
- int inetd_mode = 0;
+ struct string_list listen_addr = STRING_LIST_INIT_NODUP;
+ 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_extract_argv0_path(argv[0]);
char *arg = argv[i];
if (!prefixcmp(arg, "--listen=")) {
- listen_addr = xstrdup_tolower(arg + 9);
+ string_list_append(&listen_addr, xstrdup_tolower(arg + 9));
continue;
}
if (!prefixcmp(arg, "--port=")) {
continue;
}
}
+ if (!strcmp(arg, "--serve")) {
+ serve_mode = 1;
+ continue;
+ }
if (!strcmp(arg, "--inetd")) {
inetd_mode = 1;
log_syslog = 1;
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;
set_die_routine(daemon_die);
} else
/* avoid splitting a message in the middle */
- setvbuf(stderr, NULL, _IOLBF, 0);
+ setvbuf(stderr, NULL, _IOFBF, 4096);
- if (inetd_mode && (group_name || user_name))
- die("--user and --group are incompatible with --inetd");
+ if (inetd_mode && (detach || group_name || user_name))
+ die("--detach, --user and --group are incompatible with --inetd");
- if (inetd_mode && (listen_port || listen_addr))
+ if (inetd_mode && (listen_port || (listen_addr.nr > 0)))
die("--listen= and --port= are incompatible with --inetd");
else if (listen_port == 0)
listen_port = DEFAULT_GIT_PORT;
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");
base_path);
if (inetd_mode) {
- struct sockaddr_storage ss;
- struct sockaddr *peer = (struct sockaddr *)&ss;
- socklen_t slen = sizeof(ss);
-
if (!freopen("/dev/null", "w", stderr))
- die("failed to redirect stderr to /dev/null: %s",
- strerror(errno));
-
- if (getpeername(0, peer, &slen))
- peer = NULL;
-
- return execute(peer);
+ die_errno("failed to redirect stderr to /dev/null");
}
+ if (inetd_mode || serve_mode)
+ return execute();
+
if (detach) {
daemonize();
loginfo("Ready to rumble");
if (pid_file)
store_pid(pid_file);
- return serve(listen_addr, listen_port, pass, gid);
+ /* prepare argv for serving-processes */
+ cld_argv = xmalloc(sizeof (char *) * (argc + 2));
+ 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, cred);
}