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}