bump version number
[got-portable.git] / gotwebd / parse.y
blobb0f3611d62197317b09162c820b05d4b23c3557e
1 /*
2 * Copyright (c) 2016-2019, 2020-2021 Tracey Emery <tracey@traceyemery.net>
3 * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
4 * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
5 * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
6 * Copyright (c) 2001 Markus Friedl. All rights reserved.
7 * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
8 * Copyright (c) 2001 Theo de Raadt. All rights reserved.
10 * Permission to use, copy, modify, and distribute this software for any
11 * purpose with or without fee is hereby granted, provided that the above
12 * copyright notice and this permission notice appear in all copies.
14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 #include "got_compat.h"
26 #include <sys/ioctl.h>
27 #include <sys/types.h>
28 #include <sys/queue.h>
29 #include <sys/socket.h>
30 #include <sys/stat.h>
31 #include <sys/un.h>
33 #include <net/if.h>
34 #include <netinet/in.h>
36 #include <arpa/inet.h>
38 #include <ctype.h>
39 #include <err.h>
40 #include <errno.h>
41 #include <event.h>
42 #include <ifaddrs.h>
43 #include <limits.h>
44 #include <netdb.h>
45 #include <stdarg.h>
46 #include <stdlib.h>
47 #include <stdio.h>
48 #include <string.h>
49 #include <syslog.h>
50 #include <unistd.h>
52 #include "got_reference.h"
54 #include "gotwebd.h"
55 #include "log.h"
57 TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
58 static struct file {
59 TAILQ_ENTRY(file) entry;
60 FILE *stream;
61 char *name;
62 int lineno;
63 int errors;
64 } *file;
65 struct file *newfile(const char *, int);
66 static void closefile(struct file *);
67 int check_file_secrecy(int, const char *);
68 int yyparse(void);
69 int yylex(void);
70 int yyerror(const char *, ...)
71 __attribute__((__format__ (printf, 1, 2)))
72 __attribute__((__nonnull__ (1)));
73 int kw_cmp(const void *, const void *);
74 int lookup(char *);
75 int lgetc(int);
76 int lungetc(int);
77 int findeol(void);
79 TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
80 struct sym {
81 TAILQ_ENTRY(sym) entry;
82 int used;
83 int persist;
84 char *nam;
85 char *val;
88 int symset(const char *, const char *, int);
89 char *symget(const char *);
91 static int errors;
93 static struct gotwebd *gotwebd;
94 static struct server *new_srv;
95 static struct server *conf_new_server(const char *);
96 int getservice(const char *);
97 int n;
99 int get_addrs(const char *, const char *);
100 int get_unix_addr(const char *);
101 int addr_dup_check(struct addresslist *, struct address *);
102 int add_addr(struct address *);
104 typedef struct {
105 union {
106 long long number;
107 char *string;
108 } v;
109 int lineno;
110 } YYSTYPE;
114 %token LISTEN WWW_PATH SITE_NAME SITE_OWNER SITE_LINK LOGO
115 %token LOGO_URL SHOW_REPO_OWNER SHOW_REPO_AGE SHOW_REPO_DESCRIPTION
116 %token MAX_REPOS_DISPLAY REPOS_PATH MAX_COMMITS_DISPLAY ON ERROR
117 %token SHOW_SITE_OWNER SHOW_REPO_CLONEURL PORT PREFORK RESPECT_EXPORTOK
118 %token SERVER CHROOT CUSTOM_CSS SOCKET
119 %token SUMMARY_COMMITS_DISPLAY SUMMARY_TAGS_DISPLAY USER
121 %token <v.string> STRING
122 %token <v.number> NUMBER
123 %type <v.number> boolean
124 %type <v.string> listen_addr
128 grammar : /* empty */
129 | grammar '\n'
130 | grammar varset '\n'
131 | grammar main '\n'
132 | grammar server '\n'
133 | grammar error '\n' { file->errors++; }
136 varset : STRING '=' STRING {
137 char *s = $1;
138 while (*s++) {
139 if (isspace((unsigned char)*s)) {
140 yyerror("macro name cannot contain "
141 "whitespace");
142 free($1);
143 free($3);
144 YYERROR;
147 if (symset($1, $3, 0) == -1)
148 fatal("cannot store variable");
149 free($1);
150 free($3);
154 boolean : STRING {
155 if (strcasecmp($1, "1") == 0 ||
156 strcasecmp($1, "on") == 0)
157 $$ = 1;
158 else if (strcasecmp($1, "0") == 0 ||
159 strcasecmp($1, "off") == 0)
160 $$ = 0;
161 else {
162 yyerror("invalid boolean value '%s'", $1);
163 free($1);
164 YYERROR;
166 free($1);
168 | ON { $$ = 1; }
169 | NUMBER {
170 if ($1 != 0 && $1 != 1) {
171 yyerror("invalid boolean value '%lld'", $1);
172 YYERROR;
174 $$ = $1;
178 listen_addr : '*' { $$ = NULL; }
179 | STRING
182 main : PREFORK NUMBER {
183 if ($2 <= 0 || $2 > PROC_MAX_INSTANCES) {
184 yyerror("prefork is %s: %lld",
185 $2 <= 0 ? "too small" : "too large", $2);
186 YYERROR;
188 gotwebd->prefork_gotwebd = $2;
190 | CHROOT STRING {
191 if (*$2 == '\0') {
192 yyerror("chroot path can't be an empty"
193 " string");
194 free($2);
195 YYERROR;
198 n = strlcpy(gotwebd->httpd_chroot, $2,
199 sizeof(gotwebd->httpd_chroot));
200 if (n >= sizeof(gotwebd->httpd_chroot)) {
201 yyerror("%s: httpd_chroot truncated", __func__);
202 free($2);
203 YYERROR;
205 free($2);
207 | LISTEN ON listen_addr PORT STRING {
208 if (get_addrs($3, $5) == -1) {
209 yyerror("could not get addrs");
210 YYERROR;
212 free($3);
213 free($5);
215 | LISTEN ON listen_addr PORT NUMBER {
216 char portno[32];
217 int n;
219 n = snprintf(portno, sizeof(portno), "%lld",
220 (long long)$5);
221 if (n < 0 || (size_t)n >= sizeof(portno))
222 fatalx("port number too long: %lld",
223 (long long)$5);
225 if (get_addrs($3, portno) == -1) {
226 yyerror("could not get addrs");
227 YYERROR;
229 free($3);
231 | LISTEN ON SOCKET STRING {
232 if (get_unix_addr($4) == -1) {
233 yyerror("can't listen on %s", $4);
234 free($4);
235 YYERROR;
237 free($4);
239 | USER STRING {
240 if (gotwebd->user != NULL)
241 yyerror("user already specified");
242 free(gotwebd->user);
243 gotwebd->user = $2;
247 server : SERVER STRING {
248 struct server *srv;
250 TAILQ_FOREACH(srv, &gotwebd->servers, entry) {
251 if (strcmp(srv->name, $2) == 0) {
252 yyerror("server name exists '%s'", $2);
253 free($2);
254 YYERROR;
258 new_srv = conf_new_server($2);
259 log_debug("adding server %s", $2);
260 free($2);
262 | SERVER STRING {
263 struct server *srv;
265 TAILQ_FOREACH(srv, &gotwebd->servers, entry) {
266 if (strcmp(srv->name, $2) == 0) {
267 yyerror("server name exists '%s'", $2);
268 free($2);
269 YYERROR;
273 new_srv = conf_new_server($2);
274 log_debug("adding server %s", $2);
275 free($2);
276 } '{' optnl serveropts2 '}' {
280 serveropts1 : REPOS_PATH STRING {
281 n = strlcpy(new_srv->repos_path, $2,
282 sizeof(new_srv->repos_path));
283 if (n >= sizeof(new_srv->repos_path)) {
284 yyerror("%s: repos_path truncated", __func__);
285 free($2);
286 YYERROR;
288 free($2);
290 | SITE_NAME STRING {
291 n = strlcpy(new_srv->site_name, $2,
292 sizeof(new_srv->site_name));
293 if (n >= sizeof(new_srv->site_name)) {
294 yyerror("%s: site_name truncated", __func__);
295 free($2);
296 YYERROR;
298 free($2);
300 | SITE_OWNER STRING {
301 n = strlcpy(new_srv->site_owner, $2,
302 sizeof(new_srv->site_owner));
303 if (n >= sizeof(new_srv->site_owner)) {
304 yyerror("%s: site_owner truncated", __func__);
305 free($2);
306 YYERROR;
308 free($2);
310 | SITE_LINK STRING {
311 n = strlcpy(new_srv->site_link, $2,
312 sizeof(new_srv->site_link));
313 if (n >= sizeof(new_srv->site_link)) {
314 yyerror("%s: site_link truncated", __func__);
315 free($2);
316 YYERROR;
318 free($2);
320 | LOGO STRING {
321 n = strlcpy(new_srv->logo, $2, sizeof(new_srv->logo));
322 if (n >= sizeof(new_srv->logo)) {
323 yyerror("%s: logo truncated", __func__);
324 free($2);
325 YYERROR;
327 free($2);
329 | LOGO_URL STRING {
330 n = strlcpy(new_srv->logo_url, $2,
331 sizeof(new_srv->logo_url));
332 if (n >= sizeof(new_srv->logo_url)) {
333 yyerror("%s: logo_url truncated", __func__);
334 free($2);
335 YYERROR;
337 free($2);
339 | CUSTOM_CSS STRING {
340 n = strlcpy(new_srv->custom_css, $2,
341 sizeof(new_srv->custom_css));
342 if (n >= sizeof(new_srv->custom_css)) {
343 yyerror("%s: custom_css truncated", __func__);
344 free($2);
345 YYERROR;
347 free($2);
349 | SHOW_SITE_OWNER boolean {
350 new_srv->show_site_owner = $2;
352 | SHOW_REPO_OWNER boolean {
353 new_srv->show_repo_owner = $2;
355 | SHOW_REPO_AGE boolean {
356 new_srv->show_repo_age = $2;
358 | SHOW_REPO_DESCRIPTION boolean {
359 new_srv->show_repo_description = $2;
361 | SHOW_REPO_CLONEURL boolean {
362 new_srv->show_repo_cloneurl = $2;
364 | RESPECT_EXPORTOK boolean {
365 new_srv->respect_exportok = $2;
367 | MAX_REPOS_DISPLAY NUMBER {
368 if ($2 < 0) {
369 yyerror("max_repos_display is too small: %lld",
370 $2);
371 YYERROR;
373 new_srv->max_repos_display = $2;
375 | MAX_COMMITS_DISPLAY NUMBER {
376 if ($2 <= 1) {
377 yyerror("max_commits_display is too small:"
378 " %lld", $2);
379 YYERROR;
381 new_srv->max_commits_display = $2;
383 | SUMMARY_COMMITS_DISPLAY NUMBER {
384 if ($2 < 1) {
385 yyerror("summary_commits_display is too small:"
386 " %lld", $2);
387 YYERROR;
389 new_srv->summary_commits_display = $2;
391 | SUMMARY_TAGS_DISPLAY NUMBER {
392 if ($2 < 1) {
393 yyerror("summary_tags_display is too small:"
394 " %lld", $2);
395 YYERROR;
397 new_srv->summary_tags_display = $2;
401 serveropts2 : serveropts2 serveropts1 nl
402 | serveropts1 optnl
405 nl : '\n' optnl
408 optnl : '\n' optnl /* zero or more newlines */
409 | /* empty */
414 struct keywords {
415 const char *k_name;
416 int k_val;
420 yyerror(const char *fmt, ...)
422 va_list ap;
423 char *msg;
425 file->errors++;
426 va_start(ap, fmt);
427 if (vasprintf(&msg, fmt, ap) == -1)
428 fatalx("yyerror vasprintf");
429 va_end(ap);
430 logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
431 free(msg);
432 return (0);
436 kw_cmp(const void *k, const void *e)
438 return (strcmp(k, ((const struct keywords *)e)->k_name));
442 lookup(char *s)
444 /* This has to be sorted always. */
445 static const struct keywords keywords[] = {
446 { "chroot", CHROOT },
447 { "custom_css", CUSTOM_CSS },
448 { "listen", LISTEN },
449 { "logo", LOGO },
450 { "logo_url", LOGO_URL },
451 { "max_commits_display", MAX_COMMITS_DISPLAY },
452 { "max_repos_display", MAX_REPOS_DISPLAY },
453 { "on", ON },
454 { "port", PORT },
455 { "prefork", PREFORK },
456 { "repos_path", REPOS_PATH },
457 { "respect_exportok", RESPECT_EXPORTOK },
458 { "server", SERVER },
459 { "show_repo_age", SHOW_REPO_AGE },
460 { "show_repo_cloneurl", SHOW_REPO_CLONEURL },
461 { "show_repo_description", SHOW_REPO_DESCRIPTION },
462 { "show_repo_owner", SHOW_REPO_OWNER },
463 { "show_site_owner", SHOW_SITE_OWNER },
464 { "site_link", SITE_LINK },
465 { "site_name", SITE_NAME },
466 { "site_owner", SITE_OWNER },
467 { "socket", SOCKET },
468 { "summary_commits_display", SUMMARY_COMMITS_DISPLAY },
469 { "summary_tags_display", SUMMARY_TAGS_DISPLAY },
470 { "user", USER },
472 const struct keywords *p;
474 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
475 sizeof(keywords[0]), kw_cmp);
477 if (p)
478 return (p->k_val);
479 else
480 return (STRING);
483 #define MAXPUSHBACK 128
485 unsigned char *parsebuf;
486 int parseindex;
487 unsigned char pushback_buffer[MAXPUSHBACK];
488 int pushback_index = 0;
491 lgetc(int quotec)
493 int c, next;
495 if (parsebuf) {
496 /* Read character from the parsebuffer instead of input. */
497 if (parseindex >= 0) {
498 c = parsebuf[parseindex++];
499 if (c != '\0')
500 return (c);
501 parsebuf = NULL;
502 } else
503 parseindex++;
506 if (pushback_index)
507 return (pushback_buffer[--pushback_index]);
509 if (quotec) {
510 c = getc(file->stream);
511 if (c == EOF)
512 yyerror("reached end of file while parsing "
513 "quoted string");
514 return (c);
517 c = getc(file->stream);
518 while (c == '\\') {
519 next = getc(file->stream);
520 if (next != '\n') {
521 c = next;
522 break;
524 yylval.lineno = file->lineno;
525 file->lineno++;
526 c = getc(file->stream);
529 return (c);
533 lungetc(int c)
535 if (c == EOF)
536 return (EOF);
537 if (parsebuf) {
538 parseindex--;
539 if (parseindex >= 0)
540 return (c);
542 if (pushback_index < MAXPUSHBACK-1)
543 return (pushback_buffer[pushback_index++] = c);
544 else
545 return (EOF);
549 findeol(void)
551 int c;
553 parsebuf = NULL;
555 /* Skip to either EOF or the first real EOL. */
556 while (1) {
557 if (pushback_index)
558 c = pushback_buffer[--pushback_index];
559 else
560 c = lgetc(0);
561 if (c == '\n') {
562 file->lineno++;
563 break;
565 if (c == EOF)
566 break;
568 return (ERROR);
572 yylex(void)
574 unsigned char buf[8096];
575 unsigned char *p, *val;
576 int quotec, next, c;
577 int token;
579 top:
580 p = buf;
581 c = lgetc(0);
582 while (c == ' ' || c == '\t')
583 c = lgetc(0); /* nothing */
585 yylval.lineno = file->lineno;
586 if (c == '#') {
587 c = lgetc(0);
588 while (c != '\n' && c != EOF)
589 c = lgetc(0); /* nothing */
591 if (c == '$' && parsebuf == NULL) {
592 while (1) {
593 c = lgetc(0);
594 if (c == EOF)
595 return (0);
597 if (p + 1 >= buf + sizeof(buf) - 1) {
598 yyerror("string too long");
599 return (findeol());
601 if (isalnum(c) || c == '_') {
602 *p++ = c;
603 continue;
605 *p = '\0';
606 lungetc(c);
607 break;
609 val = symget(buf);
610 if (val == NULL) {
611 yyerror("macro '%s' not defined", buf);
612 return (findeol());
614 parsebuf = val;
615 parseindex = 0;
616 goto top;
619 switch (c) {
620 case '\'':
621 case '"':
622 quotec = c;
623 while (1) {
624 c = lgetc(quotec);
625 if (c == EOF)
626 return (0);
627 if (c == '\n') {
628 file->lineno++;
629 continue;
630 } else if (c == '\\') {
631 next = lgetc(quotec);
632 if (next == EOF)
633 return (0);
634 if (next == quotec || c == ' ' || c == '\t')
635 c = next;
636 else if (next == '\n') {
637 file->lineno++;
638 continue;
639 } else
640 lungetc(next);
641 } else if (c == quotec) {
642 *p = '\0';
643 break;
644 } else if (c == '\0') {
645 yyerror("syntax error");
646 return (findeol());
648 if (p + 1 >= buf + sizeof(buf) - 1) {
649 yyerror("string too long");
650 return (findeol());
652 *p++ = c;
654 yylval.v.string = strdup(buf);
655 if (yylval.v.string == NULL)
656 err(1, "yylex: strdup");
657 return (STRING);
660 #define allowed_to_end_number(x) \
661 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
663 if (c == '-' || isdigit(c)) {
664 do {
665 *p++ = c;
666 if ((unsigned)(p-buf) >= sizeof(buf)) {
667 yyerror("string too long");
668 return (findeol());
670 c = lgetc(0);
671 } while (c != EOF && isdigit(c));
672 lungetc(c);
673 if (p == buf + 1 && buf[0] == '-')
674 goto nodigits;
675 if (c == EOF || allowed_to_end_number(c)) {
676 const char *errstr = NULL;
678 *p = '\0';
679 yylval.v.number = strtonum(buf, LLONG_MIN,
680 LLONG_MAX, &errstr);
681 if (errstr) {
682 yyerror("\"%s\" invalid number: %s",
683 buf, errstr);
684 return (findeol());
686 return (NUMBER);
687 } else {
688 nodigits:
689 while (p > buf + 1)
690 lungetc(*--p);
691 c = *--p;
692 if (c == '-')
693 return (c);
697 #define allowed_in_string(x) \
698 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
699 x != '{' && x != '}' && \
700 x != '!' && x != '=' && x != '#' && \
701 x != ','))
703 if (isalnum(c) || c == ':' || c == '_') {
704 do {
705 *p++ = c;
706 if ((unsigned)(p-buf) >= sizeof(buf)) {
707 yyerror("string too long");
708 return (findeol());
710 c = lgetc(0);
711 } while (c != EOF && (allowed_in_string(c)));
712 lungetc(c);
713 *p = '\0';
714 token = lookup(buf);
715 if (token == STRING) {
716 yylval.v.string = strdup(buf);
717 if (yylval.v.string == NULL)
718 err(1, "yylex: strdup");
720 return (token);
722 if (c == '\n') {
723 yylval.lineno = file->lineno;
724 file->lineno++;
726 if (c == EOF)
727 return (0);
728 return (c);
732 check_file_secrecy(int fd, const char *fname)
734 struct stat st;
736 if (fstat(fd, &st)) {
737 log_warn("cannot stat %s", fname);
738 return (-1);
740 if (st.st_uid != 0 && st.st_uid != getuid()) {
741 log_warnx("%s: owner not root or current user", fname);
742 return (-1);
744 if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
745 log_warnx("%s: group writable or world read/writable", fname);
746 return (-1);
748 return (0);
751 struct file *
752 newfile(const char *name, int secret)
754 struct file *nfile;
756 nfile = calloc(1, sizeof(struct file));
757 if (nfile == NULL) {
758 log_warn("calloc");
759 return (NULL);
761 nfile->name = strdup(name);
762 if (nfile->name == NULL) {
763 log_warn("strdup");
764 free(nfile);
765 return (NULL);
767 nfile->stream = fopen(nfile->name, "r");
768 if (nfile->stream == NULL) {
769 /* no warning, we don't require a conf file */
770 free(nfile->name);
771 free(nfile);
772 return (NULL);
773 } else if (secret &&
774 check_file_secrecy(fileno(nfile->stream), nfile->name)) {
775 fclose(nfile->stream);
776 free(nfile->name);
777 free(nfile);
778 return (NULL);
780 nfile->lineno = 1;
781 return (nfile);
784 static void
785 closefile(struct file *xfile)
787 fclose(xfile->stream);
788 free(xfile->name);
789 free(xfile);
792 static void
793 add_default_server(void)
795 new_srv = conf_new_server(D_SITENAME);
796 log_debug("%s: adding default server %s", __func__, D_SITENAME);
800 parse_config(const char *filename, struct gotwebd *env)
802 struct sym *sym, *next;
804 if (config_init(env) == -1)
805 fatalx("failed to initialize configuration");
807 gotwebd = env;
809 file = newfile(filename, 0);
810 if (file != NULL) {
811 /* we don't require a config file */
812 yyparse();
813 errors = file->errors;
814 closefile(file);
817 /* Free macros and check which have not been used. */
818 TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
819 if ((gotwebd->gotwebd_verbose > 1) && !sym->used)
820 fprintf(stderr, "warning: macro '%s' not used\n",
821 sym->nam);
822 if (!sym->persist) {
823 free(sym->nam);
824 free(sym->val);
825 TAILQ_REMOVE(&symhead, sym, entry);
826 free(sym);
830 /* just add default server if no config specified */
831 if (gotwebd->server_cnt == 0)
832 add_default_server();
834 /* add the implicit listen on socket */
835 if (TAILQ_EMPTY(&gotwebd->addresses)) {
836 const char *path = D_HTTPD_CHROOT D_UNIX_SOCKET;
837 if (get_unix_addr(path) == -1)
838 yyerror("can't listen on %s", path);
841 if (errors)
842 return (-1);
844 /* setup our listening sockets */
845 sockets_parse_sockets(env);
847 return (0);
850 struct server *
851 conf_new_server(const char *name)
853 struct server *srv = NULL;
855 srv = calloc(1, sizeof(*srv));
856 if (srv == NULL)
857 fatalx("%s: calloc", __func__);
859 n = strlcpy(srv->name, name, sizeof(srv->name));
860 if (n >= sizeof(srv->name))
861 fatalx("%s: strlcpy", __func__);
862 n = strlcpy(srv->repos_path, D_GOTPATH,
863 sizeof(srv->repos_path));
864 if (n >= sizeof(srv->repos_path))
865 fatalx("%s: strlcpy", __func__);
866 n = strlcpy(srv->site_name, D_SITENAME,
867 sizeof(srv->site_name));
868 if (n >= sizeof(srv->site_name))
869 fatalx("%s: strlcpy", __func__);
870 n = strlcpy(srv->site_owner, D_SITEOWNER,
871 sizeof(srv->site_owner));
872 if (n >= sizeof(srv->site_owner))
873 fatalx("%s: strlcpy", __func__);
874 n = strlcpy(srv->site_link, D_SITELINK,
875 sizeof(srv->site_link));
876 if (n >= sizeof(srv->site_link))
877 fatalx("%s: strlcpy", __func__);
878 n = strlcpy(srv->logo, D_GOTLOGO,
879 sizeof(srv->logo));
880 if (n >= sizeof(srv->logo))
881 fatalx("%s: strlcpy", __func__);
882 n = strlcpy(srv->logo_url, D_GOTURL, sizeof(srv->logo_url));
883 if (n >= sizeof(srv->logo_url))
884 fatalx("%s: strlcpy", __func__);
885 n = strlcpy(srv->custom_css, D_GOTWEBCSS, sizeof(srv->custom_css));
886 if (n >= sizeof(srv->custom_css))
887 fatalx("%s: strlcpy", __func__);
889 srv->show_site_owner = D_SHOWSOWNER;
890 srv->show_repo_owner = D_SHOWROWNER;
891 srv->show_repo_age = D_SHOWAGE;
892 srv->show_repo_description = D_SHOWDESC;
893 srv->show_repo_cloneurl = D_SHOWURL;
894 srv->respect_exportok = D_RESPECTEXPORTOK;
896 srv->max_repos_display = D_MAXREPODISP;
897 srv->max_commits_display = D_MAXCOMMITDISP;
898 srv->summary_commits_display = D_MAXSLCOMMDISP;
899 srv->summary_tags_display = D_MAXSLTAGDISP;
901 TAILQ_INSERT_TAIL(&gotwebd->servers, srv, entry);
902 gotwebd->server_cnt++;
904 return srv;
908 symset(const char *nam, const char *val, int persist)
910 struct sym *sym;
912 TAILQ_FOREACH(sym, &symhead, entry) {
913 if (strcmp(nam, sym->nam) == 0)
914 break;
917 if (sym != NULL) {
918 if (sym->persist == 1)
919 return (0);
920 else {
921 free(sym->nam);
922 free(sym->val);
923 TAILQ_REMOVE(&symhead, sym, entry);
924 free(sym);
927 sym = calloc(1, sizeof(*sym));
928 if (sym == NULL)
929 return (-1);
931 sym->nam = strdup(nam);
932 if (sym->nam == NULL) {
933 free(sym);
934 return (-1);
936 sym->val = strdup(val);
937 if (sym->val == NULL) {
938 free(sym->nam);
939 free(sym);
940 return (-1);
942 sym->used = 0;
943 sym->persist = persist;
944 TAILQ_INSERT_TAIL(&symhead, sym, entry);
945 return (0);
949 cmdline_symset(char *s)
951 char *sym, *val;
952 int ret;
954 val = strrchr(s, '=');
955 if (val == NULL)
956 return (-1);
958 sym = strndup(s, val - s);
959 if (sym == NULL)
960 fatal("%s: strndup", __func__);
962 ret = symset(sym, val + 1, 1);
963 free(sym);
965 return (ret);
968 char *
969 symget(const char *nam)
971 struct sym *sym;
973 TAILQ_FOREACH(sym, &symhead, entry) {
974 if (strcmp(nam, sym->nam) == 0) {
975 sym->used = 1;
976 return (sym->val);
979 return (NULL);
983 get_addrs(const char *hostname, const char *servname)
985 struct addrinfo hints, *res0, *res;
986 int error;
987 struct sockaddr_in *sin;
988 struct sockaddr_in6 *sin6;
989 struct address *h;
991 memset(&hints, 0, sizeof(hints));
992 hints.ai_family = AF_UNSPEC;
993 hints.ai_socktype = SOCK_STREAM;
994 hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
995 error = getaddrinfo(hostname, servname, &hints, &res0);
996 if (error) {
997 log_warnx("%s: could not parse \"%s:%s\": %s", __func__,
998 hostname, servname, gai_strerror(error));
999 return (-1);
1002 for (res = res0; res; res = res->ai_next) {
1003 if ((h = calloc(1, sizeof(*h))) == NULL)
1004 fatal(__func__);
1006 if (hostname == NULL) {
1007 strlcpy(h->ifname, "*", sizeof(h->ifname));
1008 } else {
1009 if (strlcpy(h->ifname, hostname, sizeof(h->ifname)) >=
1010 sizeof(h->ifname)) {
1011 log_warnx("%s: address truncated: %s",
1012 __func__, hostname);
1013 freeaddrinfo(res0);
1014 free(h);
1015 return (-1);
1019 h->ai_family = res->ai_family;
1020 h->ai_socktype = res->ai_socktype;
1021 h->ai_protocol = res->ai_protocol;
1022 memcpy(&h->ss, res->ai_addr, res->ai_addrlen);
1023 h->slen = res->ai_addrlen;
1025 switch (res->ai_family) {
1026 case AF_INET:
1027 sin = (struct sockaddr_in *)res->ai_addr;
1028 h->port = ntohs(sin->sin_port);
1029 break;
1030 case AF_INET6:
1031 sin6 = (struct sockaddr_in6 *)res->ai_addr;
1032 h->port = ntohs(sin6->sin6_port);
1033 break;
1034 default:
1035 fatalx("unknown address family %d", res->ai_family);
1038 if (add_addr(h) == -1) {
1039 freeaddrinfo(res0);
1040 return -1;
1043 freeaddrinfo(res0);
1044 return (0);
1048 get_unix_addr(const char *path)
1050 struct address *h;
1051 struct sockaddr_un *sun;
1053 if ((h = calloc(1, sizeof(*h))) == NULL)
1054 fatal("%s: calloc", __func__);
1056 h->ai_family = AF_UNIX;
1057 h->ai_socktype = SOCK_STREAM;
1058 h->ai_protocol = PF_UNSPEC;
1059 h->slen = sizeof(*sun);
1061 sun = (struct sockaddr_un *)&h->ss;
1062 sun->sun_family = AF_UNIX;
1063 if (strlcpy(sun->sun_path, path, sizeof(sun->sun_path)) >=
1064 sizeof(sun->sun_path)) {
1065 log_warnx("socket path too long: %s", sun->sun_path);
1066 return (-1);
1069 return add_addr(h);
1073 addr_dup_check(struct addresslist *al, struct address *h)
1075 struct address *a;
1077 TAILQ_FOREACH(a, al, entry) {
1078 if (a->ai_family != h->ai_family ||
1079 a->ai_socktype != h->ai_socktype ||
1080 a->ai_protocol != h->ai_protocol ||
1081 a->slen != h->slen ||
1082 memcmp(&a->ss, &h->ss, a->slen) != 0)
1083 continue;
1084 return -1;
1087 return 0;
1091 add_addr(struct address *h)
1093 if (addr_dup_check(&gotwebd->addresses, h) == 0) {
1094 TAILQ_INSERT_TAIL(&gotwebd->addresses, h, entry);
1095 return (0);
1098 free(h);
1099 return (0);