greenhouse

greenhouse

minimal software forge

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

greenhouse / hedge / hedge.c189 lines · 3592B · 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
140/* main */
141int
142main(int argc, char *argv[])
143{
144	char cmd[PATH_MAX + 64], arg[2 * PATH_MAX];
145	const char *s;
146	char *path;
147	size_t i;
148	int c;
149
150	while ((c = getopt(argc, argv, "a:r:")) != -1) {
151		switch (c) {
152		case 'a':
153			acl = optarg;
154			break;
155		case 'r':
156			root = optarg;
157			break;
158		default:
159			usage();
160		}
161	}
162	if (argc - optind != 1)
163		usage();
164	id = argv[optind];
165
166	if (!(s = getenv("SSH_ORIGINAL_COMMAND")))
167		s = "";
168	if (bprintf(cmd, sizeof(cmd), "%s", s) < 0)
169		deny();
170	if ((path = strchr(cmd, ' ')))
171		*path++ = '\0';
172	for (i = 0; i < LENGTH(verbs) && strcmp(cmd, verbs[i].verb); i++)
173		;
174	if (i == LENGTH(verbs)) {
175		fprintf(stderr, "hedge: hi %s, shell access is not provided\n", id);
176		return 1;
177	}
178	if (!path)
179		deny();
180	path = trim(path);
181	if (!validname(path) || !allowed(path, verbs[i].perm))
182		deny();
183	if (bprintf(arg, sizeof(arg), "%s '%s/%s.git'", verbs[i].verb, root,
184	            path) < 0)
185		deny();
186	execlp(gitshell, "git-shell", "-c", arg, (char *)NULL);
187	fprintf(stderr, "hedge: %s: %s\n", gitshell, strerror(errno));
188	return 1;
189}