2 * Copyright (c) 2022 Stefan Sperling <stsp@openbsd.org>
3 * Copyright (c) 2016-2019, 2020-2021 Tracey Emery <tracey@traceyemery.net>
4 * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
5 * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
6 * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
7 * Copyright (c) 2001 Markus Friedl. All rights reserved.
8 * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
9 * Copyright (c) 2001 Theo de Raadt. All rights reserved.
11 * Permission to use, copy, modify, and distribute this software for any
12 * purpose with or without fee is hereby granted, provided that the above
13 * copyright notice and this permission notice appear in all copies.
15 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
16 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
18 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
21 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
26 #include <sys/types.h>
27 #include <sys/queue.h>
46 #include "got_error.h"
48 #include "got_reference.h"
55 TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
57 TAILQ_ENTRY(file) entry;
63 struct file *newfile(const char *, int, int);
64 static void closefile(struct file *);
65 int check_file_secrecy(int, const char *);
68 int yyerror(const char *, ...)
69 __attribute__((__format__ (printf, 1, 2)))
70 __attribute__((__nonnull__ (1)));
71 int kw_cmp(const void *, const void *);
77 TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
79 TAILQ_ENTRY(sym) entry;
86 int symset(const char *, const char *, int);
87 char *symget(const char *);
91 static struct gotd *gotd;
92 static struct gotd_repo *new_repo;
93 static int conf_limit_user_connections(const char *, int);
94 static struct gotd_repo *conf_new_repo(const char *);
95 static void conf_new_access_rule(struct gotd_repo *,
96 enum gotd_access, int, char *);
97 static int conf_protect_ref_namespace(char **,
98 struct got_pathlist_head *, char *);
99 static int conf_protect_tag_namespace(struct gotd_repo *,
101 static int conf_protect_branch_namespace(
102 struct gotd_repo *, char *);
103 static int conf_protect_branch(struct gotd_repo *,
105 static enum gotd_procid gotd_proc_id;
118 %token PATH ERROR LISTEN ON USER REPOSITORY PERMIT DENY
119 %token RO RW CONNECTION LIMIT REQUEST TIMEOUT
120 %token PROTECT NAMESPACE BRANCH TAG
122 %token <v.string> STRING
123 %token <v.number> NUMBER
130 | grammar varset '\n'
132 | grammar repository '\n'
135 varset : STRING '=' STRING {
138 if (isspace((unsigned char)*s)) {
139 yyerror("macro name cannot contain "
146 if (symset($1, $3, 0) == -1)
147 fatal("cannot store variable");
155 yyerror("invalid timeout: %lld", $1);
163 const char *type = "seconds";
168 yyerror("invalid number of seconds: %s", $1);
174 switch ($1[len - 1]) {
194 $$.tv_sec = strtonum($1, 0, INT_MAX / mul, &errstr);
196 yyerror("number of %s is %s: %s", type,
207 main : LISTEN ON STRING {
208 if (!got_path_is_absolute($3))
209 yyerror("bad unix socket path \"%s\": "
210 "must be an absolute path", $3);
212 if (gotd_proc_id == PROC_LISTEN) {
213 if (strlcpy(gotd->unix_socket_path, $3,
214 sizeof(gotd->unix_socket_path)) >=
215 sizeof(gotd->unix_socket_path)) {
216 yyerror("%s: unix socket path too long",
225 if (strlcpy(gotd->user_name, $2,
226 sizeof(gotd->user_name)) >=
227 sizeof(gotd->user_name)) {
228 yyerror("%s: user name too long", __func__);
237 connection : CONNECTION '{' optnl conflags_l '}'
238 | CONNECTION conflags
240 conflags_l : conflags optnl conflags_l
244 conflags : REQUEST TIMEOUT timeout {
245 if ($3.tv_sec <= 0) {
246 yyerror("invalid timeout: %lld", $3.tv_sec);
249 memcpy(&gotd->request_timeout, &$3,
250 sizeof(gotd->request_timeout));
252 | LIMIT USER STRING NUMBER {
253 if (gotd_proc_id == PROC_LISTEN &&
254 conf_limit_user_connections($3, $4) == -1) {
262 protect : PROTECT '{' optnl protectflags_l '}'
263 | PROTECT protectflags
265 protectflags_l : protectflags optnl protectflags_l
269 protectflags : TAG NAMESPACE STRING {
270 if (gotd_proc_id == PROC_GOTD ||
271 gotd_proc_id == PROC_REPO_WRITE) {
272 if (conf_protect_tag_namespace(new_repo, $3)) {
279 | BRANCH NAMESPACE STRING {
280 if (gotd_proc_id == PROC_GOTD ||
281 gotd_proc_id == PROC_REPO_WRITE) {
282 if (conf_protect_branch_namespace(new_repo,
291 if (gotd_proc_id == PROC_GOTD ||
292 gotd_proc_id == PROC_REPO_WRITE) {
293 if (conf_protect_branch(new_repo, $2)) {
302 repository : REPOSITORY STRING {
303 struct gotd_repo *repo;
305 TAILQ_FOREACH(repo, &gotd->repos, entry) {
306 if (strcmp(repo->name, $2) == 0) {
307 yyerror("duplicate repository '%s'", $2);
313 if (gotd_proc_id == PROC_GOTD ||
314 gotd_proc_id == PROC_AUTH ||
315 gotd_proc_id == PROC_REPO_WRITE ||
316 gotd_proc_id == PROC_GITWRAPPER) {
317 new_repo = conf_new_repo($2);
320 } '{' optnl repoopts2 '}' {
324 repoopts1 : PATH STRING {
325 if (gotd_proc_id == PROC_GOTD ||
326 gotd_proc_id == PROC_AUTH ||
327 gotd_proc_id == PROC_REPO_WRITE ||
328 gotd_proc_id == PROC_GITWRAPPER) {
329 if (!got_path_is_absolute($2)) {
330 yyerror("%s: path %s is not absolute",
335 if (realpath($2, new_repo->path) == NULL) {
337 * To give admins a chance to create
338 * missing repositories at run-time
339 * we only warn about ENOENT here.
341 * And ignore 'permission denied' when
342 * running in gitwrapper. Users may be
343 * able to access this repository via
346 if (errno == ENOENT) {
347 yyerror("realpath %s: %s", $2,
349 } else if (errno != EACCES ||
350 gotd_proc_id != PROC_GITWRAPPER) {
351 yyerror("realpath %s: %s", $2,
357 if (strlcpy(new_repo->path, $2,
358 sizeof(new_repo->path)) >=
359 sizeof(new_repo->path))
360 yyerror("path too long");
366 if (gotd_proc_id == PROC_AUTH) {
367 conf_new_access_rule(new_repo,
368 GOTD_ACCESS_PERMITTED, GOTD_AUTH_READ, $3);
373 if (gotd_proc_id == PROC_AUTH) {
374 conf_new_access_rule(new_repo,
375 GOTD_ACCESS_PERMITTED,
376 GOTD_AUTH_READ | GOTD_AUTH_WRITE, $3);
381 if (gotd_proc_id == PROC_AUTH) {
382 conf_new_access_rule(new_repo,
383 GOTD_ACCESS_DENIED, 0, $2);
390 repoopts2 : repoopts2 repoopts1 nl
397 optnl : '\n' optnl /* zero or more newlines */
409 yyerror(const char *fmt, ...)
416 if (vasprintf(&msg, fmt, ap) == -1)
417 fatalx("yyerror vasprintf");
419 logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
425 kw_cmp(const void *k, const void *e)
427 return (strcmp(k, ((const struct keywords *)e)->k_name));
433 /* This has to be sorted always. */
434 static const struct keywords keywords[] = {
435 { "branch", BRANCH },
436 { "connection", CONNECTION },
439 { "listen", LISTEN },
440 { "namespace", NAMESPACE },
443 { "permit", PERMIT },
444 { "protect", PROTECT },
445 { "repository", REPOSITORY },
446 { "request", REQUEST },
450 { "timeout", TIMEOUT },
453 const struct keywords *p;
455 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
456 sizeof(keywords[0]), kw_cmp);
464 #define MAXPUSHBACK 128
466 unsigned char *parsebuf;
468 unsigned char pushback_buffer[MAXPUSHBACK];
469 int pushback_index = 0;
477 /* Read character from the parsebuffer instead of input. */
478 if (parseindex >= 0) {
479 c = parsebuf[parseindex++];
488 return (pushback_buffer[--pushback_index]);
491 c = getc(file->stream);
493 yyerror("reached end of file while parsing "
498 c = getc(file->stream);
500 next = getc(file->stream);
505 yylval.lineno = file->lineno;
507 c = getc(file->stream);
523 if (pushback_index < MAXPUSHBACK-1)
524 return (pushback_buffer[pushback_index++] = c);
536 /* Skip to either EOF or the first real EOL. */
539 c = pushback_buffer[--pushback_index];
555 unsigned char buf[8096];
556 unsigned char *p, *val;
563 while (c == ' ' || c == '\t')
564 c = lgetc(0); /* nothing */
566 yylval.lineno = file->lineno;
569 while (c != '\n' && c != EOF)
570 c = lgetc(0); /* nothing */
572 if (c == '$' && parsebuf == NULL) {
578 if (p + 1 >= buf + sizeof(buf) - 1) {
579 yyerror("string too long");
582 if (isalnum(c) || c == '_') {
592 yyerror("macro '%s' not defined", buf);
611 } else if (c == '\\') {
612 next = lgetc(quotec);
615 if (next == quotec || c == ' ' || c == '\t')
617 else if (next == '\n') {
622 } else if (c == quotec) {
625 } else if (c == '\0') {
626 yyerror("syntax error");
629 if (p + 1 >= buf + sizeof(buf) - 1) {
630 yyerror("string too long");
635 yylval.v.string = strdup(buf);
636 if (yylval.v.string == NULL)
637 err(1, "yylex: strdup");
641 #define allowed_to_end_number(x) \
642 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
644 if (c == '-' || isdigit(c)) {
647 if ((unsigned)(p-buf) >= sizeof(buf)) {
648 yyerror("string too long");
652 } while (c != EOF && isdigit(c));
654 if (p == buf + 1 && buf[0] == '-')
656 if (c == EOF || allowed_to_end_number(c)) {
657 const char *errstr = NULL;
660 yylval.v.number = strtonum(buf, LLONG_MIN,
663 yyerror("\"%s\" invalid number: %s",
678 #define allowed_in_string(x) \
679 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
680 x != '{' && x != '}' && \
681 x != '!' && x != '=' && x != '#' && \
684 if (isalnum(c) || c == ':' || c == '_') {
687 if ((unsigned)(p-buf) >= sizeof(buf)) {
688 yyerror("string too long");
692 } while (c != EOF && (allowed_in_string(c)));
696 if (token == STRING) {
697 yylval.v.string = strdup(buf);
698 if (yylval.v.string == NULL)
699 err(1, "yylex: strdup");
704 yylval.lineno = file->lineno;
713 check_file_secrecy(int fd, const char *fname)
717 if (fstat(fd, &st)) {
718 log_warn("cannot stat %s", fname);
721 if (st.st_uid != 0 && st.st_uid != getuid()) {
722 log_warnx("%s: owner not root or current user", fname);
725 if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
726 log_warnx("%s: group writable or world read/writable", fname);
733 newfile(const char *name, int secret, int required)
737 nfile = calloc(1, sizeof(struct file));
742 nfile->name = strdup(name);
743 if (nfile->name == NULL) {
748 nfile->stream = fopen(nfile->name, "r");
749 if (nfile->stream == NULL) {
751 log_warn("open %s", nfile->name);
756 check_file_secrecy(fileno(nfile->stream), nfile->name)) {
757 fclose(nfile->stream);
767 closefile(struct file *xfile)
769 fclose(xfile->stream);
775 parse_config(const char *filename, enum gotd_procid proc_id,
778 struct sym *sym, *next;
779 struct gotd_repo *repo;
780 int require_config_file = (proc_id != PROC_GITWRAPPER);
782 memset(env, 0, sizeof(*env));
785 gotd_proc_id = proc_id;
786 TAILQ_INIT(&gotd->repos);
788 /* Apply default values. */
789 if (strlcpy(gotd->unix_socket_path, GOTD_UNIX_SOCKET,
790 sizeof(gotd->unix_socket_path)) >= sizeof(gotd->unix_socket_path)) {
791 fprintf(stderr, "%s: unix socket path too long", __func__);
794 if (strlcpy(gotd->user_name, GOTD_USER,
795 sizeof(gotd->user_name)) >= sizeof(gotd->user_name)) {
796 fprintf(stderr, "%s: user name too long", __func__);
800 gotd->request_timeout.tv_sec = GOTD_DEFAULT_REQUEST_TIMEOUT;
801 gotd->request_timeout.tv_usec = 0;
803 file = newfile(filename, 0, require_config_file);
805 return require_config_file ? -1 : 0;
808 errors = file->errors;
811 /* Free macros and check which have not been used. */
812 TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
813 if ((gotd->verbosity > 1) && !sym->used)
814 fprintf(stderr, "warning: macro '%s' not used\n",
819 TAILQ_REMOVE(&symhead, sym, entry);
827 TAILQ_FOREACH(repo, &gotd->repos, entry) {
828 if (repo->path[0] == '\0') {
829 log_warnx("repository \"%s\": no path provided in "
830 "configuration file", repo->name);
835 if (proc_id == PROC_GOTD && TAILQ_EMPTY(&gotd->repos)) {
836 log_warnx("no repository defined in configuration file");
844 uid_connection_limit_cmp(const void *pa, const void *pb)
846 const struct gotd_uid_connection_limit *a = pa, *b = pb;
850 else if (a->uid > b->uid);
857 conf_limit_user_connections(const char *user, int maximum)
860 struct gotd_uid_connection_limit *limit;
864 yyerror("max connections cannot be smaller 1");
867 if (maximum > GOTD_MAXCLIENTS) {
868 yyerror("max connections must be <= %d", GOTD_MAXCLIENTS);
872 if (gotd_parseuid(user, &uid) == -1) {
873 yyerror("%s: no such user", user);
877 limit = gotd_find_uid_connection_limit(gotd->connection_limits,
878 gotd->nconnection_limits, uid);
880 limit->max_connections = maximum;
884 limit = gotd->connection_limits;
885 nlimits = gotd->nconnection_limits + 1;
886 limit = reallocarray(limit, nlimits, sizeof(*limit));
888 fatal("reallocarray");
890 limit[nlimits - 1].uid = uid;
891 limit[nlimits - 1].max_connections = maximum;
893 gotd->connection_limits = limit;
894 gotd->nconnection_limits = nlimits;
895 qsort(gotd->connection_limits, gotd->nconnection_limits,
896 sizeof(gotd->connection_limits[0]), uid_connection_limit_cmp);
901 static struct gotd_repo *
902 conf_new_repo(const char *name)
904 struct gotd_repo *repo;
906 if (name[0] == '\0') {
907 fatalx("syntax error: empty repository name found in %s",
911 if (strchr(name, '\n') != NULL)
912 fatalx("repository names must not contain linefeeds: %s", name);
914 repo = calloc(1, sizeof(*repo));
916 fatalx("%s: calloc", __func__);
918 STAILQ_INIT(&repo->rules);
919 TAILQ_INIT(&repo->protected_tag_namespaces);
920 TAILQ_INIT(&repo->protected_branch_namespaces);
921 TAILQ_INIT(&repo->protected_branches);
923 if (strlcpy(repo->name, name, sizeof(repo->name)) >=
925 fatalx("%s: strlcpy", __func__);
927 TAILQ_INSERT_TAIL(&gotd->repos, repo, entry);
934 conf_new_access_rule(struct gotd_repo *repo, enum gotd_access access,
935 int authorization, char *identifier)
937 struct gotd_access_rule *rule;
939 rule = calloc(1, sizeof(*rule));
943 rule->access = access;
944 rule->authorization = authorization;
945 rule->identifier = identifier;
947 STAILQ_INSERT_TAIL(&repo->rules, rule, entry);
951 refname_is_valid(char *refname)
953 if (strncmp(refname, "refs/", 5) != 0) {
954 yyerror("reference name must begin with \"refs/\": %s",
959 if (!got_ref_name_is_valid(refname)) {
960 yyerror("invalid reference name: %s", refname);
968 conf_protect_ref_namespace(char **new, struct got_pathlist_head *refs,
971 const struct got_error *error;
972 struct got_pathlist_entry *pe;
977 got_path_strip_trailing_slashes(namespace);
978 if (!refname_is_valid(namespace))
980 if (asprintf(&s, "%s/", namespace) == -1) {
981 yyerror("asprintf: %s", strerror(errno));
985 error = got_pathlist_insert(&pe, refs, s, NULL);
986 if (error || pe == NULL) {
989 yyerror("got_pathlist_insert: %s", error->msg);
991 yyerror("duplicate protected namespace %s", namespace);
1000 conf_protect_tag_namespace(struct gotd_repo *repo, char *namespace)
1002 struct got_pathlist_entry *pe;
1005 if (conf_protect_ref_namespace(&new, &repo->protected_tag_namespaces,
1009 TAILQ_FOREACH(pe, &repo->protected_branch_namespaces, entry) {
1010 if (strcmp(pe->path, new) == 0) {
1011 yyerror("duplicate protected namespace %s", namespace);
1020 conf_protect_branch_namespace(struct gotd_repo *repo, char *namespace)
1022 struct got_pathlist_entry *pe;
1025 if (conf_protect_ref_namespace(&new,
1026 &repo->protected_branch_namespaces, namespace) == -1)
1029 TAILQ_FOREACH(pe, &repo->protected_tag_namespaces, entry) {
1030 if (strcmp(pe->path, new) == 0) {
1031 yyerror("duplicate protected namespace %s", namespace);
1040 conf_protect_branch(struct gotd_repo *repo, char *branchname)
1042 const struct got_error *error;
1043 struct got_pathlist_entry *new;
1046 if (strncmp(branchname, "refs/heads/", 11) != 0) {
1047 if (asprintf(&refname, "refs/heads/%s", branchname) == -1) {
1048 yyerror("asprintf: %s", strerror(errno));
1052 refname = strdup(branchname);
1053 if (refname == NULL) {
1054 yyerror("strdup: %s", strerror(errno));
1059 if (!refname_is_valid(refname)) {
1064 error = got_pathlist_insert(&new, &repo->protected_branches,
1066 if (error || new == NULL) {
1069 yyerror("got_pathlist_insert: %s", error->msg);
1071 yyerror("duplicate protect branch %s", branchname);
1079 symset(const char *nam, const char *val, int persist)
1083 TAILQ_FOREACH(sym, &symhead, entry) {
1084 if (strcmp(nam, sym->nam) == 0)
1089 if (sym->persist == 1)
1094 TAILQ_REMOVE(&symhead, sym, entry);
1098 sym = calloc(1, sizeof(*sym));
1102 sym->nam = strdup(nam);
1103 if (sym->nam == NULL) {
1107 sym->val = strdup(val);
1108 if (sym->val == NULL) {
1114 sym->persist = persist;
1115 TAILQ_INSERT_TAIL(&symhead, sym, entry);
1120 symget(const char *nam)
1124 TAILQ_FOREACH(sym, &symhead, entry) {
1125 if (strcmp(nam, sym->nam) == 0) {
1134 gotd_find_repo_by_name(const char *repo_name, struct gotd *gotd)
1136 struct gotd_repo *repo;
1139 TAILQ_FOREACH(repo, &gotd->repos, entry) {
1140 namelen = strlen(repo->name);
1141 if (strncmp(repo->name, repo_name, namelen) != 0)
1143 if (repo_name[namelen] == '\0' ||
1144 strcmp(&repo_name[namelen], ".git") == 0)
1152 gotd_find_repo_by_path(const char *repo_path, struct gotd *gotd)
1154 struct gotd_repo *repo;
1156 TAILQ_FOREACH(repo, &gotd->repos, entry) {
1157 if (strcmp(repo->path, repo_path) == 0)
1164 struct gotd_uid_connection_limit *
1165 gotd_find_uid_connection_limit(struct gotd_uid_connection_limit *limits,
1166 size_t nlimits, uid_t uid)
1168 /* This array is always sorted to allow for binary search. */
1169 int i, left = 0, right = nlimits - 1;
1171 while (left <= right) {
1172 i = ((left + right) / 2);
1173 if (limits[i].uid == uid)
1175 if (limits[i].uid > uid)
1185 gotd_parseuid(const char *s, uid_t *uid)
1190 if ((pw = getpwnam(s)) != NULL) {
1192 if (*uid == UID_MAX)
1196 *uid = strtonum(s, 0, UID_MAX - 1, &errstr);