Commit Diff


commit - fd39115905268f01383ad19aee3aabb772854b68
commit + b09c127974759a56a6b5273da53d215f8500f5a7
blob - 8f2de2846e8dacea7b203a6e29ba07b05a069a13
blob + 86e0c721884cc636b289ca3fd5d77a75bedc9f8f
--- Makefile
+++ Makefile
@@ -7,7 +7,7 @@ SUBDIR += regress
 .endif
 
 .if make(clean) || make(obj) || make(release)
-SUBDIR += gotwebd gotd gotsh gotctl template
+SUBDIR += gotwebd gotd gotsh gotctl template gitwrapper
 .endif
 
 .if make(tags) || make(cleandir)
@@ -49,11 +49,13 @@ server:
 	${MAKE} -C gotctl
 	${MAKE} -C gotd
 	${MAKE} -C gotsh
+	${MAKE} -C gitwrapper
 
 server-install:
 	${MAKE} -C gotctl install
 	${MAKE} -C gotd install
 	${MAKE} -C gotsh install
+	${MAKE} -C gitwrapper install
 
 server-regress:
 	${MAKE} -C regress/gotd
blob - a67c022cd57805a8d11d93379be5d9306df2ec48
blob + 2328859beb94e97d832f30c6cbb5eb26ac6d3b0e
--- README
+++ README
@@ -73,6 +73,7 @@ This will install the following commands:
  gotd, the repository server program
  gotctl, the server control utility
  gotsh, the login shell for users accessing the server via the network
+ gitwrapper, like mailwrapper(8) but for git-upload-pack and git-receive-pack
 
 See the following manual page files for information about server setup:
 
@@ -80,6 +81,7 @@ See the following manual page files for information ab
  $ man -l gotd/gotd.conf.5
  $ man -l gotctl/gotctl.8
  $ man -l gotsh/gotsh.1
+ $ man -l gitwrapper/gitwrapper.1
 
 See regress/gotd/README for information about running the server test suite.
 
