/* imports */ #include #include #include #include #include #include #include #include /* 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; }