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 *);
76 static char *port_sprintf(int);
78 TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
80 TAILQ_ENTRY(sym) entry;
87 int symset(const char *, const char *, int);
88 char *symget(const char *);
92 static struct gotd *gotd;
93 static struct gotd_repo *new_repo;
94 static int conf_limit_user_connections(const char *, int);
95 static struct gotd_repo *conf_new_repo(const char *);
96 static void conf_new_access_rule(struct gotd_repo *,
97 enum gotd_access, int, char *);
98 static int conf_protect_ref_namespace(char **,
99 struct got_pathlist_head *, char *);
100 static int conf_protect_tag_namespace(struct gotd_repo *,
102 static int conf_protect_branch_namespace(
103 struct gotd_repo *, char *);
104 static int conf_protect_branch(struct gotd_repo *,
106 static int conf_notify_branch(struct gotd_repo *,
108 static int conf_notify_ref_namespace(struct gotd_repo *,
110 static int conf_notify_email(struct gotd_repo *,
111 char *, char *, char *, char *, char *);
112 static int conf_notify_http(struct gotd_repo *,
113 char *, char *, char *);
114 static enum gotd_procid gotd_proc_id;
127 %token PATH ERROR LISTEN ON USER REPOSITORY PERMIT DENY
128 %token RO RW CONNECTION LIMIT REQUEST TIMEOUT
129 %token PROTECT NAMESPACE BRANCH TAG REFERENCE RELAY PORT
130 %token NOTIFY EMAIL FROM REPLY TO URL PASSWORD
132 %token <v.string> STRING
133 %token <v.number> NUMBER
140 | grammar varset '\n'
142 | grammar repository '\n'
145 varset : STRING '=' STRING {
148 if (isspace((unsigned char)*s)) {
149 yyerror("macro name cannot contain "
156 if (symset($1, $3, 0) == -1)
157 fatal("cannot store variable");
165 yyerror("invalid timeout: %lld", $1);
173 const char *type = "seconds";
178 yyerror("invalid number of seconds: %s", $1);
184 switch ($1[len - 1]) {
204 $$.tv_sec = strtonum($1, 0, INT_MAX / mul, &errstr);
206 yyerror("number of %s is %s: %s", type,
217 main : LISTEN ON STRING {
218 if (!got_path_is_absolute($3))
219 yyerror("bad unix socket path \"%s\": "
220 "must be an absolute path", $3);
222 if (gotd_proc_id == PROC_LISTEN) {
223 if (strlcpy(gotd->unix_socket_path, $3,
224 sizeof(gotd->unix_socket_path)) >=
225 sizeof(gotd->unix_socket_path)) {
226 yyerror("%s: unix socket path too long",
235 if (strlcpy(gotd->user_name, $2,
236 sizeof(gotd->user_name)) >=
237 sizeof(gotd->user_name)) {
238 yyerror("%s: user name too long", __func__);
247 connection : CONNECTION '{' optnl conflags_l '}'
248 | CONNECTION conflags
250 conflags_l : conflags optnl conflags_l
254 conflags : REQUEST TIMEOUT timeout {
255 if ($3.tv_sec <= 0) {
256 yyerror("invalid timeout: %lld", $3.tv_sec);
259 memcpy(&gotd->request_timeout, &$3,
260 sizeof(gotd->request_timeout));
262 | LIMIT USER STRING NUMBER {
263 if (gotd_proc_id == PROC_LISTEN &&
264 conf_limit_user_connections($3, $4) == -1) {
272 protect : PROTECT '{' optnl protectflags_l '}'
273 | PROTECT protectflags
275 protectflags_l : protectflags optnl protectflags_l
279 protectflags : TAG NAMESPACE STRING {
280 if (gotd_proc_id == PROC_GOTD ||
281 gotd_proc_id == PROC_REPO_WRITE) {
282 if (conf_protect_tag_namespace(new_repo, $3)) {
289 | BRANCH NAMESPACE STRING {
290 if (gotd_proc_id == PROC_GOTD ||
291 gotd_proc_id == PROC_REPO_WRITE) {
292 if (conf_protect_branch_namespace(new_repo,
301 if (gotd_proc_id == PROC_GOTD ||
302 gotd_proc_id == PROC_REPO_WRITE) {
303 if (conf_protect_branch(new_repo, $2)) {
312 notify : NOTIFY '{' optnl notifyflags_l '}'
315 notifyflags_l : notifyflags optnl notifyflags_l
319 notifyflags : BRANCH STRING {
320 if (gotd_proc_id == PROC_GOTD ||
321 gotd_proc_id == PROC_SESSION_WRITE ||
322 gotd_proc_id == PROC_NOTIFY) {
323 if (conf_notify_branch(new_repo, $2)) {
330 | REFERENCE NAMESPACE STRING {
331 if (gotd_proc_id == PROC_GOTD ||
332 gotd_proc_id == PROC_SESSION_WRITE ||
333 gotd_proc_id == PROC_NOTIFY) {
334 if (conf_notify_ref_namespace(new_repo, $3)) {
342 if (gotd_proc_id == PROC_GOTD ||
343 gotd_proc_id == PROC_SESSION_WRITE ||
344 gotd_proc_id == PROC_NOTIFY) {
345 if (conf_notify_email(new_repo, NULL, $3,
353 | EMAIL FROM STRING TO STRING {
354 if (gotd_proc_id == PROC_GOTD ||
355 gotd_proc_id == PROC_SESSION_WRITE ||
356 gotd_proc_id == PROC_NOTIFY) {
357 if (conf_notify_email(new_repo, $3, $5,
367 | EMAIL TO STRING REPLY TO STRING {
368 if (gotd_proc_id == PROC_GOTD ||
369 gotd_proc_id == PROC_SESSION_WRITE ||
370 gotd_proc_id == PROC_NOTIFY) {
371 if (conf_notify_email(new_repo, NULL, $3,
381 | EMAIL FROM STRING TO STRING REPLY TO STRING {
382 if (gotd_proc_id == PROC_GOTD ||
383 gotd_proc_id == PROC_SESSION_WRITE ||
384 gotd_proc_id == PROC_NOTIFY) {
385 if (conf_notify_email(new_repo, $3, $5,
397 | EMAIL TO STRING RELAY STRING {
398 if (gotd_proc_id == PROC_GOTD ||
399 gotd_proc_id == PROC_SESSION_WRITE ||
400 gotd_proc_id == PROC_NOTIFY) {
401 if (conf_notify_email(new_repo, NULL, $3,
411 | EMAIL FROM STRING TO STRING RELAY STRING {
412 if (gotd_proc_id == PROC_GOTD ||
413 gotd_proc_id == PROC_SESSION_WRITE ||
414 gotd_proc_id == PROC_NOTIFY) {
415 if (conf_notify_email(new_repo, $3, $5,
427 | EMAIL TO STRING REPLY TO STRING RELAY STRING {
428 if (gotd_proc_id == PROC_GOTD ||
429 gotd_proc_id == PROC_SESSION_WRITE ||
430 gotd_proc_id == PROC_NOTIFY) {
431 if (conf_notify_email(new_repo, NULL, $3,
443 | EMAIL FROM STRING TO STRING REPLY TO STRING RELAY STRING {
444 if (gotd_proc_id == PROC_GOTD ||
445 gotd_proc_id == PROC_SESSION_WRITE ||
446 gotd_proc_id == PROC_NOTIFY) {
447 if (conf_notify_email(new_repo, $3, $5,
461 | EMAIL TO STRING RELAY STRING PORT STRING {
462 if (gotd_proc_id == PROC_GOTD ||
463 gotd_proc_id == PROC_SESSION_WRITE ||
464 gotd_proc_id == PROC_NOTIFY) {
465 if (conf_notify_email(new_repo, NULL, $3,
477 | EMAIL FROM STRING TO STRING RELAY STRING PORT STRING {
478 if (gotd_proc_id == PROC_GOTD ||
479 gotd_proc_id == PROC_SESSION_WRITE ||
480 gotd_proc_id == PROC_NOTIFY) {
481 if (conf_notify_email(new_repo, $3, $5,
495 | EMAIL TO STRING REPLY TO STRING RELAY STRING PORT STRING {
496 if (gotd_proc_id == PROC_GOTD ||
497 gotd_proc_id == PROC_SESSION_WRITE ||
498 gotd_proc_id == PROC_NOTIFY) {
499 if (conf_notify_email(new_repo, NULL, $3,
513 | EMAIL FROM STRING TO STRING REPLY TO STRING RELAY STRING PORT STRING {
514 if (gotd_proc_id == PROC_GOTD ||
515 gotd_proc_id == PROC_SESSION_WRITE ||
516 gotd_proc_id == PROC_NOTIFY) {
517 if (conf_notify_email(new_repo, $3, $5,
533 | EMAIL TO STRING RELAY STRING PORT NUMBER {
534 if (gotd_proc_id == PROC_GOTD ||
535 gotd_proc_id == PROC_SESSION_WRITE ||
536 gotd_proc_id == PROC_NOTIFY) {
537 if (conf_notify_email(new_repo, NULL, $3,
538 NULL, $5, port_sprintf($7))) {
547 | EMAIL FROM STRING TO STRING RELAY STRING PORT NUMBER {
548 if (gotd_proc_id == PROC_GOTD ||
549 gotd_proc_id == PROC_SESSION_WRITE ||
550 gotd_proc_id == PROC_NOTIFY) {
551 if (conf_notify_email(new_repo, $3, $5,
552 NULL, $7, port_sprintf($9))) {
563 | EMAIL TO STRING REPLY TO STRING RELAY STRING PORT NUMBER {
564 if (gotd_proc_id == PROC_GOTD ||
565 gotd_proc_id == PROC_SESSION_WRITE ||
566 gotd_proc_id == PROC_NOTIFY) {
567 if (conf_notify_email(new_repo, NULL, $3,
568 $6, $8, port_sprintf($10))) {
579 | EMAIL FROM STRING TO STRING REPLY TO STRING RELAY STRING PORT NUMBER {
580 if (gotd_proc_id == PROC_GOTD ||
581 gotd_proc_id == PROC_SESSION_WRITE ||
582 gotd_proc_id == PROC_NOTIFY) {
583 if (conf_notify_email(new_repo, $3, $5,
584 $8, $10, port_sprintf($12))) {
598 if (gotd_proc_id == PROC_GOTD ||
599 gotd_proc_id == PROC_SESSION_WRITE ||
600 gotd_proc_id == PROC_NOTIFY) {
601 if (conf_notify_http(new_repo, $2, NULL,
609 | URL STRING USER STRING PASSWORD STRING {
610 if (gotd_proc_id == PROC_GOTD ||
611 gotd_proc_id == PROC_SESSION_WRITE ||
612 gotd_proc_id == PROC_NOTIFY) {
613 if (conf_notify_http(new_repo, $2, $4, $6)) {
626 repository : REPOSITORY STRING {
627 struct gotd_repo *repo;
629 TAILQ_FOREACH(repo, &gotd->repos, entry) {
630 if (strcmp(repo->name, $2) == 0) {
631 yyerror("duplicate repository '%s'", $2);
637 if (gotd_proc_id == PROC_GOTD ||
638 gotd_proc_id == PROC_AUTH ||
639 gotd_proc_id == PROC_REPO_WRITE ||
640 gotd_proc_id == PROC_SESSION_WRITE ||
641 gotd_proc_id == PROC_GITWRAPPER |
642 gotd_proc_id == PROC_NOTIFY) {
643 new_repo = conf_new_repo($2);
646 } '{' optnl repoopts2 '}' {
650 repoopts1 : PATH STRING {
651 if (gotd_proc_id == PROC_GOTD ||
652 gotd_proc_id == PROC_AUTH ||
653 gotd_proc_id == PROC_REPO_WRITE ||
654 gotd_proc_id == PROC_SESSION_WRITE ||
655 gotd_proc_id == PROC_GITWRAPPER ||
656 gotd_proc_id == PROC_NOTIFY) {
657 if (!got_path_is_absolute($2)) {
658 yyerror("%s: path %s is not absolute",
663 if (realpath($2, new_repo->path) == NULL) {
665 * To give admins a chance to create
666 * missing repositories at run-time
667 * we only warn about ENOENT here.
669 * And ignore 'permission denied' when
670 * running in gitwrapper. Users may be
671 * able to access this repository via
674 if (errno == ENOENT) {
675 yyerror("realpath %s: %s", $2,
677 } else if (errno != EACCES ||
678 gotd_proc_id != PROC_GITWRAPPER) {
679 yyerror("realpath %s: %s", $2,
685 if (strlcpy(new_repo->path, $2,
686 sizeof(new_repo->path)) >=
687 sizeof(new_repo->path))
688 yyerror("path too long");
694 if (gotd_proc_id == PROC_AUTH) {
695 conf_new_access_rule(new_repo,
696 GOTD_ACCESS_PERMITTED, GOTD_AUTH_READ, $3);
701 if (gotd_proc_id == PROC_AUTH) {
702 conf_new_access_rule(new_repo,
703 GOTD_ACCESS_PERMITTED,
704 GOTD_AUTH_READ | GOTD_AUTH_WRITE, $3);
709 if (gotd_proc_id == PROC_AUTH) {
710 conf_new_access_rule(new_repo,
711 GOTD_ACCESS_DENIED, 0, $2);
719 repoopts2 : repoopts2 repoopts1 nl
726 optnl : '\n' optnl /* zero or more newlines */
738 yyerror(const char *fmt, ...)
745 if (vasprintf(&msg, fmt, ap) == -1)
746 fatalx("yyerror vasprintf");
748 logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
754 kw_cmp(const void *k, const void *e)
756 return (strcmp(k, ((const struct keywords *)e)->k_name));
762 /* This has to be sorted always. */
763 static const struct keywords keywords[] = {
764 { "branch", BRANCH },
765 { "connection", CONNECTION },
770 { "listen", LISTEN },
771 { "namespace", NAMESPACE },
772 { "notify", NOTIFY },
774 { "password", PASSWORD },
776 { "permit", PERMIT },
778 { "protect", PROTECT },
779 { "reference", REFERENCE },
782 { "repository", REPOSITORY },
783 { "request", REQUEST },
787 { "timeout", TIMEOUT },
792 const struct keywords *p;
794 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
795 sizeof(keywords[0]), kw_cmp);
803 #define MAXPUSHBACK 128
805 unsigned char *parsebuf;
807 unsigned char pushback_buffer[MAXPUSHBACK];
808 int pushback_index = 0;
816 /* Read character from the parsebuffer instead of input. */
817 if (parseindex >= 0) {
818 c = parsebuf[parseindex++];
827 return (pushback_buffer[--pushback_index]);
830 c = getc(file->stream);
832 yyerror("reached end of file while parsing "
837 c = getc(file->stream);
839 next = getc(file->stream);
844 yylval.lineno = file->lineno;
846 c = getc(file->stream);
862 if (pushback_index < MAXPUSHBACK-1)
863 return (pushback_buffer[pushback_index++] = c);
875 /* Skip to either EOF or the first real EOL. */
878 c = pushback_buffer[--pushback_index];
894 unsigned char buf[8096];
895 unsigned char *p, *val;
902 while (c == ' ' || c == '\t')
903 c = lgetc(0); /* nothing */
905 yylval.lineno = file->lineno;
908 while (c != '\n' && c != EOF)
909 c = lgetc(0); /* nothing */
911 if (c == '$' && parsebuf == NULL) {
917 if (p + 1 >= buf + sizeof(buf) - 1) {
918 yyerror("string too long");
921 if (isalnum(c) || c == '_') {
931 yyerror("macro '%s' not defined", buf);
950 } else if (c == '\\') {
951 next = lgetc(quotec);
954 if (next == quotec || c == ' ' || c == '\t')
956 else if (next == '\n') {
961 } else if (c == quotec) {
964 } else if (c == '\0') {
965 yyerror("syntax error");
968 if (p + 1 >= buf + sizeof(buf) - 1) {
969 yyerror("string too long");
974 yylval.v.string = strdup(buf);
975 if (yylval.v.string == NULL)
976 err(1, "yylex: strdup");
980 #define allowed_to_end_number(x) \
981 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
983 if (c == '-' || isdigit(c)) {
986 if ((unsigned)(p-buf) >= sizeof(buf)) {
987 yyerror("string too long");
991 } while (c != EOF && isdigit(c));
993 if (p == buf + 1 && buf[0] == '-')
995 if (c == EOF || allowed_to_end_number(c)) {
996 const char *errstr = NULL;
999 yylval.v.number = strtonum(buf, LLONG_MIN,
1000 LLONG_MAX, &errstr);
1002 yyerror("\"%s\" invalid number: %s",
1017 #define allowed_in_string(x) \
1018 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
1019 x != '{' && x != '}' && \
1020 x != '!' && x != '=' && x != '#' && \
1023 if (isalnum(c) || c == ':' || c == '_') {
1026 if ((unsigned)(p-buf) >= sizeof(buf)) {
1027 yyerror("string too long");
1031 } while (c != EOF && (allowed_in_string(c)));
1034 token = lookup(buf);
1035 if (token == STRING) {
1036 yylval.v.string = strdup(buf);
1037 if (yylval.v.string == NULL)
1038 err(1, "yylex: strdup");
1043 yylval.lineno = file->lineno;
1052 check_file_secrecy(int fd, const char *fname)
1056 if (fstat(fd, &st)) {
1057 log_warn("cannot stat %s", fname);
1060 if (st.st_uid != 0 && st.st_uid != getuid()) {
1061 log_warnx("%s: owner not root or current user", fname);
1064 if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
1065 log_warnx("%s: group writable or world read/writable", fname);
1072 newfile(const char *name, int secret, int required)
1076 nfile = calloc(1, sizeof(struct file));
1077 if (nfile == NULL) {
1081 nfile->name = strdup(name);
1082 if (nfile->name == NULL) {
1087 nfile->stream = fopen(nfile->name, "r");
1088 if (nfile->stream == NULL) {
1090 log_warn("open %s", nfile->name);
1094 } else if (secret &&
1095 check_file_secrecy(fileno(nfile->stream), nfile->name)) {
1096 fclose(nfile->stream);
1106 closefile(struct file *xfile)
1108 fclose(xfile->stream);
1114 parse_config(const char *filename, enum gotd_procid proc_id,
1117 struct sym *sym, *next;
1118 struct gotd_repo *repo;
1119 int require_config_file = (proc_id != PROC_GITWRAPPER);
1121 memset(env, 0, sizeof(*env));
1124 gotd_proc_id = proc_id;
1125 TAILQ_INIT(&gotd->repos);
1127 /* Apply default values. */
1128 if (strlcpy(gotd->unix_socket_path, GOTD_UNIX_SOCKET,
1129 sizeof(gotd->unix_socket_path)) >= sizeof(gotd->unix_socket_path)) {
1130 fprintf(stderr, "%s: unix socket path too long", __func__);
1133 if (strlcpy(gotd->user_name, GOTD_USER,
1134 sizeof(gotd->user_name)) >= sizeof(gotd->user_name)) {
1135 fprintf(stderr, "%s: user name too long", __func__);
1139 gotd->request_timeout.tv_sec = GOTD_DEFAULT_REQUEST_TIMEOUT;
1140 gotd->request_timeout.tv_usec = 0;
1142 file = newfile(filename, 0, require_config_file);
1144 return require_config_file ? -1 : 0;
1147 errors = file->errors;
1150 /* Free macros and check which have not been used. */
1151 TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
1152 if ((gotd->verbosity > 1) && !sym->used)
1153 fprintf(stderr, "warning: macro '%s' not used\n",
1155 if (!sym->persist) {
1158 TAILQ_REMOVE(&symhead, sym, entry);
1166 TAILQ_FOREACH(repo, &gotd->repos, entry) {
1167 if (repo->path[0] == '\0') {
1168 log_warnx("repository \"%s\": no path provided in "
1169 "configuration file", repo->name);
1174 if (proc_id == PROC_GOTD && TAILQ_EMPTY(&gotd->repos)) {
1175 log_warnx("no repository defined in configuration file");
1183 uid_connection_limit_cmp(const void *pa, const void *pb)
1185 const struct gotd_uid_connection_limit *a = pa, *b = pb;
1187 if (a->uid < b->uid)
1189 else if (a->uid > b->uid);
1196 conf_limit_user_connections(const char *user, int maximum)
1199 struct gotd_uid_connection_limit *limit;
1203 yyerror("max connections cannot be smaller 1");
1206 if (maximum > GOTD_MAXCLIENTS) {
1207 yyerror("max connections must be <= %d", GOTD_MAXCLIENTS);
1211 if (gotd_parseuid(user, &uid) == -1) {
1212 yyerror("%s: no such user", user);
1216 limit = gotd_find_uid_connection_limit(gotd->connection_limits,
1217 gotd->nconnection_limits, uid);
1219 limit->max_connections = maximum;
1223 limit = gotd->connection_limits;
1224 nlimits = gotd->nconnection_limits + 1;
1225 limit = reallocarray(limit, nlimits, sizeof(*limit));
1227 fatal("reallocarray");
1229 limit[nlimits - 1].uid = uid;
1230 limit[nlimits - 1].max_connections = maximum;
1232 gotd->connection_limits = limit;
1233 gotd->nconnection_limits = nlimits;
1234 qsort(gotd->connection_limits, gotd->nconnection_limits,
1235 sizeof(gotd->connection_limits[0]), uid_connection_limit_cmp);
1240 static struct gotd_repo *
1241 conf_new_repo(const char *name)
1243 struct gotd_repo *repo;
1245 if (name[0] == '\0') {
1246 fatalx("syntax error: empty repository name found in %s",
1250 if (strchr(name, '\n') != NULL)
1251 fatalx("repository names must not contain linefeeds: %s", name);
1253 repo = calloc(1, sizeof(*repo));
1255 fatalx("%s: calloc", __func__);
1257 STAILQ_INIT(&repo->rules);
1258 TAILQ_INIT(&repo->protected_tag_namespaces);
1259 TAILQ_INIT(&repo->protected_branch_namespaces);
1260 TAILQ_INIT(&repo->protected_branches);
1261 TAILQ_INIT(&repo->protected_branches);
1262 TAILQ_INIT(&repo->notification_refs);
1263 TAILQ_INIT(&repo->notification_ref_namespaces);
1264 STAILQ_INIT(&repo->notification_targets);
1266 if (strlcpy(repo->name, name, sizeof(repo->name)) >=
1268 fatalx("%s: strlcpy", __func__);
1270 TAILQ_INSERT_TAIL(&gotd->repos, repo, entry);
1277 conf_new_access_rule(struct gotd_repo *repo, enum gotd_access access,
1278 int authorization, char *identifier)
1280 struct gotd_access_rule *rule;
1282 rule = calloc(1, sizeof(*rule));
1286 rule->access = access;
1287 rule->authorization = authorization;
1288 rule->identifier = identifier;
1290 STAILQ_INSERT_TAIL(&repo->rules, rule, entry);
1294 refname_is_valid(char *refname)
1296 if (strncmp(refname, "refs/", 5) != 0) {
1297 yyerror("reference name must begin with \"refs/\": %s",
1302 if (!got_ref_name_is_valid(refname)) {
1303 yyerror("invalid reference name: %s", refname);
1311 conf_protect_ref_namespace(char **new, struct got_pathlist_head *refs,
1314 const struct got_error *error;
1315 struct got_pathlist_entry *pe;
1320 got_path_strip_trailing_slashes(namespace);
1321 if (!refname_is_valid(namespace))
1323 if (asprintf(&s, "%s/", namespace) == -1) {
1324 yyerror("asprintf: %s", strerror(errno));
1328 error = got_pathlist_insert(&pe, refs, s, NULL);
1329 if (error || pe == NULL) {
1332 yyerror("got_pathlist_insert: %s", error->msg);
1334 yyerror("duplicate protected namespace %s", namespace);
1343 conf_protect_tag_namespace(struct gotd_repo *repo, char *namespace)
1345 struct got_pathlist_entry *pe;
1348 if (conf_protect_ref_namespace(&new, &repo->protected_tag_namespaces,
1352 TAILQ_FOREACH(pe, &repo->protected_branch_namespaces, entry) {
1353 if (strcmp(pe->path, new) == 0) {
1354 yyerror("duplicate protected namespace %s", namespace);
1363 conf_protect_branch_namespace(struct gotd_repo *repo, char *namespace)
1365 struct got_pathlist_entry *pe;
1368 if (conf_protect_ref_namespace(&new,
1369 &repo->protected_branch_namespaces, namespace) == -1)
1372 TAILQ_FOREACH(pe, &repo->protected_tag_namespaces, entry) {
1373 if (strcmp(pe->path, new) == 0) {
1374 yyerror("duplicate protected namespace %s", namespace);
1383 conf_protect_branch(struct gotd_repo *repo, char *branchname)
1385 const struct got_error *error;
1386 struct got_pathlist_entry *new;
1389 if (strncmp(branchname, "refs/heads/", 11) != 0) {
1390 if (asprintf(&refname, "refs/heads/%s", branchname) == -1) {
1391 yyerror("asprintf: %s", strerror(errno));
1395 refname = strdup(branchname);
1396 if (refname == NULL) {
1397 yyerror("strdup: %s", strerror(errno));
1402 if (!refname_is_valid(refname)) {
1407 error = got_pathlist_insert(&new, &repo->protected_branches,
1409 if (error || new == NULL) {
1412 yyerror("got_pathlist_insert: %s", error->msg);
1414 yyerror("duplicate protect branch %s", branchname);
1422 conf_notify_branch(struct gotd_repo *repo, char *branchname)
1424 const struct got_error *error;
1425 struct got_pathlist_entry *pe;
1428 if (strncmp(branchname, "refs/heads/", 11) != 0) {
1429 if (asprintf(&refname, "refs/heads/%s", branchname) == -1) {
1430 yyerror("asprintf: %s", strerror(errno));
1434 refname = strdup(branchname);
1435 if (refname == NULL) {
1436 yyerror("strdup: %s", strerror(errno));
1441 if (!refname_is_valid(refname)) {
1446 error = got_pathlist_insert(&pe, &repo->notification_refs,
1450 yyerror("got_pathlist_insert: %s", error->msg);
1460 conf_notify_ref_namespace(struct gotd_repo *repo, char *namespace)
1462 const struct got_error *error;
1463 struct got_pathlist_entry *pe;
1466 got_path_strip_trailing_slashes(namespace);
1467 if (!refname_is_valid(namespace))
1470 if (asprintf(&s, "%s/", namespace) == -1) {
1471 yyerror("asprintf: %s", strerror(errno));
1475 error = got_pathlist_insert(&pe, &repo->notification_ref_namespaces,
1479 yyerror("got_pathlist_insert: %s", error->msg);
1489 conf_notify_email(struct gotd_repo *repo, char *sender, char *recipient,
1490 char *responder, char *hostname, char *port)
1492 struct gotd_notification_target *target;
1494 STAILQ_FOREACH(target, &repo->notification_targets, entry) {
1495 if (target->type != GOTD_NOTIFICATION_VIA_EMAIL)
1497 if (strcmp(target->conf.email.recipient, recipient) == 0) {
1498 yyerror("duplicate email notification for '%s' in "
1499 "repository '%s'", recipient, repo->name);
1504 target = calloc(1, sizeof(*target));
1507 target->type = GOTD_NOTIFICATION_VIA_EMAIL;
1509 target->conf.email.sender = strdup(sender);
1510 if (target->conf.email.sender == NULL)
1513 target->conf.email.recipient = strdup(recipient);
1514 if (target->conf.email.recipient == NULL)
1517 target->conf.email.responder = strdup(responder);
1518 if (target->conf.email.responder == NULL)
1522 target->conf.email.hostname = strdup(hostname);
1523 if (target->conf.email.hostname == NULL)
1527 target->conf.email.port = strdup(port);
1528 if (target->conf.email.port == NULL)
1532 STAILQ_INSERT_TAIL(&repo->notification_targets, target, entry);
1537 conf_notify_http(struct gotd_repo *repo, char *url, char *user, char *password)
1539 const struct got_error *error;
1540 struct gotd_notification_target *target;
1541 char *proto, *host, *port, *request_path;
1544 error = gotd_parse_url(&proto, &host, &port, &request_path, url);
1546 yyerror("invalid HTTP notification URL '%s' in "
1547 "repository '%s': %s", url, repo->name, error->msg);
1551 if (strcmp(proto, "http") != 0 && strcmp(proto, "https") != 0) {
1552 yyerror("invalid protocol '%s' in notification URL '%s' in "
1553 "repository '%s", proto, url, repo->name);
1558 if (strcmp(proto, "http") == 0 && (user != NULL || password != NULL)) {
1559 log_warnx("%s: WARNING: Using basic authentication over "
1560 "plaintext http:// will leak credentials; https:// is "
1561 "recommended for URL '%s'", getprogname(), url);
1564 STAILQ_FOREACH(target, &repo->notification_targets, entry) {
1565 if (target->type != GOTD_NOTIFICATION_VIA_HTTP)
1567 if (strcmp(target->conf.http.url, url) == 0) {
1568 yyerror("duplicate notification for URL '%s' in "
1569 "repository '%s'", url, repo->name);
1575 target = calloc(1, sizeof(*target));
1578 target->type = GOTD_NOTIFICATION_VIA_HTTP;
1579 target->conf.http.url = strdup(url);
1580 if (target->conf.http.url == NULL)
1583 target->conf.http.user = strdup(user);
1584 if (target->conf.http.user == NULL)
1588 target->conf.http.password = strdup(password);
1589 if (target->conf.http.password == NULL)
1593 STAILQ_INSERT_TAIL(&repo->notification_targets, target, entry);
1603 symset(const char *nam, const char *val, int persist)
1607 TAILQ_FOREACH(sym, &symhead, entry) {
1608 if (strcmp(nam, sym->nam) == 0)
1613 if (sym->persist == 1)
1618 TAILQ_REMOVE(&symhead, sym, entry);
1622 sym = calloc(1, sizeof(*sym));
1626 sym->nam = strdup(nam);
1627 if (sym->nam == NULL) {
1631 sym->val = strdup(val);
1632 if (sym->val == NULL) {
1638 sym->persist = persist;
1639 TAILQ_INSERT_TAIL(&symhead, sym, entry);
1644 symget(const char *nam)
1648 TAILQ_FOREACH(sym, &symhead, entry) {
1649 if (strcmp(nam, sym->nam) == 0) {
1658 gotd_find_repo_by_name(const char *repo_name, struct gotd_repolist *repos)
1660 struct gotd_repo *repo;
1663 TAILQ_FOREACH(repo, repos, entry) {
1664 namelen = strlen(repo->name);
1665 if (strncmp(repo->name, repo_name, namelen) != 0)
1667 if (repo_name[namelen] == '\0' ||
1668 strcmp(&repo_name[namelen], ".git") == 0)
1676 gotd_find_repo_by_path(const char *repo_path, struct gotd *gotd)
1678 struct gotd_repo *repo;
1680 TAILQ_FOREACH(repo, &gotd->repos, entry) {
1681 if (strcmp(repo->path, repo_path) == 0)
1688 struct gotd_uid_connection_limit *
1689 gotd_find_uid_connection_limit(struct gotd_uid_connection_limit *limits,
1690 size_t nlimits, uid_t uid)
1692 /* This array is always sorted to allow for binary search. */
1693 int i, left = 0, right = nlimits - 1;
1695 while (left <= right) {
1696 i = ((left + right) / 2);
1697 if (limits[i].uid == uid)
1699 if (limits[i].uid > uid)
1709 gotd_parseuid(const char *s, uid_t *uid)
1714 if ((pw = getpwnam(s)) != NULL) {
1716 if (*uid == UID_MAX)
1720 *uid = strtonum(s, 0, UID_MAX - 1, &errstr);
1726 const struct got_error *
1727 gotd_parse_url(char **proto, char **host, char **port,
1728 char **request_path, const char *url)
1730 const struct got_error *err = NULL;
1733 *proto = *host = *port = *request_path = NULL;
1735 p = strstr(url, "://");
1737 return got_error(GOT_ERR_PARSE_URI);
1739 *proto = strndup(url, p - url);
1740 if (*proto == NULL) {
1741 err = got_error_from_errno("strndup");
1747 if (p == NULL || strlen(p) == 1) {
1748 err = got_error(GOT_ERR_PARSE_URI);
1752 q = memchr(s, ':', p - s);
1754 *host = strndup(s, q - s);
1755 if (*host == NULL) {
1756 err = got_error_from_errno("strndup");
1759 if ((*host)[0] == '\0') {
1760 err = got_error(GOT_ERR_PARSE_URI);
1763 *port = strndup(q + 1, p - (q + 1));
1764 if (*port == NULL) {
1765 err = got_error_from_errno("strndup");
1768 if ((*port)[0] == '\0') {
1769 err = got_error(GOT_ERR_PARSE_URI);
1773 *host = strndup(s, p - s);
1774 if (*host == NULL) {
1775 err = got_error_from_errno("strndup");
1778 if ((*host)[0] == '\0') {
1779 err = got_error(GOT_ERR_PARSE_URI);
1784 while (p[0] == '/' && p[1] == '/')
1786 *request_path = strdup(p);
1787 if (*request_path == NULL) {
1788 err = got_error_from_errno("strdup");
1791 got_path_strip_trailing_slashes(*request_path);
1792 if ((*request_path)[0] == '\0') {
1793 err = got_error(GOT_ERR_PARSE_URI);
1804 free(*request_path);
1805 *request_path = NULL;
1813 static char portno[32];
1816 n = snprintf(portno, sizeof(portno), "%lld", (long long)p);
1817 if (n < 0 || (size_t)n >= sizeof(portno))
1818 fatalx("port number too long: %lld", (long long)p);