blob - /dev/null
blob + cd56f97718ffa1b0dc5eabec3e2e6048cbb763b7 (mode 644)
--- /dev/null
+++ gitwrapper/Makefile
@@ -0,0 +1,41 @@
+.PATH:${.CURDIR}/../lib
+.PATH:${.CURDIR}/../gotd
+
+.include "../got-version.mk"
+
+.if ${GOT_RELEASE} == "Yes"
+BINDIR ?=	${PREFIX}/bin
+.endif
+
+PROG=		gitwrapper
+SRCS=		gitwrapper.c parse.y log.c serve.c auth.c listen.c pkt.c \
+		error.c gitproto.c hash.c reference.c object.c object_parse.c \
+		path.c object_idset.c object_create.c inflate.c opentemp.c \
+		lockfile.c repository.c gotconfig.c pack.c bloom.c buf.c \
+		object_cache.c privsep_stub.c pollfd.c imsg.c \
+		reference_parse.c object_open_io.c sigs.c deflate.c \
+		read_gotconfig.c read_gitconfig.c delta_cache.c delta.c \
+		murmurhash2.c date.c gitconfig.c
+
+CLEANFILES = parse.h
+
+MAN =		${PROG}.conf.5 ${PROG}.1
+
+CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib -I${.CURDIR}/../gotd
+
+.if defined(PROFILE)
+LDADD = -lutil_p -lz_p -lm_p -lc_p -levent_p
+.else
+LDADD = -lutil -lz -lm -levent
+.endif
+DPADD = ${LIBZ} ${LIBUTIL}
+
+.if ${GOT_RELEASE} != "Yes"
+NOMAN = Yes
+.endif
+
+realinstall:
+	${INSTALL} ${INSTALL_COPY} -o ${BINOWN} -g ${BINGRP} \
+	-m ${BINMODE} ${PROG} ${BINDIR}/${PROG}
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + 1abe622b5804d7eb31b472b5d727cb086f7c8c7e (mode 644)
--- /dev/null
+++ gitwrapper/gitwrapper.1
@@ -0,0 +1,114 @@
+.\"
+.\" Copyright (c) 2023 Stefan Sperling
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate$
+.Dt GITWRAPPER 1
+.Os
+.Sh NAME
+.Nm gitwrapper
+.Nd invoke an appropriate Git repository server
+.Sh SYNOPSIS
+.Nm Fl c Sq Cm git-receive-pack Ar repository-path
+.Nm Fl c Sq Cm git-upload-pack Ar repository-path
+.Sh DESCRIPTION
+At one time, the only Git repository server software easily available
+was built into
+.Xr git-upload-pack 1
+and
+.Xr git-receive-pack 1
+which are part of the
+.Xr git 1
+suite.
+As a result of this, most Git client implementations had the path and
+calling conventions expected by
+.Xr git 1
+compiled in.
+.Pp
+Times have changed, however.  On a modern system, the administrator may
+wish to use one of several available Git repository servers, such as
+.Xr gotd 8 .
+.Pp
+It would be difficult to modify all Git client software typically available
+on a system, so most of the authors of alternative Git servers have written
+their programs so that they use the same calling conventions as
+.Xr git-upload-pack 1
+and
+.Xr git-receive-pack 1
+and may be put into place in their stead.
+.Pp
+Although having drop-in replacements for
+.Xr git-upload-pack 1
+and
+.Xr git-receive-pack 1
+helps in installing alternative Git servers, it essentially makes the
+configuration of the system depend on hard installing new programs in /usr.
+This leads to configuration problems for many administrators, since they may
+wish to install a new Git server without altering the system provided /usr.
+(This may be, for example, to avoid having upgrade problems when a new
+version of the system is installed over the old.)  They may also have a
+shared /usr among several machines, and may wish to avoid placing implicit
+configuration information in a read-only /usr.
+.Pp
+The
+.Nm
+program is designed to replace
+.Xr git-upload-pack 1
+and
+.Xr git-receive-pack 1
+and to invoke an appropriate Git server based on configuration information
+placed in
+.Xr gotd.conf 5 .
+This permits the administrator to configure which Git server is to be
+invoked on the system at run-time.
+Git repositories which are listed in
+.Xr gotd.conf 5
+and exist on the filesystem will be served by
+.Xr gotsh 1 .
+Any other Git repositories will be served by
+.Xr git-upload-pack 1
+and
+.Xr git-receive-pack 1 .
+.Sh FILES
+Configuration for
+.Xr gotd 8
+is kept in
+.Pa /etc/gotd.conf.
+.Pp
+.Pa git-upload-pack
+and
+.Pa git-receive-pack
+are typically set up as a symlink to
+.Nm
+which is not usually invoked on its own.
+.Sh ENVIRONMENT
+.Bl -tag -width GOTD_CONF_PATH
+.It Ev GOTD_CONF_PATH
+Set the path to the configuration file for
+.Xr gotd 8 .
+If not specified, the default path
+.Pa /etc/gotd.conf
+will be used.
+.El
+.Sh SEE ALSO
+.Xr got 1 ,
+.Xr gotd.conf 5 ,
+.Xr gotd 8 ,
+.Xr mailwrapper 8
+.Sh AUTHORS
+.An Stefan Sperling Aq Mt stsp@openbsd.org
+.Sh BUGS
+The entire reason this program exists is a crock. Instead, a command for
+invoking a Git server should be standardized or the Git protocol should
+be changed to make the path to the program discoverable by Git clients.
blob - /dev/null
blob + a5d0bcb2233ff202330b3c197e216e3300f272f4 (mode 644)
--- /dev/null
+++ gitwrapper/gitwrapper.c
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2023 Stefan Sperling <stsp@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Resolve path namespace conflicts for git-upload-pack and git-receive-pack.
+ */
+
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sha1.h>
+#include <sha2.h>
+#include <syslog.h>
+#include <util.h>
+#include <unistd.h>
+
+#include "got_error.h"
+#include "got_path.h"
+#include "got_serve.h"
+
+#include "gotd.h"
+#include "log.h"
+
+#ifndef GITWRAPPER_GIT_LIBEXEC_DIR
+#define GITWRAPPER_GIT_LIBEXEC_DIR "/usr/local/libexec/git"
+#endif
+
+#ifndef GITWRAPPER_MY_SERVER_PROG
+#define GITWRAPPER_MY_SERVER_PROG "gotsh"
+#endif
+
+
+__dead static void
+usage(void)
+{
+	fprintf(stderr, "usage: %s -c '%s|%s repository-path'\n",
+	    getprogname(), GOT_SERVE_CMD_SEND, GOT_SERVE_CMD_FETCH);
+	exit(1);
+}
+
+/*
+ * Unveil the specific programs we want to start and hide everything else.
+ * This is important to limit the impact of our "exec" pledge.
+ */
+static const struct got_error *
+apply_unveil(const char *myserver)
+{
+	const char *fetchcmd = GITWRAPPER_GIT_LIBEXEC_DIR "/" \
+		GOT_SERVE_CMD_FETCH;
+	const char *sendcmd = GITWRAPPER_GIT_LIBEXEC_DIR "/" \
+		GOT_SERVE_CMD_SEND;
+
+#ifdef PROFILE
+	if (unveil("gmon.out", "rwc") != 0)
+		return got_error_from_errno2("unveil", "gmon.out");
+#endif
+	if (unveil(fetchcmd, "x") != 0)
+		return got_error_from_errno2("unveil", fetchcmd);
+
+	if (unveil(sendcmd, "x") != 0)
+		return got_error_from_errno2("unveil", sendcmd);
+
+	if (myserver && unveil(myserver, "x") != 0)
+		return got_error_from_errno2("unveil", myserver);
+
+	if (unveil(NULL, NULL) != 0)
+		return got_error_from_errno("unveil");
+
+	return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+	const struct got_error *error;
+	const char *confpath = NULL;
+	char *command = NULL, *repo_name = NULL; /* for matching gotd.conf */
+	char *myserver = NULL;
+	const char *repo_path = NULL; /* as passed on the command line */
+	const char *relpath;
+	char *gitcommand = NULL;
+	struct gotd gotd;
+	struct gotd_repo *repo = NULL;
+	pid_t pid;
+	int st = -1;
+
+	log_init(1, LOG_USER); /* Log to stderr. */
+
+#ifndef PROFILE
+	if (pledge("stdio rpath proc exec unveil", NULL) == -1)
+		err(1, "pledge");
+#endif
+
+	/*
+	 * Look up our own server program in PATH so we can unveil(2) it.
+	 * This call only errors out upon memory allocation failure.
+	 * If the program cannot be found then myserver will be set to NULL.
+	 */
+	error = got_path_find_prog(&myserver, GITWRAPPER_MY_SERVER_PROG);
+	if (error)
+		goto done;
+
+	/*
+	 * Run parse_config() before unveil(2) because parse_config()
+	 * checks whether repository paths exist on disk.
+	 * Parsing errors and warnings will be logged to stderr.
+	 * Upon failure we will run Git's native tooling so do not
+	 * bother checking for errors here.
+	 */
+	confpath = getenv("GOTD_CONF_PATH");
+	if (confpath == NULL)
+		confpath = GOTD_CONF_PATH;
+	parse_config(confpath, PROC_GOTD, &gotd);
+
+	error = apply_unveil(myserver);
+	if (error)
+		goto done;
+
+#ifndef PROFILE
+	if (pledge("stdio proc exec", NULL) == -1)
+		err(1, "pledge");
+#endif
+
+	if (strcmp(getprogname(), GOT_SERVE_CMD_SEND) == 0 ||
+	    strcmp(getprogname(), GOT_SERVE_CMD_FETCH) == 0) {
+		if (argc != 2)
+			usage();
+		command = strdup(getprogname());
+		if (command == NULL) {
+			error = got_error_from_errno("strdup");
+			goto done;
+		}
+		repo_path = argv[1];
+		relpath = argv[1];
+		while (relpath[0] == '/')
+			relpath++;
+		repo_name = strdup(relpath);
+		if (repo_name == NULL) {
+			error = got_error_from_errno("strdup");
+			goto done;
+		}
+	} else {
+		if (argc != 3 || strcmp(argv[1], "-c") != 0)
+			usage();
+		repo_path = argv[2];
+		error = got_serve_parse_command(&command, &repo_name,
+		    repo_path);
+		if (error && error->code == GOT_ERR_BAD_PACKET)
+			usage();
+		if (error)
+			goto done;
+	}
+
+	repo = gotd_find_repo_by_name(repo_name, &gotd);
+
+	/*
+	 * Invoke our custom Git server if it was found in PATH and
+	 * if the repository was found in gotd.conf.
+	 * Otherwise invoke native git(1) tooling.
+	 */
+	switch (pid = fork()) {
+	case -1:
+		goto done;
+	case 0:
+		if (repo && myserver) {
+			if (execl(myserver, command, repo_name,
+			    (char *)NULL) ==  -1) {
+				error = got_error_from_errno2("execl",
+				    myserver);
+				goto done;
+			 }
+		} else {
+			if (asprintf(&gitcommand, "%s/%s",
+			    GITWRAPPER_GIT_LIBEXEC_DIR, command) == -1) {
+				error = got_error_from_errno("asprintf");
+				goto done;
+			}
+			if (execl(gitcommand, gitcommand, repo_path,
+			    (char *)NULL) == -1) {
+				error = got_error_from_errno2("execl",
+				    gitcommand);
+				goto done;
+			 }
+		}
+		_exit(127);
+	}
+
+	while (waitpid(pid, &st, 0) == -1) {
+		if (errno != EINTR)
+			break;
+	}
+done:
+	free(command);
+	free(repo_name);
+	free(myserver);
+	free(gitcommand);
+	if (error) {
+		fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
+		return 1;
+	}
+
+	return 0;
+}
blob - 0779cab1856784fdd06081ce2ddf025599ae207a
blob + e030b87f2810cd0261799b0a15ef3e38beeb6ae0
--- got-dist.txt
+++ got-dist.txt
@@ -5,6 +5,10 @@
 /Makefile.inc
 /README
 /TODO
