Add virtualization support to git-daemon
Signed-off-by: Jon Loeliger Signed-off-by: Junio C Hamano <junkio@cox.net>maint
							parent
							
								
									62e27f273d
								
							
						
					
					
						commit
						49ba83fb67
					
				|  | @ -11,6 +11,7 @@ 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] | ||||
|              [--interpolated-path=pathtemplate] | ||||
|              [--enable=service] [--disable=service] | ||||
| 	     [--allow-override=service] [--forbid-override=service] | ||||
|              [--reuseaddr] [--detach] [--pid-file=file] | ||||
|  | @ -50,6 +51,12 @@ OPTIONS | |||
| 	'git://example.com/hello.git', `git-daemon` will interpret the path | ||||
| 	as '/srv/git/hello.git'. | ||||
|  | ||||
| --interpolated-path=pathtemplate:: | ||||
| 	To support virtual hosting, an interpolated path template can be | ||||
| 	used to dynamically construct alternate paths.  The template | ||||
| 	supports %H for the target hostname as supplied by the client, | ||||
| 	and %D for the absolute path of the named repository. | ||||
|  | ||||
| --export-all:: | ||||
| 	Allow pulling from all directories that look like GIT repositories | ||||
| 	(have the 'objects' and 'refs' subdirectories), even if they | ||||
|  | @ -135,6 +142,46 @@ upload-pack:: | |||
| 	disable it by setting `daemon.uploadpack` configuration | ||||
| 	item to `false`. | ||||
|  | ||||
| EXAMPLES | ||||
| -------- | ||||
| git-daemon as inetd server:: | ||||
| 	To set up `git-daemon` as an inetd service that handles any | ||||
| 	repository under the whitelisted set of directories, /pub/foo | ||||
| 	and /pub/bar, place an entry like the following into | ||||
| 	/etc/inetd all on one line: | ||||
| + | ||||
| ------------------------------------------------ | ||||
| 	git stream tcp nowait nobody  /usr/bin/git-daemon | ||||
| 		git-daemon --inetd --verbose | ||||
| 		--syslog --export-all | ||||
| 		/pub/foo /pub/bar | ||||
| ------------------------------------------------ | ||||
|  | ||||
|  | ||||
| git-daemon as inetd server for virtual hosts:: | ||||
| 	To set up `git-daemon` as an inetd service that handles | ||||
| 	repositories for different virtual hosts, `www.example.com` | ||||
| 	and `www.example.org`, place an entry like the following into | ||||
| 	`/etc/inetd` all on one line: | ||||
| + | ||||
| ------------------------------------------------ | ||||
| 	git stream tcp nowait nobody /usr/bin/git-daemon | ||||
| 		git-daemon --inetd --verbose | ||||
| 		--syslog --export-all | ||||
| 		--interpolated-path=/pub/%H%D | ||||
| 		/pub/www.example.org/software | ||||
| 		/pub/www.example.com/software | ||||
| 		/software | ||||
| ------------------------------------------------ | ||||
| + | ||||
| In this example, the root-level directory `/pub` will contain | ||||
| a subdirectory for each virtual host name supported. | ||||
| Further, both hosts advertise repositories simply as | ||||
| `git://www.example.com/software/repo.git`.  For pre-1.4.0 | ||||
| clients, a symlink from `/software` into the appropriate | ||||
| default repository could be made as well. | ||||
|  | ||||
|  | ||||
| Author | ||||
| ------ | ||||
| Written by Linus Torvalds <torvalds@osdl.org>, YOSHIFUJI Hideaki | ||||
|  |  | |||
							
								
								
									
										4
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										4
									
								
								Makefile
								
								
								
								
							|  | @ -246,7 +246,9 @@ DIFF_OBJS = \ | |||
|  | ||||
| LIB_OBJS = \ | ||||
| 	blob.o commit.o connect.o csum-file.o cache-tree.o base85.o \ | ||||
| 	date.o diff-delta.o entry.o exec_cmd.o ident.o lockfile.o \ | ||||
| 	date.o diff-delta.o entry.o exec_cmd.o ident.o \ | ||||
| 	interpolate.o \ | ||||
| 	lockfile.o \ | ||||
| 	object.o pack-check.o patch-delta.o path.o pkt-line.o sideband.o \ | ||||
| 	quote.o read-cache.o refs.o run-command.o dir.o object-refs.o \ | ||||
| 	server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \ | ||||
|  |  | |||
							
								
								
									
										85
									
								
								daemon.c
								
								
								
								
							
							
						
						
									
										85
									
								
								daemon.c
								
								
								
								
							|  | @ -12,6 +12,7 @@ | |||
