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 <sha1.h>
37 #include <sha2.h>
38 #include <stdarg.h>
39 #include <stdlib.h>
40 #include <stdio.h>
41 #include <string.h>
42 #include <syslog.h>
43 #include <unistd.h>
45 #include "got_error.h"
46 #include "got_path.h"
48 #include "log.h"
49 #include "gotd.h"
50 #include "auth.h"
51 #include "listen.h"
53 TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
54 static struct file {
55 TAILQ_ENTRY(file) entry;
56 FILE *stream;
57 char *name;
58 int lineno;
59 int errors;
60 } *file;
61 struct file *newfile(const char *, int);
62 static void closefile(struct file *);
63 int check_file_secrecy(int, const char *);
64 int yyparse(void);
65 int yylex(void);
66 int yyerror(const char *, ...)
67 __attribute__((__format__ (printf, 1, 2)))
68 __attribute__((__nonnull__ (1)));
69 int kw_cmp(const void *, const void *);
70 int lookup(char *);
71 int lgetc(int);
72 int lungetc(int);
73 int findeol(void);
75 TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
76 struct sym {
77 TAILQ_ENTRY(sym) entry;
78 int used;
79 int persist;
80 char *nam;
81 char *val;
82 };
84 int symset(const char *, const char *, int);
85 char *symget(const char *);
87 static int errors;
89 static struct gotd *gotd;
90 static struct gotd_repo *new_repo;
91 static int conf_limit_user_connections(const char *, int);
92 static struct gotd_repo *conf_new_repo(const char *);
93 static void conf_new_access_rule(struct gotd_repo *,
94 enum gotd_access, int, char *);
95 static enum gotd_procid gotd_proc_id;
97 typedef struct {
98 union {
99 long long number;
100 char *string;
101 struct timeval tv;
102 } v;
103 int lineno;
104 } YYSTYPE;
106 %}
108 %token PATH ERROR LISTEN ON USER REPOSITORY PERMIT DENY
109 %token RO RW CONNECTION LIMIT REQUEST TIMEOUT
111 %token <v.string> STRING
112 %token <v.number> NUMBER
113 %type <v.tv> timeout
115 %%
117 grammar :
118 | grammar '\n'
119 | grammar main '\n'
120 | grammar repository '\n'
123 timeout : NUMBER {
124 if ($1 < 0) {
125 yyerror("invalid timeout: %lld", $1);
126 YYERROR;
128 $$.tv_sec = $1;
129 $$.tv_usec = 0;
131 | STRING {
132 const char *errstr;
133 const char *type = "seconds";
134 size_t len;
135 int mul = 1;
137 if (*$1 == '\0') {
138 yyerror("invalid number of seconds: %s", $1);
139 free($1);
140 YYERROR;
143 len = strlen($1);
144 switch ($1[len - 1]) {
145 case 'S':
146 case 's':
147 $1[len - 1] = '\0';
148 break;
149 case 'M':
150 case 'm':
151 type = "minutes";
152 mul = 60;
153 $1[len - 1] = '\0';
154 break;
155 case 'H':
156 case 'h':
157 type = "hours";
158 mul = 60 * 60;
159 $1[len - 1] = '\0';
160 break;
163 $$.tv_usec = 0;
164 $$.tv_sec = strtonum($1, 0, INT_MAX / mul, &errstr);
165 if (errstr) {
166 yyerror("number of %s is %s: %s", type,
167 errstr, $1);
168 free($1);
169 YYERROR;
172 $$.tv_sec *= mul;
173 free($1);
177 main : LISTEN ON STRING {
178 if (!got_path_is_absolute($3))
179 yyerror("bad unix socket path \"%s\": "
180 "must be an absolute path", $3);
182 if (gotd_proc_id == PROC_LISTEN) {
183 if (strlcpy(gotd->unix_socket_path, $3,
184 sizeof(gotd->unix_socket_path)) >=
185 sizeof(gotd->unix_socket_path)) {
186 yyerror("%s: unix socket path too long",
187 __func__);
188 free($3);
189 YYERROR;
192 free($3);
194 | USER STRING {
195 if (strlcpy(gotd->user_name, $2,
196 sizeof(gotd->user_name)) >=
197 sizeof(gotd->user_name)) {
198 yyerror("%s: user name too long", __func__);
199 free($2);
200 YYERROR;
202 free($2);
204 | connection
207 connection : CONNECTION '{' optnl conflags_l '}'
208 | CONNECTION conflags
210 conflags_l : conflags optnl conflags_l
211 | conflags optnl
214 conflags : REQUEST TIMEOUT timeout {
215 if ($3.tv_sec <= 0) {
216 yyerror("invalid timeout: %lld", $3.tv_sec);
217 YYERROR;
219 memcpy(&gotd->request_timeout, &$3,
220 sizeof(gotd->request_timeout));
222 | LIMIT USER STRING NUMBER {
223 if (gotd_proc_id == PROC_LISTEN &&
224 conf_limit_user_connections($3, $4) == -1) {
225 free($3);
226 YYERROR;
228 free($3);
232 repository : REPOSITORY STRING {
233 struct gotd_repo *repo;
235 TAILQ_FOREACH(repo, &gotd->repos, entry) {
236 if (strcmp(repo->name, $2) == 0) {
237 yyerror("duplicate repository '%s'", $2);
238 free($2);
239 YYERROR;
243 if (gotd_proc_id == PROC_GOTD ||
244 gotd_proc_id == PROC_AUTH) {
245 new_repo = conf_new_repo($2);
247 free($2);
248 } '{' optnl repoopts2 '}' {
252 repoopts1 : PATH STRING {
253 if (gotd_proc_id == PROC_GOTD ||
254 gotd_proc_id == PROC_AUTH) {
255 if (!got_path_is_absolute($2)) {
256 yyerror("%s: path %s is not absolute",
257 __func__, $2);
258 free($2);
259 YYERROR;
261 if (realpath($2, new_repo->path) == NULL) {
262 yyerror("realpath %s: %s", $2, strerror(errno));
263 free($2);
264 YYERROR;
267 free($2);
269 | PERMIT RO STRING {
270 if (gotd_proc_id == PROC_AUTH) {
271 conf_new_access_rule(new_repo,
272 GOTD_ACCESS_PERMITTED, GOTD_AUTH_READ, $3);
275 | PERMIT RW STRING {
276 if (gotd_proc_id == PROC_AUTH) {
277 conf_new_access_rule(new_repo,
278 GOTD_ACCESS_PERMITTED,
279 GOTD_AUTH_READ | GOTD_AUTH_WRITE, $3);
282 | DENY STRING {
283 if (gotd_proc_id == PROC_AUTH) {
284 conf_new_access_rule(new_repo,
285 GOTD_ACCESS_DENIED, 0, $2);
290 repoopts2 : repoopts2 repoopts1 nl
291 | repoopts1 optnl
294 nl : '\n' optnl
297 optnl : '\n' optnl /* zero or more newlines */
298 | /* empty */
301 %%
303 struct keywords {
304 const char *k_name;
305 int k_val;
306 };
308 int
309 yyerror(const char *fmt, ...)
311 va_list ap;
312 char *msg;
314 file->errors++;
315 va_start(ap, fmt);
316 if (vasprintf(&msg, fmt, ap) == -1)
317 fatalx("yyerror vasprintf");
318 va_end(ap);
319 logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
320 free(msg);
321 return (0);
324 int
325 kw_cmp(const void *k, const void *e)
327 return (strcmp(k, ((const struct keywords *)e)->k_name));
330 int
331 lookup(char *s)
333 /* This has to be sorted always. */
334 static const struct keywords keywords[] = {
335 { "connection", CONNECTION },
336 { "deny", DENY },
337 { "limit", LIMIT },
338 { "listen", LISTEN },
339 { "on", ON },
340 { "path", PATH },
341 { "permit", PERMIT },
342 { "repository", REPOSITORY },
343 { "request", REQUEST },
344 { "ro", RO },
345 { "rw", RW },
346 { "timeout", TIMEOUT },
347 { "user", USER },
348 };
349 const struct keywords *p;
351 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
352 sizeof(keywords[0]), kw_cmp);
354 if (p)
355 return (p->k_val);
356 else
357 return (STRING);
360 #define MAXPUSHBACK 128
362 unsigned char *parsebuf;
363 int parseindex;
364 unsigned char pushback_buffer[MAXPUSHBACK];
365 int pushback_index = 0;
367 int
368 lgetc(int quotec)
370 int c, next;
372 if (parsebuf) {
373 /* Read character from the parsebuffer instead of input. */
374 if (parseindex >= 0) {
375 c = parsebuf[parseindex++];
376 if (c != '\0')
377 return (c);
378 parsebuf = NULL;
379 } else
380 parseindex++;
383 if (pushback_index)
384 return (pushback_buffer[--pushback_index]);
386 if (quotec) {
387 c = getc(file->stream);
388 if (c == EOF)
389 yyerror("reached end of file while parsing "
390 "quoted string");
391 return (c);
394 c = getc(file->stream);
395 while (c == '\\') {
396 next = getc(file->stream);
397 if (next != '\n') {
398 c = next;
399 break;
401 yylval.lineno = file->lineno;
402 file->lineno++;
403 c = getc(file->stream);
406 return (c);
409 int
410 lungetc(int c)
412 if (c == EOF)
413 return (EOF);
414 if (parsebuf) {
415 parseindex--;
416 if (parseindex >= 0)
417 return (c);
419 if (pushback_index < MAXPUSHBACK-1)
420 return (pushback_buffer[pushback_index++] = c);
421 else
422 return (EOF);
425 int
426 findeol(void)
428 int c;
430 parsebuf = NULL;
432 /* Skip to either EOF or the first real EOL. */
433 while (1) {
434 if (pushback_index)
435 c = pushback_buffer[--pushback_index];
436 else
437 c = lgetc(0);
438 if (c == '\n') {
439 file->lineno++;
440 break;
442 if (c == EOF)
443 break;
445 return (ERROR);
448 int
449 yylex(void)
451 unsigned char buf[8096];
452 unsigned char *p, *val;
453 int quotec, next, c;
454 int token;
456 top:
457 p = buf;
458 c = lgetc(0);
459 while (c == ' ' || c == '\t')
460 c = lgetc(0); /* nothing */
462 yylval.lineno = file->lineno;
463 if (c == '#') {
464 c = lgetc(0);
465 while (c != '\n' && c != EOF)
466 c = lgetc(0); /* nothing */
468 if (c == '$' && parsebuf == NULL) {
469 while (1) {
470 c = lgetc(0);
471 if (c == EOF)
472 return (0);
474 if (p + 1 >= buf + sizeof(buf) - 1) {
475 yyerror("string too long");
476 return (findeol());
478 if (isalnum(c) || c == '_') {
479 *p++ = c;
480 continue;
482 *p = '\0';
483 lungetc(c);
484 break;
486 val = symget(buf);
487 if (val == NULL) {
488 yyerror("macro '%s' not defined", buf);
489 return (findeol());
491 parsebuf = val;
492 parseindex = 0;
493 goto top;
496 switch (c) {
497 case '\'':
498 case '"':
499 quotec = c;
500 while (1) {
501 c = lgetc(quotec);
502 if (c == EOF)
503 return (0);
504 if (c == '\n') {
505 file->lineno++;
506 continue;
507 } else if (c == '\\') {
508 next = lgetc(quotec);
509 if (next == EOF)
510 return (0);
511 if (next == quotec || c == ' ' || c == '\t')
512 c = next;
513 else if (next == '\n') {
514 file->lineno++;
515 continue;
516 } else
517 lungetc(next);
518 } else if (c == quotec) {
519 *p = '\0';
520 break;
521 } else if (c == '\0') {
522 yyerror("syntax error");
523 return (findeol());
525 if (p + 1 >= buf + sizeof(buf) - 1) {
526 yyerror("string too long");
527 return (findeol());
529 *p++ = c;
531 yylval.v.string = strdup(buf);
532 if (yylval.v.string == NULL)
533 err(1, "yylex: strdup");
534 return (STRING);
537 #define allowed_to_end_number(x) \
538 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
540 if (c == '-' || isdigit(c)) {
541 do {
542 *p++ = c;
543 if ((unsigned)(p-buf) >= sizeof(buf)) {
544 yyerror("string too long");
545 return (findeol());
547 c = lgetc(0);
548 } while (c != EOF && isdigit(c));
549 lungetc(c);
550 if (p == buf + 1 && buf[0] == '-')
551 goto nodigits;
552 if (c == EOF || allowed_to_end_number(c)) {
553 const char *errstr = NULL;
555 *p = '\0';
556 yylval.v.number = strtonum(buf, LLONG_MIN,
557 LLONG_MAX, &errstr);
558 if (errstr) {
559 yyerror("\"%s\" invalid number: %s",
560 buf, errstr);
561 return (findeol());
563 return (NUMBER);
564 } else {
565 nodigits:
566 while (p > buf + 1)
567 lungetc(*--p);
568 c = *--p;
569 if (c == '-')
570 return (c);
574 #define allowed_in_string(x) \
575 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
576 x != '{' && x != '}' && \
577 x != '!' && x != '=' && x != '#' && \
578 x != ','))
580 if (isalnum(c) || c == ':' || c == '_') {
581 do {
582 *p++ = c;
583 if ((unsigned)(p-buf) >= sizeof(buf)) {
584 yyerror("string too long");
585 return (findeol());
587 c = lgetc(0);
588 } while (c != EOF && (allowed_in_string(c)));
589 lungetc(c);
590 *p = '\0';
591 token = lookup(buf);
592 if (token == STRING) {
593 yylval.v.string = strdup(buf);
594 if (yylval.v.string == NULL)
595 err(1, "yylex: strdup");
597 return (token);
599 if (c == '\n') {
600 yylval.lineno = file->lineno;
601 file->lineno++;
603 if (c == EOF)
604 return (0);
605 return (c);
608 int
609 check_file_secrecy(int fd, const char *fname)
611 struct stat st;
613 if (fstat(fd, &st)) {
614 log_warn("cannot stat %s", fname);
615 return (-1);
617 if (st.st_uid != 0 && st.st_uid != getuid()) {
618 log_warnx("%s: owner not root or current user", fname);
619 return (-1);
621 if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
622 log_warnx("%s: group writable or world read/writable", fname);
623 return (-1);
625 return (0);
628 struct file *
629 newfile(const char *name, int secret)
631 struct file *nfile;
633 nfile = calloc(1, sizeof(struct file));
634 if (nfile == NULL) {
635 log_warn("calloc");
636 return (NULL);
638 nfile->name = strdup(name);
639 if (nfile->name == NULL) {
640 log_warn("strdup");
641 free(nfile);
642 return (NULL);
644 nfile->stream = fopen(nfile->name, "r");
645 if (nfile->stream == NULL) {
646 log_warn("open %s", nfile->name);
647 free(nfile->name);
648 free(nfile);
649 return (NULL);
650 } else if (secret &&
651 check_file_secrecy(fileno(nfile->stream), nfile->name)) {
652 fclose(nfile->stream);
653 free(nfile->name);
654 free(nfile);
655 return (NULL);
657 nfile->lineno = 1;
658 return (nfile);
661 static void
662 closefile(struct file *xfile)
664 fclose(xfile->stream);
665 free(xfile->name);
666 free(xfile);
669 int
670 parse_config(const char *filename, enum gotd_procid proc_id,
671 struct gotd *env)
673 struct sym *sym, *next;
674 struct gotd_repo *repo;
676 memset(env, 0, sizeof(*env));
678 gotd = env;
679 gotd_proc_id = proc_id;
680 TAILQ_INIT(&gotd->repos);
682 /* Apply default values. */
683 if (strlcpy(gotd->unix_socket_path, GOTD_UNIX_SOCKET,
684 sizeof(gotd->unix_socket_path)) >= sizeof(gotd->unix_socket_path)) {
685 fprintf(stderr, "%s: unix socket path too long", __func__);
686 return -1;
688 if (strlcpy(gotd->user_name, GOTD_USER,
689 sizeof(gotd->user_name)) >= sizeof(gotd->user_name)) {
690 fprintf(stderr, "%s: user name too long", __func__);
691 return -1;
694 gotd->request_timeout.tv_sec = GOTD_DEFAULT_REQUEST_TIMEOUT;
695 gotd->request_timeout.tv_usec = 0;
697 file = newfile(filename, 0);
698 if (file == NULL)
699 return -1;
701 yyparse();
702 errors = file->errors;
703 closefile(file);
705 /* Free macros and check which have not been used. */
706 TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
707 if ((gotd->verbosity > 1) && !sym->used)
708 fprintf(stderr, "warning: macro '%s' not used\n",
709 sym->nam);
710 if (!sym->persist) {
711 free(sym->nam);
712 free(sym->val);
713 TAILQ_REMOVE(&symhead, sym, entry);
714 free(sym);
718 if (errors)
719 return (-1);
721 TAILQ_FOREACH(repo, &gotd->repos, entry) {
722 if (repo->path[0] == '\0') {
723 log_warnx("repository \"%s\": no path provided in "
724 "configuration file", repo->name);
725 return (-1);
729 if (proc_id == PROC_GOTD && TAILQ_EMPTY(&gotd->repos)) {
730 log_warnx("no repository defined in configuration file");
731 return (-1);
734 return (0);
737 static int
738 uid_connection_limit_cmp(const void *pa, const void *pb)
740 const struct gotd_uid_connection_limit *a = pa, *b = pb;
742 if (a->uid < b->uid)
743 return -1;
744 else if (a->uid > b->uid);
745 return 1;
747 return 0;
750 static int
751 conf_limit_user_connections(const char *user, int maximum)
753 uid_t uid;
754 struct gotd_uid_connection_limit *limit;
755 size_t nlimits;
757 if (maximum < 1) {
758 yyerror("max connections cannot be smaller 1");
759 return -1;
761 if (maximum > GOTD_MAXCLIENTS) {
762 yyerror("max connections must be <= %d", GOTD_MAXCLIENTS);
763 return -1;
766 if (gotd_auth_parseuid(user, &uid) == -1) {
767 yyerror("%s: no such user", user);
768 return -1;
771 limit = gotd_find_uid_connection_limit(gotd->connection_limits,
772 gotd->nconnection_limits, uid);
773 if (limit) {
774 limit->max_connections = maximum;
775 return 0;
778 limit = gotd->connection_limits;
779 nlimits = gotd->nconnection_limits + 1;
780 limit = reallocarray(limit, nlimits, sizeof(*limit));
781 if (limit == NULL)
782 fatal("reallocarray");
784 limit[nlimits - 1].uid = uid;
785 limit[nlimits - 1].max_connections = maximum;
787 gotd->connection_limits = limit;
788 gotd->nconnection_limits = nlimits;
789 qsort(gotd->connection_limits, gotd->nconnection_limits,
790 sizeof(gotd->connection_limits[0]), uid_connection_limit_cmp);
792 return 0;
795 static struct gotd_repo *
796 conf_new_repo(const char *name)
798 struct gotd_repo *repo;
800 if (name[0] == '\0') {
801 fatalx("syntax error: empty repository name found in %s",
802 file->name);
805 if (strchr(name, '\n') != NULL)
806 fatalx("repository names must not contain linefeeds: %s", name);
808 repo = calloc(1, sizeof(*repo));
809 if (repo == NULL)
810 fatalx("%s: calloc", __func__);
812 STAILQ_INIT(&repo->rules);
814 if (strlcpy(repo->name, name, sizeof(repo->name)) >=
815 sizeof(repo->name))
816 fatalx("%s: strlcpy", __func__);
818 TAILQ_INSERT_TAIL(&gotd->repos, repo, entry);
819 gotd->nrepos++;
821 return repo;
822 };
824 static void
825 conf_new_access_rule(struct gotd_repo *repo, enum gotd_access access,
826 int authorization, char *identifier)
828 struct gotd_access_rule *rule;
830 rule = calloc(1, sizeof(*rule));
831 if (rule == NULL)
832 fatal("calloc");
834 rule->access = access;
835 rule->authorization = authorization;
836 rule->identifier = identifier;
838 STAILQ_INSERT_TAIL(&repo->rules, rule, entry);
841 int
842 symset(const char *nam, const char *val, int persist)
844 struct sym *sym;
846 TAILQ_FOREACH(sym, &symhead, entry) {
847 if (strcmp(nam, sym->nam) == 0)
848 break;
851 if (sym != NULL) {
852 if (sym->persist == 1)
853 return (0);
854 else {
855 free(sym->nam);
856 free(sym->val);
857 TAILQ_REMOVE(&symhead, sym, entry);
858 free(sym);
861 sym = calloc(1, sizeof(*sym));
862 if (sym == NULL)
863 return (-1);
865 sym->nam = strdup(nam);
866 if (sym->nam == NULL) {
867 free(sym);
868 return (-1);
870 sym->val = strdup(val);
871 if (sym->val == NULL) {
872 free(sym->nam);
873 free(sym);
874 return (-1);
876 sym->used = 0;
877 sym->persist = persist;
878 TAILQ_INSERT_TAIL(&symhead, sym, entry);
879 return (0);
882 char *
883 symget(const char *nam)
885 struct sym *sym;
887 TAILQ_FOREACH(sym, &symhead, entry) {
888 if (strcmp(nam, sym->nam) == 0) {
889 sym->used = 1;
890 return (sym->val);
893 return (NULL);