Blob


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.
9 *
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.
13 *
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.
21 */
23 %{
24 #include <sys/ioctl.h>
25 #include <sys/types.h>
26 #include <sys/queue.h>
27 #include <sys/socket.h>
28 #include <sys/stat.h>
29 #include <sys/un.h>
31 #include <net/if.h>
32 #include <netinet/in.h>
34 #include <arpa/inet.h>
36 #include <ctype.h>
37 #include <err.h>
38 #include <errno.h>
39 #include <event.h>
40 #include <ifaddrs.h>
41 #include <imsg.h>
42 #include <limits.h>
43 #include <netdb.h>
44 #include <stdarg.h>
45 #include <stdlib.h>
46 #include <stdio.h>
47 #include <string.h>
48 #include <syslog.h>
49 #include <unistd.h>
51 #include "got_reference.h"
53 #include "gotwebd.h"
54 #include "log.h"
56 TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
57 static struct file {
58 TAILQ_ENTRY(file) entry;
59 FILE *stream;
60 char *name;
61 int lineno;
62 int errors;
63 } *file;
64 struct file *newfile(const char *, int);
65 static void closefile(struct file *);
66 int check_file_secrecy(int, const char *);
67 int yyparse(void);
68 int yylex(void);
69 int yyerror(const char *, ...)
70 __attribute__((__format__ (printf, 1, 2)))
71 __attribute__((__nonnull__ (1)));
72 int kw_cmp(const void *, const void *);
73 int lookup(char *);
74 int lgetc(int);
75 int lungetc(int);
76 int findeol(void);
78 TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
79 struct sym {
80 TAILQ_ENTRY(sym) entry;
81 int used;
82 int persist;
83 char *nam;
84 char *val;
85 };
87 int symset(const char *, const char *, int);
88 char *symget(const char *);
90 static int errors;
92 static struct gotwebd *gotwebd;
93 static struct server *new_srv;
94 static struct server *conf_new_server(const char *);
95 int getservice(const char *);
96 int n;
98 int get_addrs(const char *, const char *);
99 int get_unix_addr(const char *);
100 int addr_dup_check(struct addresslist *, struct address *);
101 int add_addr(struct address *);
103 typedef struct {
104 union {
105 long long number;
106 char *string;
107 } v;
108 int lineno;
109 } YYSTYPE;
111 %}
113 %token LISTEN WWW_PATH SITE_NAME SITE_OWNER SITE_LINK LOGO
114 %token LOGO_URL SHOW_REPO_OWNER SHOW_REPO_AGE SHOW_REPO_DESCRIPTION
115 %token MAX_REPOS_DISPLAY REPOS_PATH MAX_COMMITS_DISPLAY ON ERROR
116 %token SHOW_SITE_OWNER SHOW_REPO_CLONEURL PORT PREFORK RESPECT_EXPORTOK
117 %token SERVER CHROOT CUSTOM_CSS SOCKET
118 %token SUMMARY_COMMITS_DISPLAY SUMMARY_TAGS_DISPLAY USER
120 %token <v.string> STRING
121 %token <v.number> NUMBER
122 %type <v.number> boolean
123 %type <v.string> listen_addr
125 %%
127 grammar : /* empty */
128 | grammar '\n'
129 | grammar varset '\n'
130 | grammar main '\n'
131 | grammar server '\n'
132 | grammar error '\n' { file->errors++; }
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 boolean : STRING {
154 if (strcasecmp($1, "1") == 0 ||
155 strcasecmp($1, "on") == 0)
156 $$ = 1;
157 else if (strcasecmp($1, "0") == 0 ||
158 strcasecmp($1, "off") == 0)
159 $$ = 0;
160 else {
161 yyerror("invalid boolean value '%s'", $1);
162 free($1);
163 YYERROR;
165 free($1);
167 | ON { $$ = 1; }
168 | NUMBER {
169 if ($1 != 0 && $1 != 1) {
170 yyerror("invalid boolean value '%lld'", $1);
171 YYERROR;
173 $$ = $1;
177 listen_addr : '*' { $$ = NULL; }
178 | STRING
181 main : PREFORK NUMBER {
182 if ($2 <= 0 || $2 > PROC_MAX_INSTANCES) {
183 yyerror("prefork is %s: %lld",
184 $2 <= 0 ? "too small" : "too large", $2);
185 YYERROR;
187 gotwebd->prefork_gotwebd = $2;
189 | CHROOT STRING {
190 if (*$2 == '\0') {
191 yyerror("chroot path can't be an empty"
192 " string");
193 free($2);
194 YYERROR;
197 n = strlcpy(gotwebd->httpd_chroot, $2,
198 sizeof(gotwebd->httpd_chroot));
199 if (n >= sizeof(gotwebd->httpd_chroot)) {
200 yyerror("%s: httpd_chroot truncated", __func__);
201 free($2);
202 YYERROR;
204 free($2);
206 | LISTEN ON listen_addr PORT STRING {
207 if (get_addrs($3, $5) == -1) {
208 yyerror("could not get addrs");
209 YYERROR;
211 free($3);
212 free($5);
214 | LISTEN ON listen_addr PORT NUMBER {
215 char portno[32];
216 int n;
218 n = snprintf(portno, sizeof(portno), "%lld",
219 (long long)$5);
220 if (n < 0 || (size_t)n >= sizeof(portno))
221 fatalx("port number too long: %lld",
222 (long long)$5);
224 if (get_addrs($3, portno) == -1) {
225 yyerror("could not get addrs");
226 YYERROR;
228 free($3);
230 | LISTEN ON SOCKET STRING {
231 if (get_unix_addr($4) == -1) {
232 yyerror("can't listen on %s", $4);
233 free($4);
234 YYERROR;
236 free($4);
238 | USER STRING {
239 if (gotwebd->user != NULL)
240 yyerror("user already specified");
241 free(gotwebd->user);
242 gotwebd->user = $2;
246 server : SERVER STRING {
247 struct server *srv;
249 TAILQ_FOREACH(srv, &gotwebd->servers, entry) {
250 if (strcmp(srv->name, $2) == 0) {
251 yyerror("server name exists '%s'", $2);
252 free($2);
253 YYERROR;
257 new_srv = conf_new_server($2);
258 log_debug("adding server %s", $2);
259 free($2);
261 | SERVER STRING {
262 struct server *srv;
264 TAILQ_FOREACH(srv, &gotwebd->servers, entry) {
265 if (strcmp(srv->name, $2) == 0) {
266 yyerror("server name exists '%s'", $2);
267 free($2);
268 YYERROR;
272 new_srv = conf_new_server($2);
273 log_debug("adding server %s", $2);
274 free($2);
275 } '{' optnl serveropts2 '}' {
279 serveropts1 : REPOS_PATH STRING {
280 n = strlcpy(new_srv->repos_path, $2,
281 sizeof(new_srv->repos_path));
282 if (n >= sizeof(new_srv->repos_path)) {
283 yyerror("%s: repos_path truncated", __func__);
284 free($2);
285 YYERROR;
287 free($2);
289 | SITE_NAME STRING {
290 n = strlcpy(new_srv->site_name, $2,
291 sizeof(new_srv->site_name));
292 if (n >= sizeof(new_srv->site_name)) {
293 yyerror("%s: site_name truncated", __func__);
294 free($2);
295 YYERROR;
297 free($2);
299 | SITE_OWNER STRING {
300 n = strlcpy(new_srv->site_owner, $2,
301 sizeof(new_srv->site_owner));
302 if (n >= sizeof(new_srv->site_owner)) {
303 yyerror("%s: site_owner truncated", __func__);
304 free($2);
305 YYERROR;
307 free($2);
309 | SITE_LINK STRING {
310 n = strlcpy(new_srv->site_link, $2,
311 sizeof(new_srv->site_link));
312 if (n >= sizeof(new_srv->site_link)) {
313 yyerror("%s: site_link truncated", __func__);
314 free($2);
315 YYERROR;
317 free($2);
319 | LOGO STRING {
320 n = strlcpy(new_srv->logo, $2, sizeof(new_srv->logo));
321 if (n >= sizeof(new_srv->logo)) {
322 yyerror("%s: logo truncated", __func__);
323 free($2);
324 YYERROR;
326 free($2);
328 | LOGO_URL STRING {
329 n = strlcpy(new_srv->logo_url, $2,
330 sizeof(new_srv->logo_url));
331 if (n >= sizeof(new_srv->logo_url)) {
332 yyerror("%s: logo_url truncated", __func__);
333 free($2);
334 YYERROR;
336 free($2);
338 | CUSTOM_CSS STRING {
339 n = strlcpy(new_srv->custom_css, $2,
340 sizeof(new_srv->custom_css));
341 if (n >= sizeof(new_srv->custom_css)) {
342 yyerror("%s: custom_css truncated", __func__);
343 free($2);
344 YYERROR;
346 free($2);
348 | SHOW_SITE_OWNER boolean {
349 new_srv->show_site_owner = $2;
351 | SHOW_REPO_OWNER boolean {
352 new_srv->show_repo_owner = $2;
354 | SHOW_REPO_AGE boolean {
355 new_srv->show_repo_age = $2;
357 | SHOW_REPO_DESCRIPTION boolean {
358 new_srv->show_repo_description = $2;
360 | SHOW_REPO_CLONEURL boolean {
361 new_srv->show_repo_cloneurl = $2;
363 | RESPECT_EXPORTOK boolean {
364 new_srv->respect_exportok = $2;
366 | MAX_REPOS_DISPLAY NUMBER {
367 if ($2 < 0) {
368 yyerror("max_repos_display is too small: %lld",
369 $2);
370 YYERROR;
372 new_srv->max_repos_display = $2;
374 | MAX_COMMITS_DISPLAY NUMBER {
375 if ($2 <= 1) {
376 yyerror("max_commits_display is too small:"
377 " %lld", $2);
378 YYERROR;
380 new_srv->max_commits_display = $2;
382 | SUMMARY_COMMITS_DISPLAY NUMBER {
383 if ($2 < 1) {
384 yyerror("summary_commits_display is too small:"
385 " %lld", $2);
386 YYERROR;
388 new_srv->summary_commits_display = $2;
390 | SUMMARY_TAGS_DISPLAY NUMBER {
391 if ($2 < 1) {
392 yyerror("summary_tags_display is too small:"
393 " %lld", $2);
394 YYERROR;
396 new_srv->summary_tags_display = $2;
400 serveropts2 : serveropts2 serveropts1 nl
401 | serveropts1 optnl
404 nl : '\n' optnl
407 optnl : '\n' optnl /* zero or more newlines */
408 | /* empty */
411 %%
413 struct keywords {
414 const char *k_name;
415 int k_val;
416 };
418 int
419 yyerror(const char *fmt, ...)
421 va_list ap;
422 char *msg;
424 file->errors++;
425 va_start(ap, fmt);
426 if (vasprintf(&msg, fmt, ap) == -1)
427 fatalx("yyerror vasprintf");
428 va_end(ap);
429 logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
430 free(msg);
431 return (0);
434 int
435 kw_cmp(const void *k, const void *e)
437 return (strcmp(k, ((const struct keywords *)e)->k_name));
440 int
441 lookup(char *s)
443 /* This has to be sorted always. */
444 static const struct keywords keywords[] = {
445 { "chroot", CHROOT },
446 { "custom_css", CUSTOM_CSS },
447 { "listen", LISTEN },
448 { "logo", LOGO },
449 { "logo_url", LOGO_URL },
450 { "max_commits_display", MAX_COMMITS_DISPLAY },
451 { "max_repos_display", MAX_REPOS_DISPLAY },
452 { "on", ON },
453 { "port", PORT },
454 { "prefork", PREFORK },
455 { "repos_path", REPOS_PATH },
456 { "respect_exportok", RESPECT_EXPORTOK },
457 { "server", SERVER },
458 { "show_repo_age", SHOW_REPO_AGE },
459 { "show_repo_cloneurl", SHOW_REPO_CLONEURL },
460 { "show_repo_description", SHOW_REPO_DESCRIPTION },
461 { "show_repo_owner", SHOW_REPO_OWNER },
462 { "show_site_owner", SHOW_SITE_OWNER },
463 { "site_link", SITE_LINK },
464 { "site_name", SITE_NAME },
465 { "site_owner", SITE_OWNER },
466 { "socket", SOCKET },
467 { "summary_commits_display", SUMMARY_COMMITS_DISPLAY },
468 { "summary_tags_display", SUMMARY_TAGS_DISPLAY },
469 { "user", USER },
470 };
471 const struct keywords *p;
473 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
474 sizeof(keywords[0]), kw_cmp);
476 if (p)
477 return (p->k_val);
478 else
479 return (STRING);
482 #define MAXPUSHBACK 128
484 unsigned char *parsebuf;
485 int parseindex;
486 unsigned char pushback_buffer[MAXPUSHBACK];
487 int pushback_index = 0;
489 int
490 lgetc(int quotec)
492 int c, next;
494 if (parsebuf) {
495 /* Read character from the parsebuffer instead of input. */
496 if (parseindex >= 0) {
497 c = parsebuf[parseindex++];
498 if (c != '\0')
499 return (c);
500 parsebuf = NULL;
501 } else
502 parseindex++;
505 if (pushback_index)
506 return (pushback_buffer[--pushback_index]);
508 if (quotec) {
509 c = getc(file->stream);
510 if (c == EOF)
511 yyerror("reached end of file while parsing "
512 "quoted string");
513 return (c);
516 c = getc(file->stream);
517 while (c == '\\') {
518 next = getc(file->stream);
519 if (next != '\n') {
520 c = next;
521 break;
523 yylval.lineno = file->lineno;
524 file->lineno++;
525 c = getc(file->stream);
528 return (c);
531 int
532 lungetc(int c)
534 if (c == EOF)
535 return (EOF);
536 if (parsebuf) {
537 parseindex--;
538 if (parseindex >= 0)
539 return (c);
541 if (pushback_index < MAXPUSHBACK-1)
542 return (pushback_buffer[pushback_index++] = c);
543 else
544 return (EOF);
547 int
548 findeol(void)
550 int c;
552 parsebuf = NULL;
554 /* Skip to either EOF or the first real EOL. */
555 while (1) {
556 if (pushback_index)
557 c = pushback_buffer[--pushback_index];
558 else
559 c = lgetc(0);
560 if (c == '\n') {
561 file->lineno++;
562 break;
564 if (c == EOF)
565 break;
567 return (ERROR);
570 int
571 yylex(void)
573 unsigned char buf[8096];
574 unsigned char *p, *val;
575 int quotec, next, c;
576 int token;
578 top:
579 p = buf;
580 c = lgetc(0);
581 while (c == ' ' || c == '\t')
582 c = lgetc(0); /* nothing */
584 yylval.lineno = file->lineno;
585 if (c == '#') {
586 c = lgetc(0);
587 while (c != '\n' && c != EOF)
588 c = lgetc(0); /* nothing */
590 if (c == '$' && parsebuf == NULL) {
591 while (1) {
592 c = lgetc(0);
593 if (c == EOF)
594 return (0);
596 if (p + 1 >= buf + sizeof(buf) - 1) {
597 yyerror("string too long");
598 return (findeol());
600 if (isalnum(c) || c == '_') {
601 *p++ = c;
602 continue;
604 *p = '\0';
605 lungetc(c);
606 break;
608 val = symget(buf);
609 if (val == NULL) {
610 yyerror("macro '%s' not defined", buf);
611 return (findeol());
613 parsebuf = val;
614 parseindex = 0;
615 goto top;
618 switch (c) {
619 case '\'':
620 case '"':
621 quotec = c;
622 while (1) {
623 c = lgetc(quotec);
624 if (c == EOF)
625 return (0);
626 if (c == '\n') {
627 file->lineno++;
628 continue;
629 } else if (c == '\\') {
630 next = lgetc(quotec);
631 if (next == EOF)
632 return (0);
633 if (next == quotec || c == ' ' || c == '\t')
634 c = next;
635 else if (next == '\n') {
636 file->lineno++;
637 continue;
638 } else
639 lungetc(next);
640 } else if (c == quotec) {
641 *p = '\0';
642 break;
643 } else if (c == '\0') {
644 yyerror("syntax error");
645 return (findeol());
647 if (p + 1 >= buf + sizeof(buf) - 1) {
648 yyerror("string too long");
649 return (findeol());
651 *p++ = c;
653 yylval.v.string = strdup(buf);
654 if (yylval.v.string == NULL)
655 err(1, "yylex: strdup");
656 return (STRING);
659 #define allowed_to_end_number(x) \
660 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
662 if (c == '-' || isdigit(c)) {
663 do {
664 *p++ = c;
665 if ((unsigned)(p-buf) >= sizeof(buf)) {
666 yyerror("string too long");
667 return (findeol());
669 c = lgetc(0);
670 } while (c != EOF && isdigit(c));
671 lungetc(c);
672 if (p == buf + 1 && buf[0] == '-')
673 goto nodigits;
674 if (c == EOF || allowed_to_end_number(c)) {
675 const char *errstr = NULL;
677 *p = '\0';
678 yylval.v.number = strtonum(buf, LLONG_MIN,
679 LLONG_MAX, &errstr);
680 if (errstr) {
681 yyerror("\"%s\" invalid number: %s",
682 buf, errstr);
683 return (findeol());
685 return (NUMBER);
686 } else {
687 nodigits:
688 while (p > buf + 1)
689 lungetc(*--p);
690 c = *--p;
691 if (c == '-')
692 return (c);
696 #define allowed_in_string(x) \
697 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
698 x != '{' && x != '}' && \
699 x != '!' && x != '=' && x != '#' && \
700 x != ','))
702 if (isalnum(c) || c == ':' || c == '_') {
703 do {
704 *p++ = c;
705 if ((unsigned)(p-buf) >= sizeof(buf)) {
706 yyerror("string too long");
707 return (findeol());
709 c = lgetc(0);
710 } while (c != EOF && (allowed_in_string(c)));
711 lungetc(c);
712 *p = '\0';
713 token = lookup(buf);
714 if (token == STRING) {
715 yylval.v.string = strdup(buf);
716 if (yylval.v.string == NULL)
717 err(1, "yylex: strdup");
719 return (token);
721 if (c == '\n') {
722 yylval.lineno = file->lineno;
723 file->lineno++;
725 if (c == EOF)
726 return (0);
727 return (c);
730 int
731 check_file_secrecy(int fd, const char *fname)
733 struct stat st;
735 if (fstat(fd, &st)) {
736 log_warn("cannot stat %s", fname);
737 return (-1);
739 if (st.st_uid != 0 && st.st_uid != getuid()) {
740 log_warnx("%s: owner not root or current user", fname);
741 return (-1);
743 if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
744 log_warnx("%s: group writable or world read/writable", fname);
745 return (-1);
747 return (0);
750 struct file *
751 newfile(const char *name, int secret)
753 struct file *nfile;
755 nfile = calloc(1, sizeof(struct file));
756 if (nfile == NULL) {
757 log_warn("calloc");
758 return (NULL);
760 nfile->name = strdup(name);
761 if (nfile->name == NULL) {
762 log_warn("strdup");
763 free(nfile);
764 return (NULL);
766 nfile->stream = fopen(nfile->name, "r");
767 if (nfile->stream == NULL) {
768 /* no warning, we don't require a conf file */
769 free(nfile->name);
770 free(nfile);
771 return (NULL);
772 } else if (secret &&
773 check_file_secrecy(fileno(nfile->stream), nfile->name)) {
774 fclose(nfile->stream);
775 free(nfile->name);
776 free(nfile);
777 return (NULL);
779 nfile->lineno = 1;
780 return (nfile);
783 static void
784 closefile(struct file *xfile)
786 fclose(xfile->stream);
787 free(xfile->name);
788 free(xfile);
791 static void
792 add_default_server(void)
794 new_srv = conf_new_server(D_SITENAME);
795 log_debug("%s: adding default server %s", __func__, D_SITENAME);
798 int
799 parse_config(const char *filename, struct gotwebd *env)
801 struct sym *sym, *next;
803 if (config_init(env) == -1)
804 fatalx("failed to initialize configuration");
806 gotwebd = env;
808 file = newfile(filename, 0);
809 if (file == NULL) {
810 add_default_server();
811 sockets_parse_sockets(env);
812 /* just return, as we don't require a conf file */
813 return (0);
816 yyparse();
817 errors = file->errors;
818 closefile(file);
820 /* Free macros and check which have not been used. */
821 TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
822 if ((gotwebd->gotwebd_verbose > 1) && !sym->used)
823 fprintf(stderr, "warning: macro '%s' not used\n",
824 sym->nam);
825 if (!sym->persist) {
826 free(sym->nam);
827 free(sym->val);
828 TAILQ_REMOVE(&symhead, sym, entry);
829 free(sym);
833 /* just add default server if no config specified */
834 if (gotwebd->server_cnt == 0)
835 add_default_server();
837 /* add the implicit listen on socket */
838 if (TAILQ_EMPTY(&gotwebd->addresses)) {
839 const char *path = D_HTTPD_CHROOT D_UNIX_SOCKET;
840 if (get_unix_addr(path) == -1)
841 yyerror("can't listen on %s", path);
844 if (errors)
845 return (-1);
847 /* setup our listening sockets */
848 sockets_parse_sockets(env);
850 return (0);
853 struct server *
854 conf_new_server(const char *name)
856 struct server *srv = NULL;
858 srv = calloc(1, sizeof(*srv));
859 if (srv == NULL)
860 fatalx("%s: calloc", __func__);
862 n = strlcpy(srv->name, name, sizeof(srv->name));
863 if (n >= sizeof(srv->name))
864 fatalx("%s: strlcpy", __func__);
865 n = strlcpy(srv->repos_path, D_GOTPATH,
866 sizeof(srv->repos_path));
867 if (n >= sizeof(srv->repos_path))
868 fatalx("%s: strlcpy", __func__);
869 n = strlcpy(srv->site_name, D_SITENAME,
870 sizeof(srv->site_name));
871 if (n >= sizeof(srv->site_name))
872 fatalx("%s: strlcpy", __func__);
873 n = strlcpy(srv->site_owner, D_SITEOWNER,
874 sizeof(srv->site_owner));
875 if (n >= sizeof(srv->site_owner))
876 fatalx("%s: strlcpy", __func__);
877 n = strlcpy(srv->site_link, D_SITELINK,
878 sizeof(srv->site_link));
879 if (n >= sizeof(srv->site_link))
880 fatalx("%s: strlcpy", __func__);
881 n = strlcpy(srv->logo, D_GOTLOGO,
882 sizeof(srv->logo));
883 if (n >= sizeof(srv->logo))
884 fatalx("%s: strlcpy", __func__);
885 n = strlcpy(srv->logo_url, D_GOTURL, sizeof(srv->logo_url));
886 if (n >= sizeof(srv->logo_url))
887 fatalx("%s: strlcpy", __func__);
888 n = strlcpy(srv->custom_css, D_GOTWEBCSS, sizeof(srv->custom_css));
889 if (n >= sizeof(srv->custom_css))
890 fatalx("%s: strlcpy", __func__);
892 srv->show_site_owner = D_SHOWSOWNER;
893 srv->show_repo_owner = D_SHOWROWNER;
894 srv->show_repo_age = D_SHOWAGE;
895 srv->show_repo_description = D_SHOWDESC;
896 srv->show_repo_cloneurl = D_SHOWURL;
897 srv->respect_exportok = D_RESPECTEXPORTOK;
899 srv->max_repos_display = D_MAXREPODISP;
900 srv->max_commits_display = D_MAXCOMMITDISP;
901 srv->summary_commits_display = D_MAXSLCOMMDISP;
902 srv->summary_tags_display = D_MAXSLTAGDISP;
904 TAILQ_INSERT_TAIL(&gotwebd->servers, srv, entry);
905 gotwebd->server_cnt++;
907 return srv;
908 };
910 int
911 symset(const char *nam, const char *val, int persist)
913 struct sym *sym;
915 TAILQ_FOREACH(sym, &symhead, entry) {
916 if (strcmp(nam, sym->nam) == 0)
917 break;
920 if (sym != NULL) {
921 if (sym->persist == 1)
922 return (0);
923 else {
924 free(sym->nam);
925 free(sym->val);
926 TAILQ_REMOVE(&symhead, sym, entry);
927 free(sym);
930 sym = calloc(1, sizeof(*sym));
931 if (sym == NULL)
932 return (-1);
934 sym->nam = strdup(nam);
935 if (sym->nam == NULL) {
936 free(sym);
937 return (-1);
939 sym->val = strdup(val);
940 if (sym->val == NULL) {
941 free(sym->nam);
942 free(sym);
943 return (-1);
945 sym->used = 0;
946 sym->persist = persist;
947 TAILQ_INSERT_TAIL(&symhead, sym, entry);
948 return (0);
951 int
952 cmdline_symset(char *s)
954 char *sym, *val;
955 int ret;
957 val = strrchr(s, '=');
958 if (val == NULL)
959 return (-1);
961 sym = strndup(s, val - s);
962 if (sym == NULL)
963 fatal("%s: strndup", __func__);
965 ret = symset(sym, val + 1, 1);
966 free(sym);
968 return (ret);
971 char *
972 symget(const char *nam)
974 struct sym *sym;
976 TAILQ_FOREACH(sym, &symhead, entry) {
977 if (strcmp(nam, sym->nam) == 0) {
978 sym->used = 1;
979 return (sym->val);
982 return (NULL);
985 int
986 get_addrs(const char *hostname, const char *servname)
988 struct addrinfo hints, *res0, *res;
989 int error;
990 struct sockaddr_in *sin;
991 struct sockaddr_in6 *sin6;
992 struct address *h;
994 memset(&hints, 0, sizeof(hints));
995 hints.ai_family = AF_UNSPEC;
996 hints.ai_socktype = SOCK_STREAM;
997 hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
998 error = getaddrinfo(hostname, servname, &hints, &res0);
999 if (error) {
1000 log_warnx("%s: could not parse \"%s:%s\": %s", __func__,
1001 hostname, servname, gai_strerror(error));
1002 return (-1);
1005 for (res = res0; res; res = res->ai_next) {
1006 if ((h = calloc(1, sizeof(*h))) == NULL)
1007 fatal(__func__);
1009 if (hostname == NULL) {
1010 strlcpy(h->ifname, "*", sizeof(h->ifname));
1011 } else {
1012 if (strlcpy(h->ifname, hostname, sizeof(h->ifname)) >=
1013 sizeof(h->ifname)) {
1014 log_warnx("%s: address truncated: %s",
1015 __func__, hostname);
1016 freeaddrinfo(res0);
1017 free(h);
1018 return (-1);
1022 h->ai_family = res->ai_family;
1023 h->ai_socktype = res->ai_socktype;
1024 h->ai_protocol = res->ai_protocol;
1025 memcpy(&h->ss, res->ai_addr, res->ai_addrlen);
1026 h->slen = res->ai_addrlen;
1028 switch (res->ai_family) {
1029 case AF_INET:
1030 sin = (struct sockaddr_in *)res->ai_addr;
1031 h->port = ntohs(sin->sin_port);
1032 break;
1033 case AF_INET6:
1034 sin6 = (struct sockaddr_in6 *)res->ai_addr;
1035 h->port = ntohs(sin6->sin6_port);
1036 break;
1037 default:
1038 fatalx("unknown address family %d", res->ai_family);
1041 if (add_addr(h) == -1) {
1042 freeaddrinfo(res0);
1043 return -1;
1046 freeaddrinfo(res0);
1047 return (0);
1050 int
1051 get_unix_addr(const char *path)
1053 struct address *h;
1054 struct sockaddr_un *sun;
1056 if ((h = calloc(1, sizeof(*h))) == NULL)
1057 fatal("%s: calloc", __func__);
1059 h->ai_family = AF_UNIX;
1060 h->ai_socktype = SOCK_STREAM;
1061 h->ai_protocol = PF_UNSPEC;
1062 h->slen = sizeof(*sun);
1064 sun = (struct sockaddr_un *)&h->ss;
1065 sun->sun_family = AF_UNIX;
1066 if (strlcpy(sun->sun_path, path, sizeof(sun->sun_path)) >=
1067 sizeof(sun->sun_path)) {
1068 log_warnx("socket path too long: %s", sun->sun_path);
1069 return (-1);
1072 return add_addr(h);
1075 int
1076 addr_dup_check(struct addresslist *al, struct address *h)
1078 struct address *a;
1080 TAILQ_FOREACH(a, al, entry) {
1081 if (a->ai_family != h->ai_family ||
1082 a->ai_socktype != h->ai_socktype ||
1083 a->ai_protocol != h->ai_protocol ||
1084 a->slen != h->slen ||
1085 memcmp(&a->ss, &h->ss, a->slen) != 0)
1086 continue;
1087 return -1;
1090 return 0;
1093 int
1094 add_addr(struct address *h)
1096 if (addr_dup_check(&gotwebd->addresses, h) == 0) {
1097 TAILQ_INSERT_TAIL(&gotwebd->addresses, h, entry);
1098 return (0);
1101 free(h);
1102 return (0);