| #include "pkt-line.h" | ||||
| #include "cache.h" | ||||
| #include "exec_cmd.h" | ||||
| #include "interpolate.h" | ||||
|  | ||||
| static int log_syslog; | ||||
| static int verbose; | ||||
|  | @ -21,6 +22,7 @@ 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" | ||||
| "           [--interpolated-path=path]\n" | ||||
| "           [--reuseaddr] [--detach] [--pid-file=file]\n" | ||||
| "           [--[enable|disable|allow-override|forbid-override]=service]\n" | ||||
| "           [--user=user [[--group=group]] [directory...]"; | ||||
|  | @ -34,6 +36,10 @@ static int export_all_trees; | |||
|  | ||||
| /* Take all paths relative to this one if non-NULL */ | ||||
| static char *base_path; | ||||
| static char *interpolated_path; | ||||
|  | ||||
| /* Flag indicating client sent extra args. */ | ||||
| static int saw_extended_args; | ||||
|  | ||||
| /* If defined, ~user notation is allowed and the string is inserted | ||||
|  * after ~user/.  E.g. a request to git://host/~alice/frotz would | ||||
|  | @ -45,6 +51,21 @@ static const char *user_path; | |||
| static unsigned int timeout; | ||||
| static unsigned int init_timeout; | ||||
|  | ||||
| /* | ||||
|  * Static table for now.  Ugh. | ||||
|  * Feel free to make dynamic as needed. | ||||
|  */ | ||||
| #define INTERP_SLOT_HOST	(0) | ||||
| #define INTERP_SLOT_DIR		(1) | ||||
| #define INTERP_SLOT_PERCENT	(2) | ||||
|  | ||||
| static struct interp interp_table[] = { | ||||
| 	{ "%H", 0}, | ||||
| 	{ "%D", 0}, | ||||
| 	{ "%%", "%"}, | ||||
| }; | ||||
|  | ||||
|  | ||||
| static void logreport(int priority, const char *err, va_list params) | ||||
| { | ||||
| 	/* We should do a single write so that it is atomic and output | ||||
|  | @ -152,10 +173,14 @@ static int avoid_alias(char *p) | |||
| 	} | ||||
| } | ||||
|  | ||||
| static char *path_ok(char *dir) | ||||
| static char *path_ok(struct interp *itable) | ||||
| { | ||||
| 	static char rpath[PATH_MAX]; | ||||
| 	static char interp_path[PATH_MAX]; | ||||
| 	char *path; | ||||
| 	char *dir; | ||||
|  | ||||
| 	dir = itable[INTERP_SLOT_DIR].value; | ||||
|  | ||||
| 	if (avoid_alias(dir)) { | ||||
| 		logerror("'%s': aliased", dir); | ||||
|  | @ -184,17 +209,28 @@ static char *path_ok(char *dir) | |||
| 			dir = rpath; | ||||
| 		} | ||||
| 	} | ||||
| 	else if (interpolated_path && saw_extended_args) { | ||||
| 		if (*dir != '/') { | ||||
| 			/* Allow only absolute */ | ||||
| 			logerror("'%s': Non-absolute path denied (interpolated-path active)", dir); | ||||
| 			return NULL; | ||||
| 		} | ||||
|  | ||||
| 		interpolate(interp_path, PATH_MAX, interpolated_path, | ||||
| 			    interp_table, ARRAY_SIZE(interp_table)); | ||||
| 		loginfo("Interpolated dir '%s'", interp_path); | ||||
|  | ||||
| 		dir = interp_path; | ||||
| 	} | ||||
| 	else if (base_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); | ||||
| 		dir = rpath; | ||||
| 	} | ||||
| 	} | ||||
|  | ||||
| 	path = enter_repo(dir, strict_paths); | ||||
|  | ||||
|  | @ -257,12 +293,14 @@ static int git_daemon_config(const char *var, const char *value) | |||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static int run_service(char *dir, struct daemon_service *service) | ||||
| static int run_service(struct interp *itable, struct daemon_service *service) | ||||
| { | ||||
| 	const char *path; | ||||
| 	int enabled = service->enabled; | ||||
|  | ||||
| 	loginfo("Request %s for '%s'", service->name, dir); | ||||
| 	loginfo("Request %s for '%s'", | ||||
| 		service->name, | ||||
| 		itable[INTERP_SLOT_DIR].value); | ||||
|  | ||||
| 	if (!enabled && !service->overridable) { | ||||
| 		logerror("'%s': service not enabled.", service->name); | ||||
|  | @ -270,7 +308,7 @@ static int run_service(char *dir, struct daemon_service *service) | |||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
| 	if (!(path = path_ok(dir))) | ||||
| 	if (!(path = path_ok(itable))) | ||||
| 		return -1; | ||||
|  | ||||
| 	/* | ||||
|  | @ -358,6 +396,28 @@ static void make_service_overridable(const char *name, int ena) { | |||
| 	die("No such service %s", name); | ||||
| } | ||||
|  | ||||
| static void parse_extra_args(char *extra_args, int buflen) | ||||
| { | ||||
| 	char *val; | ||||
| 	int vallen; | ||||
| 	char *end = extra_args + buflen; | ||||
|  | ||||
| 	while (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) { | ||||
| 				char *save = xmalloc(vallen); | ||||
| 				interp_table[INTERP_SLOT_HOST].value = save; | ||||
| 				strlcpy(save, val, vallen); | ||||
| 			} | ||||
| 			/* On to the next one */ | ||||
| 			extra_args = val + vallen; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static int execute(struct sockaddr *addr) | ||||
| { | ||||
| 	static char line[1000]; | ||||
|  | @ -398,13 +458,18 @@ static int execute(struct sockaddr *addr) | |||
| 	if (len && line[len-1] == '\n') | ||||
| 		line[--len] = 0; | ||||
|  | ||||
| 	if (len != pktlen) | ||||
| 	    parse_extra_args(line + len + 1, pktlen - len - 1); | ||||
|  | ||||
| 	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); | ||||
| 		    line[namelen + 4] == ' ') { | ||||
| 			interp_table[INTERP_SLOT_DIR].value = line+namelen+5; | ||||
| 			return run_service(interp_table, s); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	logerror("Protocol error: '%s'", line); | ||||
|  | @ -867,6 +932,10 @@ int main(int argc, char **argv) | |||
| 			base_path = arg+12; | ||||
| 			continue; | ||||
| 		} | ||||
| 		if (!strncmp(arg, "--interpolated-path=", 20)) { | ||||
| 			interpolated_path = arg+20; | ||||
| 			continue; | ||||
| 		} | ||||
| 		if (!strcmp(arg, "--reuseaddr")) { | ||||
| 			reuseaddr = 1; | ||||
| 			continue; | ||||
|  |  | |||
|  | @ -0,0 +1,82 @@ | |||
| /* | ||||
|  * Copyright 2006 Jon Loeliger | ||||
|  */ | ||||
|  | ||||
| #include <string.h> | ||||
|  | ||||
| #include "interpolate.h" | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Convert a NUL-terminated string in buffer orig | ||||
|  * into the supplied buffer, result, whose length is reslen, | ||||
|  * performing substitutions on %-named sub-strings from | ||||
|  * the table, interps, with ninterps entries. | ||||
|  * | ||||
|  * Example interps: | ||||
|  *    { | ||||
|  *        { "%H", "example.org"}, | ||||
|  *        { "%port", "123"}, | ||||
|  *        { "%%", "%"}, | ||||
|  *    } | ||||
|  * | ||||
|  * Returns 1 on a successful substitution pass that fits in result, | ||||
|  * Returns 0 on a failed or overflowing substitution pass. | ||||
|  */ | ||||
|  | ||||
| int interpolate(char *result, int reslen, | ||||
| 		char *orig, | ||||
| 		struct interp *interps, int ninterps) | ||||
| { | ||||
| 	char *src = orig; | ||||
| 	char *dest = result; | ||||
| 	int newlen = 0; | ||||
| 	char *name, *value; | ||||
| 	int namelen, valuelen; | ||||
| 	int i; | ||||
| 	char c; | ||||
|  | ||||
|         memset(result, 0, reslen); | ||||
|  | ||||
| 	while ((c = *src) && newlen < reslen - 1) { | ||||
| 		if (c == '%') { | ||||
| 			/* Try to match an interpolation string. */ | ||||
| 			for (i = 0; i < ninterps; i++) { | ||||
| 				name = interps[i].name; | ||||
| 				namelen = strlen(name); | ||||
| 				if (strncmp(src, name, namelen) == 0) { | ||||
| 					break; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			/* Check for valid interpolation. */ | ||||
| 			if (i < ninterps) { | ||||
| 				value = interps[i].value; | ||||
| 				valuelen = strlen(value); | ||||
|  | ||||
| 				if (newlen + valuelen < reslen - 1) { | ||||
| 					/* Substitute. */ | ||||
| 					strncpy(dest, value, valuelen); | ||||
| 					newlen += valuelen; | ||||
| 					dest += valuelen; | ||||
| 					src += namelen; | ||||
| 				} else { | ||||
| 					/* Something's not fitting. */ | ||||
| 					return 0; | ||||
| 				} | ||||
|  | ||||
| 			} else { | ||||
| 				/* Skip bogus interpolation. */ | ||||
| 				*dest++ = *src++; | ||||
| 				newlen++; | ||||
| 			} | ||||
|  | ||||
| 		} else { | ||||
| 			/* Straight copy one non-interpolation character. */ | ||||
| 			*dest++ = *src++; | ||||
| 			newlen++; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return newlen < reslen - 1; | ||||
| } | ||||
|  | @ -0,0 +1,18 @@ | |||
| /* | ||||
|  * Copyright 2006 Jon Loeliger | ||||
|  */ | ||||
|  | ||||
| #ifndef INTERPOLATE_H | ||||
| #define INTERPOLATE_H | ||||
|  | ||||
|  | ||||
| struct interp { | ||||
| 	char *name; | ||||
| 	char *value; | ||||
| }; | ||||
|  | ||||
| extern int interpolate(char *result, int reslen, | ||||
| 		       char *orig, | ||||
| 		       struct interp *interps, int ninterps); | ||||
|  | ||||
| #endif /* INTERPOLATE_H */ | ||||
		Loading…
	
		Reference in New Issue
	
	 Jon Loeliger
						Jon Loeliger