feat(hedge): port kore-git to hedge
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;+}