+/gitwrapper
+/gitwrapper/Makefile
+/gitwrapper/gitwrapper.1
+/gitwrapper/gitwrapper.c
 /got
 /got-version.mk
 /got/Makefile
blob - 86f2b6013f0c53e31f8adbc89cd954b062c2ee0b
blob + c7a273204feec2eff188ea2e2530aa3c3d833253
--- gotd/gotd.c
+++ gotd/gotd.c
@@ -498,25 +498,7 @@ stop_gotd(struct gotd_client *client)
 	/* NOTREACHED */
 	return NULL;
 }
-
-static struct gotd_repo *
-find_repo_by_name(const char *repo_name)
-{
-	struct gotd_repo *repo;
-	size_t namelen;
 
-	TAILQ_FOREACH(repo, &gotd.repos, entry) {
-		namelen = strlen(repo->name);
-		if (strncmp(repo->name, repo_name, namelen) != 0)
-			continue;
-		if (repo_name[namelen] == '\0' ||
-		    strcmp(&repo_name[namelen], ".git") == 0)
-			return repo;
-	}
-
-	return NULL;
-}
-
 static const struct got_error *
 start_client_authentication(struct gotd_client *client, struct imsg *imsg)
 {
@@ -541,7 +523,7 @@ start_client_authentication(struct gotd_client *client
 		err = ensure_client_is_not_writing(client);
 		if (err)
 			return err;
-		repo = find_repo_by_name(ireq.repo_name);
+		repo = gotd_find_repo_by_name(ireq.repo_name, &gotd);
 		if (repo == NULL)
 			return got_error(GOT_ERR_NOT_GIT_REPO);
 		err = start_auth_child(client, GOTD_AUTH_READ, repo,
@@ -553,7 +535,7 @@ start_client_authentication(struct gotd_client *client
 		err = ensure_client_is_not_reading(client);
 		if (err)
 			return err;
-		repo = find_repo_by_name(ireq.repo_name);
+		repo = gotd_find_repo_by_name(ireq.repo_name, &gotd);
 		if (repo == NULL)
 			return got_error(GOT_ERR_NOT_GIT_REPO);
 		err = start_auth_child(client,
@@ -1142,7 +1124,7 @@ gotd_dispatch_auth_child(int fd, short event, void *ar
 		goto done;
 	}
 
-	repo = find_repo_by_name(client->auth->repo_name);
+	repo = gotd_find_repo_by_name(client->auth->repo_name, &gotd);
 	if (repo == NULL) {
 		err = got_error(GOT_ERR_NOT_GIT_REPO);
 		goto done;
@@ -1292,8 +1274,9 @@ gotd_dispatch_client_session(int fd, short event, void
 
 		if (do_start_repo_child) {
 			struct gotd_repo *repo;
+			const char *name = client->session->repo_name;
 
-			repo = find_repo_by_name(client->session->repo_name);
+			repo = gotd_find_repo_by_name(name, &gotd);
 			if (repo != NULL) {
 				enum gotd_procid proc_type;
 
blob - e22197aef200b4b37d02bf22b05649f1af0a55a4
blob + 63cffc677d842269d09d3f07a3cc61bdf1e6c28b
--- gotd/gotd.h
+++ gotd/gotd.h
@@ -447,6 +447,7 @@ struct gotd_imsg_auth {
 };
 
 int parse_config(const char *, enum gotd_procid, struct gotd *);
+struct gotd_repo *gotd_find_repo_by_name(const char *, struct gotd *);
 
 /* imsg.c */
 const struct got_error *gotd_imsg_flush(struct imsgbuf *);
blob - 7c1d70c3d90da2de044cf00d5ed56f0b3efae0c3
blob + 9a5b91d273ade58fbf2195742c6bf0866eb7ac69
--- gotd/parse.y
+++ gotd/parse.y
@@ -891,4 +891,22 @@ symget(const char *nam)
 		}
 	}
 	return (NULL);
+}
+
+struct gotd_repo *
+gotd_find_repo_by_name(const char *repo_name, struct gotd *gotd)
+{
+	struct gotd_repo *repo;
+	size_t namelen;
+
+	TAILQ_FOREACH(repo, &gotd->repos, entry) {
+		namelen = strlen(repo->name);
+		if (strncmp(repo->name, repo_name, namelen) != 0)
+			continue;
+		if (repo_name[namelen] == '\0' ||
+		    strcmp(&repo_name[namelen], ".git") == 0)
+			return repo;
+	}
+
+	return NULL;
 }