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>
34 #include <netinet/in.h>
36 #include <arpa/inet.h>
52 #include "got_reference.h"
57 TAILQ_HEAD
(files
, file
) files
= TAILQ_HEAD_INITIALIZER
(files
);
59 TAILQ_ENTRY
(file
) entry
;
65 struct file
*newfile
(const char *, int);
66 static void closefile
(struct file
*);
67 int check_file_secrecy
(int, const char *);
70 int yyerror(const char *, ...
)
71 __attribute__
((__format__
(printf
, 1, 2)))
72 __attribute__
((__nonnull__
(1)));
73 int kw_cmp
(const void *, const void *);
79 TAILQ_HEAD
(symhead
, sym
) symhead
= TAILQ_HEAD_INITIALIZER
(symhead
);
81 TAILQ_ENTRY
(sym
) entry
;
88 int symset
(const char *, const char *, int);
89 char *symget
(const char *);
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 *);
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
*);
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 */
130 | grammar varset
'\n'
132 | grammar server
'\n'
133 | grammar
error '\n' { file
->errors
++; }
136 varset
: STRING
'=' STRING
{
139 if
(isspace
((unsigned char)*s
)) {
140 yyerror("macro name cannot contain "
147 if
(symset
($1, $3, 0) == -1)
148 fatal
("cannot store variable");
155 if
(strcasecmp
($1, "1") == 0 ||
156 strcasecmp
($1, "on") == 0)
158 else if
(strcasecmp
($1, "0") == 0 ||
159 strcasecmp
($1, "off") == 0)
162 yyerror("invalid boolean value '%s'", $1);
170 if
($1 != 0 && $1 != 1) {
171 yyerror("invalid boolean value '%lld'", $1);
178 listen_addr
: '*' { $$
= NULL
; }
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);
188 gotwebd
->prefork_gotwebd
= $2;
192 yyerror("chroot path can't be an empty"
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__
);
207 | LISTEN ON listen_addr PORT STRING
{
208 if
(get_addrs
($3, $5) == -1) {
209 yyerror("could not get addrs");
215 | LISTEN ON listen_addr PORT NUMBER
{
219 n
= snprintf
(portno
, sizeof
(portno
), "%lld",
221 if
(n
< 0 ||
(size_t)n
>= sizeof
(portno
))
222 fatalx
("port number too long: %lld",
225 if
(get_addrs
($3, portno
) == -1) {
226 yyerror("could not get addrs");
231 | LISTEN ON SOCKET STRING
{
232 if
(get_unix_addr
($4) == -1) {
233 yyerror("can't listen on %s", $4);
240 if
(gotwebd
->user
!= NULL
)
241 yyerror("user already specified");
247 server
: SERVER STRING
{
250 TAILQ_FOREACH
(srv
, &gotwebd
->servers
, entry
) {
251 if
(strcmp
(srv
->name
, $2) == 0) {
252 yyerror("server name exists '%s'", $2);
258 new_srv
= conf_new_server
($2);
259 log_debug
("adding server %s", $2);
265 TAILQ_FOREACH
(srv
, &gotwebd
->servers
, entry
) {
266 if
(strcmp
(srv
->name
, $2) == 0) {
267 yyerror("server name exists '%s'", $2);
273 new_srv
= conf_new_server
($2);
274 log_debug
("adding server %s", $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__
);
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__
);
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__
);
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__
);
321 n
= strlcpy
(new_srv
->logo
, $2, sizeof
(new_srv
->logo
));
322 if
(n
>= sizeof
(new_srv
->logo
)) {
323 yyerror("%s: logo truncated", __func__
);
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__
);
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__
);
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
{
369 yyerror("max_repos_display is too small: %lld",
373 new_srv
->max_repos_display
= $2;
375 | MAX_COMMITS_DISPLAY NUMBER
{
377 yyerror("max_commits_display is too small:"
381 new_srv
->max_commits_display
= $2;
383 | SUMMARY_COMMITS_DISPLAY NUMBER
{
385 yyerror("summary_commits_display is too small:"
389 new_srv
->summary_commits_display
= $2;
391 | SUMMARY_TAGS_DISPLAY NUMBER
{
393 yyerror("summary_tags_display is too small:"
397 new_srv
->summary_tags_display
= $2;
401 serveropts2
: serveropts2 serveropts1 nl
408 optnl
: '\n' optnl
/* zero or more newlines */
420 yyerror(const char *fmt
, ...
)
427 if
(vasprintf
(&msg
, fmt
, ap
) == -1)
428 fatalx
("yyerror vasprintf");
430 logit
(LOG_CRIT
, "%s:%d: %s", file
->name
, yylval.lineno
, msg
);
436 kw_cmp
(const void *k
, const void *e
)
438 return
(strcmp
(k
, ((const struct keywords
*)e
)->k_name
));
444 /* This has to be sorted always. */
445 static const struct keywords keywords
[] = {
446 { "chroot", CHROOT
},
447 { "custom_css", CUSTOM_CSS
},
448 { "listen", LISTEN
},
450 { "logo_url", LOGO_URL
},
451 { "max_commits_display", MAX_COMMITS_DISPLAY
},
452 { "max_repos_display", MAX_REPOS_DISPLAY
},
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
},
472 const struct keywords
*p
;
474 p
= bsearch
(s
, keywords
, sizeof
(keywords
)/sizeof
(keywords
[0]),
475 sizeof
(keywords
[0]), kw_cmp
);
483 #define MAXPUSHBACK 128
485 unsigned char *parsebuf
;
487 unsigned char pushback_buffer
[MAXPUSHBACK
];
488 int pushback_index
= 0;
496 /* Read character from the parsebuffer instead of input. */
497 if
(parseindex
>= 0) {
498 c
= parsebuf
[parseindex
++];
507 return
(pushback_buffer
[--pushback_index
]);
510 c
= getc
(file
->stream
);
512 yyerror("reached end of file while parsing "
517 c
= getc
(file
->stream
);
519 next
= getc
(file
->stream
);
524 yylval.lineno
= file
->lineno
;
526 c
= getc
(file
->stream
);
542 if
(pushback_index
< MAXPUSHBACK
-1)
543 return
(pushback_buffer
[pushback_index
++] = c
);
555 /* Skip to either EOF or the first real EOL. */
558 c
= pushback_buffer
[--pushback_index
];
574 unsigned char buf
[8096];
575 unsigned char *p
, *val
;
582 while
(c
== ' ' || c
== '\t')
583 c
= lgetc
(0); /* nothing */
585 yylval.lineno
= file
->lineno
;
588 while
(c
!= '\n' && c
!= EOF
)
589 c
= lgetc
(0); /* nothing */
591 if
(c
== '$' && parsebuf
== NULL
) {
597 if
(p
+ 1 >= buf
+ sizeof
(buf
) - 1) {
598 yyerror("string too long");
601 if
(isalnum
(c
) || c
== '_') {
611 yyerror("macro '%s' not defined", buf
);
630 } else if
(c
== '\\') {
631 next
= lgetc
(quotec
);
634 if
(next
== quotec || c
== ' ' || c
== '\t')
636 else if
(next
== '\n') {
641 } else if
(c
== quotec
) {
644 } else if
(c
== '\0') {
645 yyerror("syntax error");
648 if
(p
+ 1 >= buf
+ sizeof
(buf
) - 1) {
649 yyerror("string too long");
654 yylval.v.
string = strdup
(buf
);
655 if
(yylval.v.
string == NULL
)
656 err
(1, "yylex: strdup");
660 #define allowed_to_end_number(x) \
661 (isspace
(x
) || x
== ')' || x
==',' || x
== '/' || x
== '}' || x
== '=')
663 if
(c
== '-' || isdigit
(c
)) {
666 if
((unsigned)(p
-buf
) >= sizeof
(buf
)) {
667 yyerror("string too long");
671 } while
(c
!= EOF
&& isdigit
(c
));
673 if
(p
== buf
+ 1 && buf
[0] == '-')
675 if
(c
== EOF || allowed_to_end_number
(c
)) {
676 const char *errstr
= NULL
;
679 yylval.v.number
= strtonum
(buf
, LLONG_MIN
,
682 yyerror("\"%s\" invalid number: %s",
697 #define allowed_in_string(x) \
698 (isalnum
(x
) ||
(ispunct
(x
) && x
!= '(' && x
!= ')' && \
699 x
!= '{' && x
!= '}' && \
700 x
!= '!' && x
!= '=' && x
!= '#' && \
703 if
(isalnum
(c
) || c
== ':' || c
== '_') {
706 if
((unsigned)(p
-buf
) >= sizeof
(buf
)) {
707 yyerror("string too long");
711 } while
(c
!= EOF
&& (allowed_in_string
(c
)));
715 if
(token
== STRING
) {
716 yylval.v.
string = strdup
(buf
);
717 if
(yylval.v.
string == NULL
)
718 err
(1, "yylex: strdup");
723 yylval.lineno
= file
->lineno
;
732 check_file_secrecy
(int fd
, const char *fname
)
736 if
(fstat
(fd
, &st
)) {
737 log_warn
("cannot stat %s", fname
);
740 if
(st.st_uid
!= 0 && st.st_uid
!= getuid
()) {
741 log_warnx
("%s: owner not root or current user", fname
);
744 if
(st.st_mode
& (S_IWGRP | S_IXGRP | S_IRWXO
)) {
745 log_warnx
("%s: group writable or world read/writable", fname
);
752 newfile
(const char *name
, int secret
)
756 nfile
= calloc
(1, sizeof
(struct file
));
761 nfile
->name
= strdup
(name
);
762 if
(nfile
->name
== NULL
) {
767 nfile
->stream
= fopen
(nfile
->name
, "r");
768 if
(nfile
->stream
== NULL
) {
769 /* no warning, we don't require a conf file */
774 check_file_secrecy
(fileno
(nfile
->stream
), nfile
->name
)) {
775 fclose
(nfile
->stream
);
785 closefile
(struct file
*xfile
)
787 fclose
(xfile
->stream
);
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");
809 file
= newfile
(filename
, 0);
811 /* we don't require a config file */
813 errors
= file
->errors
;
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",
825 TAILQ_REMOVE
(&symhead
, sym
, entry
);
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
);
844 /* setup our listening sockets */
845 sockets_parse_sockets
(env
);
851 conf_new_server
(const char *name
)
853 struct server
*srv
= NULL
;
855 srv
= calloc
(1, sizeof
(*srv
));
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
,
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
++;
908 symset
(const char *nam
, const char *val
, int persist
)
912 TAILQ_FOREACH
(sym
, &symhead
, entry
) {
913 if
(strcmp
(nam
, sym
->nam
) == 0)
918 if
(sym
->persist
== 1)
923 TAILQ_REMOVE
(&symhead
, sym
, entry
);
927 sym
= calloc
(1, sizeof
(*sym
));
931 sym
->nam
= strdup
(nam
);
932 if
(sym
->nam
== NULL
) {
936 sym
->val
= strdup
(val
);
937 if
(sym
->val
== NULL
) {
943 sym
->persist
= persist
;
944 TAILQ_INSERT_TAIL
(&symhead
, sym
, entry
);
949 cmdline_symset
(char *s
)
954 val
= strrchr
(s
, '=');
958 sym
= strndup
(s
, val
- s
);
960 fatal
("%s: strndup", __func__
);
962 ret
= symset
(sym
, val
+ 1, 1);
969 symget
(const char *nam
)
973 TAILQ_FOREACH
(sym
, &symhead
, entry
) {
974 if
(strcmp
(nam
, sym
->nam
) == 0) {
983 get_addrs
(const char *hostname
, const char *servname
)
985 struct addrinfo hints
, *res0
, *res
;
987 struct sockaddr_in
*sin
;
988 struct sockaddr_in6
*sin6
;
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
);
997 log_warnx
("%s: could not parse \"%s:%s\": %s", __func__
,
998 hostname
, servname
, gai_strerror
(error));
1002 for
(res
= res0
; res
; res
= res
->ai_next
) {
1003 if
((h
= calloc
(1, sizeof
(*h
))) == NULL
)
1006 if
(hostname
== NULL
) {
1007 strlcpy
(h
->ifname
, "*", sizeof
(h
->ifname
));
1009 if
(strlcpy
(h
->ifname
, hostname
, sizeof
(h
->ifname
)) >=
1010 sizeof
(h
->ifname
)) {
1011 log_warnx
("%s: address truncated: %s",
1012 __func__
, hostname
);
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
) {
1027 sin
= (struct sockaddr_in
*)res
->ai_addr
;
1028 h
->port
= ntohs
(sin
->sin_port
);
1031 sin6
= (struct sockaddr_in6
*)res
->ai_addr
;
1032 h
->port
= ntohs
(sin6
->sin6_port
);
1035 fatalx
("unknown address family %d", res
->ai_family
);
1038 if
(add_addr
(h
) == -1) {
1048 get_unix_addr
(const char *path
)
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
);
1073 addr_dup_check
(struct addresslist
*al
, struct address
*h
)
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)
1091 add_addr
(struct address
*h
)
1093 if
(addr_dup_check
(&gotwebd
->addresses
, h
) == 0) {
1094 TAILQ_INSERT_TAIL
(&gotwebd
->addresses
, h
, entry
);