Signed-off-by: Pierre Habouzit <madcoder@xxxxxxxxxx> --- Documentation/git-daemon.txt | 54 +++++++++++ daemon.c | 207 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 240 insertions(+), 21 deletions(-) diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 0f7d274..796ae7e 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -11,6 +11,8 @@ SYNOPSIS 'git-daemon' [--verbose] [--syslog] [--inetd | --port=n] [--export-all] [--timeout=n] [--init-timeout=n] [--strict-paths] [--base-path=path] [--user-path | --user-path=path] + [--base-path-fmt=pathfmt] [--user-path-fmt=pathfmt] + [--default-hostname=hostname] [--reuseaddr] [--detach] [--pid-file=file] [directory...] DESCRIPTION @@ -45,6 +47,10 @@ OPTIONS 'git://example.com/hello.git', `git-daemon` will interpret the path as '/srv/git/hello.git'. +--base-path-fmt=pathfmt:: + Works like --base-path, but uses a format instead. See Path + Formats section below. + --export-all:: Allow pulling from all directories that look like GIT repositories (have the 'objects' and 'refs' subdirectories), even if they @@ -79,9 +85,20 @@ OPTIONS taken as a request to access `path/foo` repository in the home directory of user `alice`. +--user-path-fmt=pathfmt:: + Allow ~user notation to be used in requests. The path used to look + for the git repository is a format string, see Path Formats + section below. + --verbose:: Log details about the incoming connections and requested files. +--default-hostname=hostname:: + Backward compatibility switch: argument is used as a fallback + hostname to use with virtual hosting (to fill the `%h` format in + path formats) when the client is too old to provide the information + in the query (older than 1.4.0 series). + --reuseaddr:: Use SO_REUSEADDR when binding the listening socket. This allows the server to restart without waiting for @@ -98,10 +115,45 @@ OPTIONS --strict-paths is specified this will also include subdirectories of each named directory. +Path Formats +------------ + +%h:: + requested hostname. That won't work with clients older than 1.4.0 + that do not pass the hostname in their query, and in that case, the + value of --default-hostname (if any) is used. + +%p:: + requested path. + +%P:: + requested path without the first slash when exists (`/`). + +%u:: + requested username (only allowed for --user-path-fmt). + +%%:: + plain `%`. + +Examples +~~~~~~~~ + +* `--base-path-fmt=/srv/git/%P` emulates `--base-path=/srv/git` + +* `--base-path-fmt=/srv/git/%h%p` will look into + `/srv/git/git.example.com/foo` if the request goes to + git://git.example.com/foo + +* `--user-path-fmt=/home/%u/public_git/%P` works mostly like + `--user-path=public_git` (the difference is that it assumes that home + directories live in `/home/username` instead of using the real `username` + home). + Author ------ Written by Linus Torvalds <torvalds@xxxxxxxx>, YOSHIFUJI Hideaki -<yoshfuji@xxxxxxxxxxxxxx> and the git-list <git@xxxxxxxxxxxxxxx> +<yoshfuji@xxxxxxxxxxxxxx>, Pierre Habouzit <madcoder@xxxxxxxxxx>, +and the git-list <git@xxxxxxxxxxxxxxx> Documentation -------------- diff --git a/daemon.c b/daemon.c index 012936f..a2fb9b4 100644 --- a/daemon.c +++ b/daemon.c @@ -19,6 +19,8 @@ static const char daemon_usage[] = "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" +" [--base-path-fmt=pathfmt] [--user-path-fmt=pathfmt]\n" +" [--default-hostname=hostname]\n" " [--reuseaddr] [--detach] [--pid-file=file] [directory...]"; /* List of acceptable pathname prefixes */ @@ -29,18 +31,27 @@ static int strict_paths; static int export_all_trees; /* Take all paths relative to this one if non-NULL */ -static char *base_path; +static struct { + const char *path; + int use_as_fmt; +} 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; +static struct { + const char *path; + int use_as_fmt; +} user_path; /* Timeout, and initial timeout */ static unsigned int timeout; static unsigned int init_timeout; +/* default host used when the client is old and does not provides host= */ +static char *default_host; + static void logreport(int priority, const char *err, va_list params) { /* We should do a single write so that it is atomic and output @@ -148,7 +159,107 @@ static int avoid_alias(char *p) } } -static char *path_ok(char *dir) +static void check_path_fmt(const char *check, int allow_user) +{ + const char *p = check; + + while (*p) { + if (*p++ != '%') + continue; + + switch (*p) { + case '\0': + die("invalid format: <%s> ends with an unescaped %%", + check); + + case '%': + case 'p': case 'P': + case 'h': + break; + + case 'u': + if (allow_user) + break; + /* fallthrough */ + + default: + die("invalid format: <%s> uses unknown specifier %%%c", + check, *p); + } + + p++; + } +} + +static char * +git_path_fmt(char rpath[PATH_MAX], const char *fmt, + const char *vhost, const char *path, + const char *username, int namlen) +{ + const char *p = fmt; + int pos = 0; + + while (*p && pos < PATH_MAX) { + if (*p != '%') { + rpath[pos++] = *p++; + continue; + } + + switch (*++p) { + case '%': + rpath[pos++] = *p; + break; + + case 'h': + if (!vhost && !default_host) { + logerror("abort: missing host=, no --default-host given, client is too old"); + return NULL; + } + + if (!vhost) { + logerror("old client without host=, using <%s>", default_host); + pos += strlcpy(rpath + pos, default_host, PATH_MAX - pos); + } else { + pos += strlcpy(rpath + pos, vhost, PATH_MAX - pos); + } + break; + + case 'P': + pos += strlcpy(rpath + pos, path + (*path == '/'), + PATH_MAX - pos); + break; + + case 'p': + pos += strlcpy(rpath + pos, path, PATH_MAX - pos); + break; + + case 'u': + pos += snprintf(rpath + pos, PATH_MAX - pos, "%.*s", + namlen, username); + break; + } + + p++; + } + + if (pos >= PATH_MAX) { + logerror("generated path is too long"); + return NULL; + } + + rpath[pos] = '\0'; + + return rpath; +} + +static inline char * +git_base_path_fmt(char rpath[PATH_MAX], const char *fmt, + const char *vhost, const char *path) +{ + return git_path_fmt(rpath, fmt, vhost, path, NULL, 0); +} + +static char *path_ok(char *dir, char *vhost) { static char rpath[PATH_MAX]; char *path; @@ -159,11 +270,11 @@ static char *path_ok(char *dir) } if (*dir == '~') { - if (!user_path) { + if (!user_path.path) { logerror("'%s': User-path not allowed", dir); return NULL; } - if (*user_path) { + if (*user_path.path) { /* Got either "~alice" or "~alice/foo"; * rewrite them to "~alice/%s" or * "~alice/%s/foo". @@ -174,24 +285,45 @@ static char *path_ok(char *dir) 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; + + if (user_path.use_as_fmt) { + loginfo("host <%s>, " + "userpathfmt <%s>, request <%s>, " + "namlen %d, restlen %d, slash <%s>", + vhost, + user_path.path, dir, + namlen, restlen, slash); + dir = git_path_fmt(rpath, user_path.path, vhost, + slash, dir + 1, namlen - 1); + } else { + loginfo("userpath <%s>, request <%s>, " + "namlen %d, restlen %d, slash <%s>", + user_path.path, dir, + namlen, restlen, slash); + snprintf(rpath, PATH_MAX, "%.*s/%s%.*s", + namlen, dir, user_path.path, restlen, slash); + dir = rpath; + } } } - else if (base_path) { + else if (base_path.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); + + if (base_path.use_as_fmt) { + dir = git_base_path_fmt(rpath, base_path.path, vhost, dir); + } else { + snprintf(rpath, PATH_MAX, "%s%s", base_path.path, dir); dir = rpath; } } + if (!dir) + return NULL; + path = enter_repo(dir, strict_paths); if (!path) { @@ -229,7 +361,7 @@ static char *path_ok(char *dir) return NULL; /* Fallthrough. Deny by default */ } -static int upload(char *dir) +static int upload(char *dir, char *vhost) { /* Timeout as string */ char timeout_buf[64]; @@ -237,7 +369,7 @@ static int upload(char *dir) loginfo("Request for '%s'", dir); - if (!(path = path_ok(dir))) + if (!(path = path_ok(dir, vhost))) return -1; /* @@ -274,6 +406,7 @@ static int execute(struct sockaddr *addr { static char line[1000]; int pktlen, len; + char *vhost = NULL; if (addr) { char addrbuf[256] = ""; @@ -303,15 +436,30 @@ #endif alarm(0); len = strlen(line); - if (pktlen != len) + + if (pktlen != len) { + int arg_pos = len + 1; + loginfo("Extended attributes (%d bytes) exist <%.*s>", (int) pktlen - len, - (int) pktlen - len, line + len + 1); + (int) pktlen - len, line + arg_pos); + + while (arg_pos < pktlen) { + int arg_len = strlen(line + arg_pos); + + if (!strncmp("host=", line + arg_pos, 5)) { + vhost = line + arg_pos + 5; + } + + arg_pos += arg_len + 1; + } + } + if (len && line[len-1] == '\n') line[--len] = 0; if (!strncmp("git-upload-pack ", line, 16)) - return upload(line+16); + return upload(line+16, vhost); logerror("Protocol error: '%s'", line); return -1; @@ -767,19 +915,38 @@ int main(int argc, char **argv) continue; } if (!strncmp(arg, "--base-path=", 12)) { - base_path = arg+12; + base_path.path = arg+12; + base_path.use_as_fmt = 0; + continue; + } + if (!strncmp(arg, "--base-path-fmt=", 16)) { + base_path.path = arg+16; + base_path.use_as_fmt = 1; + check_path_fmt(base_path.path, 0); continue; } if (!strcmp(arg, "--reuseaddr")) { reuseaddr = 1; continue; } + if (!strncmp(arg, "--default-hostname=", 19)) { + default_host = arg + 19; + continue; + } if (!strcmp(arg, "--user-path")) { - user_path = ""; + user_path.path = ""; + user_path.use_as_fmt = 0; continue; } if (!strncmp(arg, "--user-path=", 12)) { - user_path = arg + 12; + user_path.path = arg + 12; + user_path.use_as_fmt = 0; + continue; + } + if (!strncmp(arg, "--user-path-fmt=", 16)) { + user_path.path = arg + 16; + user_path.use_as_fmt = 1; + check_path_fmt(user_path.path, 1); continue; } if (!strncmp(arg, "--pid-file=", 11)) { -- 1.4.1.1 - To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html