commit b09c127974759a56a6b5273da53d215f8500f5a7 from: Stefan Sperling date: Tue Mar 28 09:54:20 2023 UTC add gitwrapper(1) ok op@, tracey@ earlier version 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 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 + * + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; }