Blob


1 /*
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.
10 *
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.
14 *
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.
22 */
24 %{
25 #include <sys/time.h>
26 #include <sys/types.h>
27 #include <sys/queue.h>
28 #include <sys/stat.h>
30 #include <ctype.h>
31 #include <err.h>
32 #include <errno.h>
33 #include <event.h>
34 #include <imsg.h>
35 #include <limits.h>
36 #include <pwd.h>
37 #include <sha1.h>
38 #include <sha2.h>
39 #include <stdarg.h>
40 #include <stdlib.h>
41 #include <stdio.h>
42 #include <string.h>
43 #include <syslog.h>
44 #include <unistd.h>
46 #include "got_error.h"
47 #include "got_path.h"
48 #include "got_reference.h"
50 #include "log.h"
51 #include "gotd.h"
52 #include "auth.h"
53 #include "listen.h"
55 TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
56 static struct file {
57 TAILQ_ENTRY(file) entry;
58 FILE *stream;
59 char *name;
60 int lineno;
61 int errors;
62 } *file;
63 struct file *newfile(const char *, int, int);
64 static void closefile(struct file *);
65 int check_file_secrecy(int, const char *);
66 int yyparse(void);
67 int yylex(void);
68 int yyerror(const char *, ...)
69 __attribute__((__format__ (printf, 1, 2)))
70 __attribute__((__nonnull__ (1)));
71 int kw_cmp(const void *, const void *);
72 int lookup(char *);
73 int lgetc(int);
74 int lungetc(int);
75 int findeol(void);
77 TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
78 struct sym {
79 TAILQ_ENTRY(sym) entry;
80 int used;
81 int persist;
82 char *nam;
83 char *val;
84 };
86 int symset(const char *, const char *, int);
87 char *symget(const char *);
89 static int errors;
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 *,
100 char *);
101 static int conf_protect_branch_namespace(
102 struct gotd_repo *, char *);
103 static int conf_protect_branch(struct gotd_repo *,
104 char *);
105 static enum gotd_procid gotd_proc_id;
107 typedef struct {
108 union {
109 long long number;
110 char *string;
111 struct timeval tv;
112 } v;
113 int lineno;
114 } YYSTYPE;
116 %}
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
124 %type <v.tv> timeout
126 %%
128 grammar :
129 | grammar '\n'
130 | grammar varset '\n'
131 | grammar main '\n'
132 | grammar repository '\n'
135 varset : STRING '=' STRING {
136 char *s = $1;
137 while (*s++) {
138 if (isspace((unsigned char)*s)) {
139 yyerror("macro name cannot contain "
140 "whitespace");
141 free($1);
142 free($3);
143 YYERROR;
146 if (symset($1, $3, 0) == -1)
147 fatal("cannot store variable");
148 free($1);
149 free($3);
153 timeout : NUMBER {
154 if ($1 < 0) {
155 yyerror("invalid timeout: %lld", $1);
156 YYERROR;
158 $$.tv_sec = $1;
159 $$.tv_usec = 0;
161 | STRING {
162 const char *errstr;
163 const char *type = "seconds";
164 size_t len;
165 int mul = 1;
167 if (*$1 == '\0') {
168 yyerror("invalid number of seconds: %s", $1);
169 free($1);
170 YYERROR;
173 len = strlen($1);
174 switch ($1[len - 1]) {
175 case 'S':
176 case 's':
177 $1[len - 1] = '\0';
178 break;
179 case 'M':
180 case 'm':
181 type = "minutes";
182 mul = 60;
183 $1[len - 1] = '\0';
184 break;
185 case 'H':
186 case 'h':
187 type = "hours";
188 mul = 60 * 60;
189 $1[len - 1] = '\0';
190 break;
193 $$.tv_usec = 0;
194 $$.tv_sec = strtonum($1, 0, INT_MAX / mul, &errstr);
195 if (errstr) {
196 yyerror("number of %s is %s: %s", type,
197 errstr, $1);
198 free($1);
199 YYERROR;
202 $$.tv_sec *= mul;
203 free($1);
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",
217 __func__);
218 free($3);
219 YYERROR;
222 free($3);
224 | USER STRING {
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__);
229 free($2);
230 YYERROR;
232 free($2);
234 | connection
237 connection : CONNECTION '{' optnl conflags_l '}'
238 | CONNECTION conflags
240 conflags_l : conflags optnl conflags_l
241 | conflags optnl
244 conflags : REQUEST TIMEOUT timeout {
245 if ($3.tv_sec <= 0) {
246 yyerror("invalid timeout: %lld", $3.tv_sec);
247 YYERROR;
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) {
255 free($3);
256 YYERROR;
258 free($3);
262 protect : PROTECT '{' optnl protectflags_l '}'
263 | PROTECT protectflags
265 protectflags_l : protectflags optnl protectflags_l
266 | protectflags optnl
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)) {
273 free($3);
274 YYERROR;
277 free($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,
283 $3)) {
284 free($3);
285 YYERROR;
288 free($3);
290 | BRANCH STRING {
291 if (gotd_proc_id == PROC_GOTD ||
292 gotd_proc_id == PROC_REPO_WRITE) {
293 if (conf_protect_branch(new_repo, $2)) {
294 free($2);
295 YYERROR;
298 free($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);
308 free($2);
309 YYERROR;
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);
319 free($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",
331 __func__, $2);
332 free($2);
333 YYERROR;
335 if (realpath($2, new_repo->path) == NULL) {
336 /*
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
344 * gotd regardless.
345 */
346 if (errno == ENOENT) {
347 yyerror("realpath %s: %s", $2,
348 strerror(errno));
349 } else if (errno != EACCES ||
350 gotd_proc_id != PROC_GITWRAPPER) {
351 yyerror("realpath %s: %s", $2,
352 strerror(errno));
353 free($2);
354 YYERROR;
357 if (strlcpy(new_repo->path, $2,
358 sizeof(new_repo->path)) >=
359 sizeof(new_repo->path))
360 yyerror("path too long");
363 free($2);
365 | PERMIT RO STRING {
366 if (gotd_proc_id == PROC_AUTH) {
367 conf_new_access_rule(new_repo,
368 GOTD_ACCESS_PERMITTED, GOTD_AUTH_READ, $3);
369 } else
370 free($3);
372 | PERMIT RW STRING {
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);
377 } else
378 free($3);
380 | DENY STRING {
381 if (gotd_proc_id == PROC_AUTH) {
382 conf_new_access_rule(new_repo,
383 GOTD_ACCESS_DENIED, 0, $2);
384 } else
385 free($2);
387 | protect
390 repoopts2 : repoopts2 repoopts1 nl
391 | repoopts1 optnl
394 nl : '\n' optnl
397 optnl : '\n' optnl /* zero or more newlines */
398 | /* empty */
401 %%
403 struct keywords {
404 const char *k_name;
405 int k_val;
406 };
408 int
409 yyerror(const char *fmt, ...)
411 va_list ap;
412 char *msg;
414 file->errors++;
415 va_start(ap, fmt);
416 if (vasprintf(&msg, fmt, ap) == -1)
417 fatalx("yyerror vasprintf");
418 va_end(ap);
419 logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
420 free(msg);
421 return (0);
424 int
425 kw_cmp(const void *k, const void *e)
427 return (strcmp(k, ((const struct keywords *)e)->k_name));
430 int
431 lookup(char *s)
433 /* This has to be sorted always. */
434 static const struct keywords keywords[] = {
435 { "branch", BRANCH },
436 { "connection", CONNECTION },
437 { "deny", DENY },
438 { "limit", LIMIT },
439 { "listen", LISTEN },
440 { "namespace", NAMESPACE },
441 { "on", ON },
442 { "path", PATH },
443 { "permit", PERMIT },
444 { "protect", PROTECT },
445 { "repository", REPOSITORY },
446 { "request", REQUEST },
447 { "ro", RO },
448 { "rw", RW },
449 { "tag", TAG },
450 { "timeout", TIMEOUT },
451 { "user", USER },
452 };
453 const struct keywords *p;
455 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
456 sizeof(keywords[0]), kw_cmp);
458 if (p)
459 return (p->k_val);
460 else
461 return (STRING);
464 #define MAXPUSHBACK 128
466 unsigned char *parsebuf;
467 int parseindex;
468 unsigned char pushback_buffer[MAXPUSHBACK];
469 int pushback_index = 0;
471 int
472 lgetc(int quotec)
474 int c, next;
476 if (parsebuf) {
477 /* Read character from the parsebuffer instead of input. */
478 if (parseindex >= 0) {
479 c = parsebuf[parseindex++];
480 if (c != '\0')
481 return (c);
482 parsebuf = NULL;
483 } else
484 parseindex++;
487 if (pushback_index)
488 return (pushback_buffer[--pushback_index]);
490 if (quotec) {
491 c = getc(file->stream);
492 if (c == EOF)
493 yyerror("reached end of file while parsing "
494 "quoted string");
495 return (c);
498 c = getc(file->stream);
499 while (c == '\\') {
500 next = getc(file->stream);
501 if (next != '\n') {
502 c = next;
503 break;
505 yylval.lineno = file->lineno;
506 file->lineno++;
507 c = getc(file->stream);
510 return (c);
513 int
514 lungetc(int c)
516 if (c == EOF)
517 return (EOF);
518 if (parsebuf) {
519 parseindex--;
520 if (parseindex >= 0)
521 return (c);
523 if (pushback_index < MAXPUSHBACK-1)
524 return (pushback_buffer[pushback_index++] = c);
525 else
526 return (EOF);
529 int
530 findeol(void)
532 int c;
534 parsebuf = NULL;
536 /* Skip to either EOF or the first real EOL. */
537 while (1) {
538 if (pushback_index)
539 c = pushback_buffer[--pushback_index];
540 else
541 c = lgetc(0);
542 if (c == '\n') {
543 file->lineno++;
544 break;
546 if (c == EOF)
547 break;
549 return (ERROR);
552 int
553 yylex(void)
555 unsigned char buf[8096];
556 unsigned char *p, *val;
557 int quotec, next, c;
558 int token;
560 top:
561 p = buf;
562 c = lgetc(0);
563 while (c == ' ' || c == '\t')
564 c = lgetc(0); /* nothing */
566 yylval.lineno = file->lineno;
567 if (c == '#') {
568 c = lgetc(0);
569 while (c != '\n' && c != EOF)
570 c = lgetc(0); /* nothing */
572 if (c == '$' && parsebuf == NULL) {
573 while (1) {
574 c = lgetc(0);
575 if (c == EOF)
576 return (0);
578 if (p + 1 >= buf + sizeof(buf) - 1) {
579 yyerror("string too long");
580 return (findeol());
582 if (isalnum(c) || c == '_') {
583 *p++ = c;
584 continue;
586 *p = '\0';
587 lungetc(c);
588 break;
590 val = symget(buf);
591 if (val == NULL) {
592 yyerror("macro '%s' not defined", buf);
593 return (findeol());
595 parsebuf = val;
596 parseindex = 0;
597 goto top;
600 switch (c) {
601 case '\'':
602 case '"':
603 quotec = c;
604 while (1) {
605 c = lgetc(quotec);
606 if (c == EOF)
607 return (0);
608 if (c == '\n') {
609 file->lineno++;
610 continue;
611 } else if (c == '\\') {
612 next = lgetc(quotec);
613 if (next == EOF)
614 return (0);
615 if (next == quotec || c == ' ' || c == '\t')
616 c = next;
617 else if (next == '\n') {
618 file->lineno++;
619 continue;
620 } else
621 lungetc(next);
622 } else if (c == quotec) {
623 *p = '\0';
624 break;
625 } else if (c == '\0') {
626 yyerror("syntax error");
627 return (findeol());
629 if (p + 1 >= buf + sizeof(buf) - 1) {
630 yyerror("string too long");
631 return (findeol());
633 *p++ = c;
635 yylval.v.string = strdup(buf);
636 if (yylval.v.string == NULL)
637 err(1, "yylex: strdup");
638 return (STRING);
641 #define allowed_to_end_number(x) \
642 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
644 if (c == '-' || isdigit(c)) {
645 do {
646 *p++ = c;
647 if ((unsigned)(p-buf) >= sizeof(buf)) {
648 yyerror("string too long");
649 return (findeol());
651 c = lgetc(0);
652 } while (c != EOF && isdigit(c));
653 lungetc(c);
654 if (p == buf + 1 && buf[0] == '-')
655 goto nodigits;
656 if (c == EOF || allowed_to_end_number(c)) {
657 const char *errstr = NULL;
659 *p = '\0';
660 yylval.v.number = strtonum(buf, LLONG_MIN,
661 LLONG_MAX, &errstr);
662 if (errstr) {
663 yyerror("\"%s\" invalid number: %s",
664 buf, errstr);
665 return (findeol());
667 return (NUMBER);
668 } else {
669 nodigits:
670 while (p > buf + 1)
671 lungetc(*--p);
672 c = *--p;
673 if (c == '-')
674 return (c);
678 #define allowed_in_string(x) \
679 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
680 x != '{' && x != '}' && \
681 x != '!' && x != '=' && x != '#' && \
682 x != ','))
684 if (isalnum(c) || c == ':' || c == '_') {
685 do {
686 *p++ = c;
687 if ((unsigned)(p-buf) >= sizeof(buf)) {
688 yyerror("string too long");
689 return (findeol());
691 c = lgetc(0);
692 } while (c != EOF && (allowed_in_string(c)));
693 lungetc(c);
694 *p = '\0';
695 token = lookup(buf);
696 if (token == STRING) {
697 yylval.v.string = strdup(buf);
698 if (yylval.v.string == NULL)
699 err(1, "yylex: strdup");
701 return (token);
703 if (c == '\n') {
704 yylval.lineno = file->lineno;
705 file->lineno++;
707 if (c == EOF)
708 return (0);
709 return (c);
712 int
713 check_file_secrecy(int fd, const char *fname)
715 struct stat st;
717 if (fstat(fd, &st)) {
718 log_warn("cannot stat %s", fname);
719 return (-1);
721 if (st.st_uid != 0 && st.st_uid != getuid()) {
722 log_warnx("%s: owner not root or current user", fname);
723 return (-1);
725 if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
726 log_warnx("%s: group writable or world read/writable", fname);
727 return (-1);
729 return (0);
732 struct file *
733 newfile(const char *name, int secret, int required)
735 struct file *nfile;
737 nfile = calloc(1, sizeof(struct file));
738 if (nfile == NULL) {
739 log_warn("calloc");
740 return (NULL);
742 nfile->name = strdup(name);
743 if (nfile->name == NULL) {
744 log_warn("strdup");
745 free(nfile);
746 return (NULL);
748 nfile->stream = fopen(nfile->name, "r");
749 if (nfile->stream == NULL) {
750 if (required)
751 log_warn("open %s", nfile->name);
752 free(nfile->name);
753 free(nfile);
754 return (NULL);
755 } else if (secret &&
756 check_file_secrecy(fileno(nfile->stream), nfile->name)) {
757 fclose(nfile->stream);
758 free(nfile->name);
759 free(nfile);
760 return (NULL);
762 nfile->lineno = 1;
763 return (nfile);
766 static void
767 closefile(struct file *xfile)
769 fclose(xfile->stream);
770 free(xfile->name);
771 free(xfile);
774 int
775 parse_config(const char *filename, enum gotd_procid proc_id,
776 struct gotd *env)
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));
784 gotd = 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__);
792 return -1;
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__);
797 return -1;
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);
804 if (file == NULL)
805 return require_config_file ? -1 : 0;
807 yyparse();
808 errors = file->errors;
809 closefile(file);
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",
815 sym->nam);
816 if (!sym->persist) {
817 free(sym->nam);
818 free(sym->val);
819 TAILQ_REMOVE(&symhead, sym, entry);
820 free(sym);
824 if (errors)
825 return (-1);
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);
831 return (-1);
835 if (proc_id == PROC_GOTD && TAILQ_EMPTY(&gotd->repos)) {
836 log_warnx("no repository defined in configuration file");
837 return (-1);
840 return (0);
843 static int
844 uid_connection_limit_cmp(const void *pa, const void *pb)
846 const struct gotd_uid_connection_limit *a = pa, *b = pb;
848 if (a->uid < b->uid)
849 return -1;
850 else if (a->uid > b->uid);
851 return 1;
853 return 0;
856 static int
857 conf_limit_user_connections(const char *user, int maximum)
859 uid_t uid;
860 struct gotd_uid_connection_limit *limit;
861 size_t nlimits;
863 if (maximum < 1) {
864 yyerror("max connections cannot be smaller 1");
865 return -1;
867 if (maximum > GOTD_MAXCLIENTS) {
868 yyerror("max connections must be <= %d", GOTD_MAXCLIENTS);
869 return -1;
872 if (gotd_parseuid(user, &uid) == -1) {
873 yyerror("%s: no such user", user);
874 return -1;
877 limit = gotd_find_uid_connection_limit(gotd->connection_limits,
878 gotd->nconnection_limits, uid);
879 if (limit) {
880 limit->max_connections = maximum;
881 return 0;
884 limit = gotd->connection_limits;
885 nlimits = gotd->nconnection_limits + 1;
886 limit = reallocarray(limit, nlimits, sizeof(*limit));
887 if (limit == NULL)
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);
898 return 0;
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",
908 file->name);
911 if (strchr(name, '\n') != NULL)
912 fatalx("repository names must not contain linefeeds: %s", name);
914 repo = calloc(1, sizeof(*repo));
915 if (repo == NULL)
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)) >=
924 sizeof(repo->name))
925 fatalx("%s: strlcpy", __func__);
927 TAILQ_INSERT_TAIL(&gotd->repos, repo, entry);
928 gotd->nrepos++;
930 return repo;
931 };
933 static void
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));
940 if (rule == NULL)
941 fatal("calloc");
943 rule->access = access;
944 rule->authorization = authorization;
945 rule->identifier = identifier;
947 STAILQ_INSERT_TAIL(&repo->rules, rule, entry);
950 static int
951 refname_is_valid(char *refname)
953 if (strncmp(refname, "refs/", 5) != 0) {
954 yyerror("reference name must begin with \"refs/\": %s",
955 refname);
956 return 0;
959 if (!got_ref_name_is_valid(refname)) {
960 yyerror("invalid reference name: %s", refname);
961 return 0;
964 return 1;
967 static int
968 conf_protect_ref_namespace(char **new, struct got_pathlist_head *refs,
969 char *namespace)
971 const struct got_error *error;
972 struct got_pathlist_entry *pe;
973 char *s;
975 *new = NULL;
977 got_path_strip_trailing_slashes(namespace);
978 if (!refname_is_valid(namespace))
979 return -1;
980 if (asprintf(&s, "%s/", namespace) == -1) {
981 yyerror("asprintf: %s", strerror(errno));
982 return -1;
985 error = got_pathlist_insert(&pe, refs, s, NULL);
986 if (error || pe == NULL) {
987 free(s);
988 if (error)
989 yyerror("got_pathlist_insert: %s", error->msg);
990 else
991 yyerror("duplicate protected namespace %s", namespace);
992 return -1;
995 *new = s;
996 return 0;
999 static int
1000 conf_protect_tag_namespace(struct gotd_repo *repo, char *namespace)
1002 struct got_pathlist_entry *pe;
1003 char *new;
1005 if (conf_protect_ref_namespace(&new, &repo->protected_tag_namespaces,
1006 namespace) == -1)
1007 return -1;
1009 TAILQ_FOREACH(pe, &repo->protected_branch_namespaces, entry) {
1010 if (strcmp(pe->path, new) == 0) {
1011 yyerror("duplicate protected namespace %s", namespace);
1012 return -1;
1016 return 0;
1019 static int
1020 conf_protect_branch_namespace(struct gotd_repo *repo, char *namespace)
1022 struct got_pathlist_entry *pe;
1023 char *new;
1025 if (conf_protect_ref_namespace(&new,
1026 &repo->protected_branch_namespaces, namespace) == -1)
1027 return -1;
1029 TAILQ_FOREACH(pe, &repo->protected_tag_namespaces, entry) {
1030 if (strcmp(pe->path, new) == 0) {
1031 yyerror("duplicate protected namespace %s", namespace);
1032 return -1;
1036 return 0;
1039 static int
1040 conf_protect_branch(struct gotd_repo *repo, char *branchname)
1042 const struct got_error *error;
1043 struct got_pathlist_entry *new;
1044 char *refname;
1046 if (strncmp(branchname, "refs/heads/", 11) != 0) {
1047 if (asprintf(&refname, "refs/heads/%s", branchname) == -1) {
1048 yyerror("asprintf: %s", strerror(errno));
1049 return -1;
1051 } else {
1052 refname = strdup(branchname);
1053 if (refname == NULL) {
1054 yyerror("strdup: %s", strerror(errno));
1055 return -1;
1059 if (!refname_is_valid(refname)) {
1060 free(refname);
1061 return -1;
1064 error = got_pathlist_insert(&new, &repo->protected_branches,
1065 refname, NULL);
1066 if (error || new == NULL) {
1067 free(refname);
1068 if (error)
1069 yyerror("got_pathlist_insert: %s", error->msg);
1070 else
1071 yyerror("duplicate protect branch %s", branchname);
1072 return -1;
1075 return 0;
1078 int
1079 symset(const char *nam, const char *val, int persist)
1081 struct sym *sym;
1083 TAILQ_FOREACH(sym, &symhead, entry) {
1084 if (strcmp(nam, sym->nam) == 0)
1085 break;
1088 if (sym != NULL) {
1089 if (sym->persist == 1)
1090 return (0);
1091 else {
1092 free(sym->nam);
1093 free(sym->val);
1094 TAILQ_REMOVE(&symhead, sym, entry);
1095 free(sym);
1098 sym = calloc(1, sizeof(*sym));
1099 if (sym == NULL)
1100 return (-1);
1102 sym->nam = strdup(nam);
1103 if (sym->nam == NULL) {
1104 free(sym);
1105 return (-1);
1107 sym->val = strdup(val);
1108 if (sym->val == NULL) {
1109 free(sym->nam);
1110 free(sym);
1111 return (-1);
1113 sym->used = 0;
1114 sym->persist = persist;
1115 TAILQ_INSERT_TAIL(&symhead, sym, entry);
1116 return (0);
1119 char *
1120 symget(const char *nam)
1122 struct sym *sym;
1124 TAILQ_FOREACH(sym, &symhead, entry) {
1125 if (strcmp(nam, sym->nam) == 0) {
1126 sym->used = 1;
1127 return (sym->val);
1130 return (NULL);
1133 struct gotd_repo *
1134 gotd_find_repo_by_name(const char *repo_name, struct gotd *gotd)
1136 struct gotd_repo *repo;
1137 size_t namelen;
1139 TAILQ_FOREACH(repo, &gotd->repos, entry) {
1140 namelen = strlen(repo->name);
1141 if (strncmp(repo->name, repo_name, namelen) != 0)
1142 continue;
1143 if (repo_name[namelen] == '\0' ||
1144 strcmp(&repo_name[namelen], ".git") == 0)
1145 return repo;
1148 return NULL;
1151 struct gotd_repo *
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)
1158 return repo;
1161 return NULL;
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)
1174 return &limits[i];
1175 if (limits[i].uid > uid)
1176 left = i + 1;
1177 else
1178 right = i - 1;
1181 return NULL;
1184 int
1185 gotd_parseuid(const char *s, uid_t *uid)
1187 struct passwd *pw;
1188 const char *errstr;
1190 if ((pw = getpwnam(s)) != NULL) {
1191 *uid = pw->pw_uid;
1192 if (*uid == UID_MAX)
1193 return -1;
1194 return 0;
1196 *uid = strtonum(s, 0, UID_MAX - 1, &errstr);
1197 if (errstr)
1198 return -1;
1199 return 0;