greenhouse

greenhouse

minimal software forge

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

greenhouse / hedge / hedge.c188 lines · 3581B · raw

1/* imports */
2#include <ctype.h>
3#include <errno.h>
4#include <limits.h>
5#include <stdarg.h>
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9#include <unistd.h>
10
11/* macros */
12#define LENGTH(X) (sizeof(X) / sizeof((X)[0]))
13
14/* enums */
15typedef struct {
16	const char *verb;
17	const char *perm;
18} Verb;
19
20/* function declarations */
21static int allowed(const char *path, const char *perm);
22static int bprintf(char *buf, size_t len, const char *fmt, ...);
23static void deny(void);
24static char *trim(char *path);
25static void usage(void);
26static int validname(const char *s);
27
28/* variables */
29static const Verb verbs[] = {
30	{ "git-upload-pack",    "read"  },
31	{ "git-upload-archive", "read"  },
32	{ "git-receive-pack",   "write" },
33};
34static const char *id;
35
36#include "config.h"
37
38/* function implementations */
39int
40allowed(const char *path, const char *perm)
41{
42	char line[PATH_MAX + 64], w[PATH_MAX + 64], r[PATH_MAX + 64];
43	FILE *fp;
44	size_t n;
45	int c, ok = 0;
46
47	if (bprintf(w, sizeof(w), "%s write %s", path, id) < 0
48	|| bprintf(r, sizeof(r), "%s read %s", path, id) < 0)
49		return 0;
50	if (!(fp = fopen(acl, "r"))) {
51		fprintf(stderr, "hedge: %s: %s\n", acl, strerror(errno));
52		return 0;
53	}
54	while (!ok && fgets(line, sizeof(line), fp)) {
55		n = strcspn(line, "\n");
56		if (!line[n] && !feof(fp)) {
57			while ((c = fgetc(fp)) != EOF && c != '\n')
58				;
59			continue;
60		}
61		line[n] = '\0';
62		ok = !strcmp(line, w)
63		  || (!strcmp(perm, "read") && !strcmp(line, r));
64	}
65	fclose(fp);
66	return ok;
67}
68
69int
70bprintf(char *buf, size_t len, const char *fmt, ...)
71{
72	va_list ap;
73	int n;
74
75	va_start(ap, fmt);
76	n = vsnprintf(buf, len, fmt, ap);
77	va_end(ap);
78	if (n < 0 || (size_t)n >= len)
79		return -1;
80	return 0;
81}
82
83void
84deny(void)
85{
86	fputs("hedge: access denied\n", stderr);
87	exit(1);
88}
89
90char *
91trim(char *path)
92{
93	size_t n;
94
95	if (*path == '\'')
96		path++;
97	if ((n = strlen(path)) && path[n - 1] == '\'')
98		path[--n] = '\0';
99	if (*path == '/')
100		path++;
101	if ((n = strlen(path)) && path[n - 1] == '/')
102		path[--n] = '\0';
103	if (n >= 4 && !strcmp(path + n - 4, ".git"))
104		path[n - 4] = '\0';
105	return path;
106}
107
108void
109usage(void)
110{
111	fputs("usage: hedge [-a acl] [-r root] id\n", stderr);
112	exit(1);
113}
114
115int
116validname(const char *s)
117{
118	unsigned char c;
119	int start = 1;
120
121	if (!*s)
122		return 0;
123	for (; *s; s++) {
124		c = *s;
125		if (c == '/') {
126			if (start)
127				return 0;
128			start = 1;
129			continue;
130		}
131		if (start && !(isalnum(c) || c == '_'))
132			return 0;
133		if (!(isalnum(c) || c == '.' || c == '_' || c == '-'))
134			return 0;
135		start = 0;
136	}
137	return !start;
138}
139
140int
141main(int argc, char *argv[])
142{
143	char cmd[PATH_MAX + 64], arg[2 * PATH_MAX];
144	const char *s;
145	char *path;
146	size_t i;
147	int c;
148
149	while ((c = getopt(argc, argv, "a:r:")) != -1) {
150		switch (c) {
151		case 'a':
152			acl = optarg;
153			break;
154		case 'r':
155			root = optarg;
156			break;
157		default:
158			usage();
159		}
160	}
161	if (argc - optind != 1)
162		usage();
163	id = argv[optind];
164
165	if (!(s = getenv("SSH_ORIGINAL_COMMAND")))
166		s = "";
167	if (bprintf(cmd, sizeof(cmd), "%s", s) < 0)
168		deny();
169	if ((path = strchr(cmd, ' ')))
170		*path++ = '\0';
171	for (i = 0; i < LENGTH(verbs) && strcmp(cmd, verbs[i].verb); i++)
172		;
173	if (i == LENGTH(verbs)) {
174		fprintf(stderr, "hedge: hi %s, shell access is not provided\n", id);
175		return 1;
176	}
177	if (!path)
178		deny();
179	path = trim(path);
180	if (!validname(path) || !allowed(path, verbs[i].perm))
181		deny();
182	if (bprintf(arg, sizeof(arg), "%s '%s/%s.git'", verbs[i].verb, root,
183	            path) < 0)
184		deny();
185	execlp(gitshell, "git-shell", "-c", arg, (char *)NULL);
186	fprintf(stderr, "hedge: %s: %s\n", gitshell, strerror(errno));
187	return 1;
188}