greenhouse

greenhouse

minimal software forge

git clone git@git.lostduk.com:greenhouse.git

feat(hedge): port kore-git to hedge

commitc2eaba29b18c2a3ce95da933debc3d6511bc9aaf
parent1085825b056f9a9bdba925d3ea7f3851d1f06f1b
authorlostduk <hi@lostduk.com>
date2026-10-17 16:38 +0700
.gitignore3+
hedge/Makefile30+++++++
hedge/config.def.h3+
hedge/config.mk7++
hedge/hedge.c188++++++++++++++++++++++++++++++++++++++++

5 files changed, -0/+231

.gitignore

@@ -1,4 +1,7 @@ .dev/+hedge/hedge+hedge/*.o+hedge/config.h petal/petal petal/*.o petal/config.h

hedge/Makefile

@@ -0,0 +1,30 @@+include config.mk++SRC = hedge.c+OBJ = ${SRC:.c=.o}++all: hedge++.c.o:+	${CC} -c ${CFLAGS} $<++${OBJ}: config.h config.mk++config.h:+	cp config.def.h $@++hedge: ${OBJ}+	${CC} -o $@ ${OBJ} ${LDFLAGS}++clean:+	rm -f hedge ${OBJ}++install: all+	mkdir -p ${DESTDIR}${PREFIX}/bin+	cp -f hedge ${DESTDIR}${PREFIX}/bin+	chmod 755 ${DESTDIR}${PREFIX}/bin/hedge++uninstall:+	rm -f ${DESTDIR}${PREFIX}/bin/hedge++.PHONY: all clean install uninstall

hedge/config.def.h

@@ -0,0 +1,3 @@+static const char *root     = "/srv/git";+static const char *acl      = "/etc/hedge/acl";+static const char *gitshell = "git-shell";

hedge/config.mk

@@ -0,0 +1,7 @@+PREFIX = /usr/local++CPPFLAGS = -D_XOPEN_SOURCE=700+CFLAGS   = -std=c99 -pedantic -Wall -Wextra -Os ${CPPFLAGS}+LDFLAGS  =++CC = cc

hedge/hedge.c

@@ -0,0 +1,188 @@+/* imports */+#include <ctype.h>+#include <errno.h>+#include <limits.h>+#include <stdarg.h>+#include <stdio.h>+#include <stdlib.h>+#include <string.h>+#include <unistd.h>++/* macros */+#define LENGTH(X) (sizeof(X) / sizeof((X)[0]))++/* enums */+typedef struct {+	const char *verb;+	const char *perm;+} Verb;++/* function declarations */+static int allowed(const char *path, const char *perm);+static int bprintf(char *buf, size_t len, const char *fmt, ...);+static void deny(void);+static char *trim(char *path);+static void usage(void);+static int validname(const char *s);++/* variables */+static const Verb verbs[] = {+	{ "git-upload-pack",    "read"  },+	{ "git-upload-archive", "read"  },+	{ "git-receive-pack",   "write" },+};+static const char *id;++#include "config.h"++/* function implementations */+int+allowed(const char *path, const char *perm)+{+	char line[PATH_MAX + 64], w[PATH_MAX + 64], r[PATH_MAX + 64];+	FILE *fp;+	size_t n;+	int c, ok = 0;++	if (bprintf(w, sizeof(w), "%s write %s", path, id) < 0+	|| bprintf(r, sizeof(r), "%s read %s", path, id) < 0)+		return 0;+	if (!(fp = fopen(acl, "r"))) {+		fprintf(stderr, "hedge: %s: %s\n", acl, strerror(errno));+		return 0;+	}+	while (!ok && fgets(line, sizeof(line), fp)) {+		n = strcspn(line, "\n");+		if (!line[n] && !feof(fp)) {+			while ((c = fgetc(fp)) != EOF && c != '\n')+				;+			continue;+		}+		line[n] = '\0';+		ok = !strcmp(line, w)+		  || (!strcmp(perm, "read") && !strcmp(line, r));+	}+	fclose(fp);+	return ok;+}++int+bprintf(char *buf, size_t len, const char *fmt, ...)+{+	va_list ap;+	int n;++	va_start(ap, fmt);+	n = vsnprintf(buf, len, fmt, ap);+	va_end(ap);+	if (n < 0 || (size_t)n >= len)+		return -1;+	return 0;+}++void+deny(void)+{+	fputs("hedge: access denied\n", stderr);+	exit(1);+}++char *+trim(char *path)+{+	size_t n;++	if (*path == '\'')+		path++;+	if ((n = strlen(path)) && path[n - 1] == '\'')+		path[--n] = '\0';+	if (*path == '/')+		path++;+	if ((n = strlen(path)) && path[n - 1] == '/')+		path[--n] = '\0';+	if (n >= 4 && !strcmp(path + n - 4, ".git"))+		path[n - 4] = '\0';+	return path;+}++void+usage(void)+{+	fputs("usage: hedge [-a acl] [-r root] id\n", stderr);+	exit(1);+}++int+validname(const char *s)+{+	unsigned char c;+	int start = 1;++	if (!*s)+		return 0;+	for (; *s; s++) {+		c = *s;+		if (c == '/') {+			if (start)+				return 0;+			start = 1;+			continue;+		}+		if (start && !(isalnum(c) || c == '_'))+			return 0;+		if (!(isalnum(c) || c == '.' || c == '_' || c == '-'))+			return 0;+		start = 0;+	}+	return !start;+}++int+main(int argc, char *argv[])+{+	char cmd[PATH_MAX + 64], arg[2 * PATH_MAX];+	const char *s;+	char *path;+	size_t i;+	int c;++	while ((c = getopt(argc, argv, "a:r:")) != -1) {+		switch (c) {+		case 'a':+			acl = optarg;+			break;+		case 'r':+			root = optarg;+			break;+		default:+			usage();+		}+	}+	if (argc - optind != 1)+		usage();+	id = argv[optind];++	if (!(s = getenv("SSH_ORIGINAL_COMMAND")))+		s = "";+	if (bprintf(cmd, sizeof(cmd), "%s", s) < 0)+		deny();+	if ((path = strchr(cmd, ' ')))+		*path++ = '\0';+	for (i = 0; i < LENGTH(verbs) && strcmp(cmd, verbs[i].verb); i++)+		;+	if (i == LENGTH(verbs)) {+		fprintf(stderr, "hedge: hi %s, shell access is not provided\n", id);+		return 1;+	}+	if (!path)+		deny();+	path = trim(path);+	if (!validname(path) || !allowed(path, verbs[i].perm))+		deny();+	if (bprintf(arg, sizeof(arg), "%s '%s/%s.git'", verbs[i].verb, root,+	            path) < 0)+		deny();+	execlp(gitshell, "git-shell", "-c", arg, (char *)NULL);+	fprintf(stderr, "hedge: %s: %s\n", gitshell, strerror(errno));+	return 1;+}