commit 509d0509a50883a6f8407b63774f40dd1e41dadf from: Omar Polo date: Fri Jun 23 21:03:29 2023 UTC implement `listen on' Listening by default on all the addresses is so bad I don't know why I haven't changed this before. Anyway. Add a `listen on $hostname port $port' syntax to the config file and deprecate the old "port" and "ipv6" global setting. Still try to honour them when no "listen on" directive is used for backward compatibily, but this will go away in the next next version hopefully. At the moment the `listen on' in server context don't filter the host, i.e. one can still reach a host from a address not specified in the corresponding `liste on', this will be added later. commit - 37df23d183de23b74f8a026977b8210dc22701a6 commit + 509d0509a50883a6f8407b63774f40dd1e41dadf blob - 3f563ed2aba7598f4c4da31068d0ece6d23cb7a9 blob + 82097604764fbbc218ab0d4a03cbd130ac9d8376 --- config.c +++ config.c @@ -37,9 +37,8 @@ config_new(void) TAILQ_INIT(&conf->fcgi); TAILQ_INIT(&conf->hosts); TAILQ_INIT(&conf->pkis); + TAILQ_INIT(&conf->addrs); - conf->port = 1965; - conf->ipv6 = 0; conf->protos = TLS_PROTOCOL_TLSv1_2 | TLS_PROTOCOL_TLSv1_3; init_mime(&conf->mime); @@ -50,9 +49,6 @@ config_new(void) conf->use_privsep_crypto = 1; #endif - conf->sock4 = -1; - conf->sock6 = -1; - return conf; } @@ -67,21 +63,12 @@ config_purge(struct conf *conf) struct envlist *e, *te; struct alist *a, *ta; struct pki *pki, *tpki; + struct address *addr, *taddr; int use_privsep_crypto; ps = conf->ps; use_privsep_crypto = conf->use_privsep_crypto; - if (conf->sock4 != -1) { - event_del(&conf->evsock4); - close(conf->sock4); - } - - if (conf->sock6 != -1) { - event_del(&conf->evsock6); - close(conf->sock6); - } - free_mime(&conf->mime); TAILQ_FOREACH_SAFE(f, &conf->fcgi, fcgi, tf) { TAILQ_REMOVE(&conf->fcgi, f, fcgi); @@ -96,6 +83,11 @@ config_purge(struct conf *conf) free(h->key); free(h->ocsp); + TAILQ_FOREACH_SAFE(addr, &h->addrs, addrs, taddr) { + TAILQ_REMOVE(&h->addrs, addr, addrs); + free(addr); + } + TAILQ_FOREACH_SAFE(l, &h->locations, locations, tl) { TAILQ_REMOVE(&h->locations, l, locations); @@ -139,11 +131,19 @@ config_purge(struct conf *conf) free(pki); } + TAILQ_FOREACH_SAFE(addr, &conf->addrs, addrs, taddr) { + TAILQ_REMOVE(&conf->addrs, addr, addrs); + if (addr->sock != -1) { + close(addr->sock); + event_del(&addr->evsock); + } + free(addr); + } + memset(conf, 0, sizeof(*conf)); conf->ps = ps; conf->use_privsep_crypto = use_privsep_crypto; - conf->sock4 = conf->sock6 = -1; conf->protos = TLS_PROTOCOL_TLSv1_2 | TLS_PROTOCOL_TLSv1_3; init_mime(&conf->mime); TAILQ_INIT(&conf->fcgi); @@ -223,82 +223,47 @@ config_send_kp(struct privsep *ps, int cert_type, int } static int -make_socket(int port, int family) +config_send_socks(struct conf *conf) { - int sock, v; - struct sockaddr_in addr4; - struct sockaddr_in6 addr6; - struct sockaddr *addr; - socklen_t len; + struct privsep *ps = conf->ps; + struct address *addr, a; + int sock, v; - switch (family) { - case AF_INET: - memset(&addr4, 0, sizeof(addr4)); - addr4.sin_family = family; - addr4.sin_port = htons(port); - addr4.sin_addr.s_addr = INADDR_ANY; - addr = (struct sockaddr*)&addr4; - len = sizeof(addr4); - break; + TAILQ_FOREACH(addr, &conf->addrs, addrs) { + sock = socket(addr->ai_family, addr->ai_socktype, + addr->ai_protocol); + if (sock == -1) + fatal("socket"); - case AF_INET6: - memset(&addr6, 0, sizeof(addr6)); - addr6.sin6_family = AF_INET6; - addr6.sin6_port = htons(port); - addr6.sin6_addr = in6addr_any; - addr = (struct sockaddr*)&addr6; - len = sizeof(addr6); - break; + v = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(v)) + == -1) + fatal("setsockopt(SO_REUSEADDR)"); - default: - /* unreachable */ - abort(); - } + v = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &v, sizeof(v)) + == -1) + fatal("setsockopt(SO_REUSEPORT)"); - if ((sock = socket(family, SOCK_STREAM, 0)) == -1) - fatal("socket"); + mark_nonblock(sock); - v = 1; - if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(v)) == -1) - fatal("setsockopt(SO_REUSEADDR)"); + if (bind(sock, (struct sockaddr *)&addr->ss, addr->slen) + == -1) + fatal("bind"); + + if (listen(sock, 16) == -1) + fatal("listen"); - v = 1; - if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &v, sizeof(v)) == -1) - fatal("setsockopt(SO_REUSEPORT)"); + memcpy(&a, addr, sizeof(a)); + a.conf = NULL; + a.sock = -1; + memset(&a.evsock, 0, sizeof(a.evsock)); + memset(&a.addrs, 0, sizeof(a.addrs)); - mark_nonblock(sock); - - if (bind(sock, addr, len) == -1) - fatal("bind"); - - if (listen(sock, 16) == -1) - fatal("listen"); - - return sock; -} - -static int -config_send_socks(struct conf *conf) -{ - struct privsep *ps = conf->ps; - int sock; - - if ((sock = make_socket(conf->port, AF_INET)) == -1) - return -1; - - if (config_send_file(ps, PROC_SERVER, IMSG_RECONF_SOCK4, sock, - NULL, 0) == -1) - return -1; - - if (!conf->ipv6) - return 0; - - if ((sock = make_socket(conf->port, AF_INET6)) == -1) - return -1; - - if (config_send_file(ps, PROC_SERVER, IMSG_RECONF_SOCK6, sock, - NULL, 0) == -1) - return -1; + if (config_send_file(ps, PROC_SERVER, IMSG_RECONF_SOCK, sock, + &a, sizeof(a)) == -1) + return -1; + } return 0; } @@ -327,10 +292,6 @@ config_send(struct conf *conf) &conf->protos, sizeof(conf->protos)) == -1) return -1; - if (proc_compose(ps, PROC_SERVER, IMSG_RECONF_PORT, - &conf->port, sizeof(conf->port)) == -1) - return -1; - if (proc_flush_imsg(ps, PROC_SERVER, -1) == -1) return -1; @@ -549,6 +510,7 @@ config_recv(struct conf *conf, struct imsg *imsg) struct envlist *env; struct alist *alias; struct proxy *proxy; + struct address *addr; uint8_t *d; size_t len, datalen; @@ -577,31 +539,19 @@ config_recv(struct conf *conf, struct imsg *imsg) memcpy(&conf->protos, imsg->data, datalen); break; - case IMSG_RECONF_PORT: - IMSG_SIZE_CHECK(imsg, &conf->port); - memcpy(&conf->port, imsg->data, datalen); - break; - - case IMSG_RECONF_SOCK4: - if (conf->sock4 != -1) - fatalx("socket ipv4 already recv'd"); + case IMSG_RECONF_SOCK: + addr = xcalloc(1, sizeof(*addr)); + IMSG_SIZE_CHECK(imsg, addr); + memcpy(addr, imsg->data, sizeof(*addr)); if (imsg->fd == -1) fatalx("missing socket for IMSG_RECONF_SOCK4"); - conf->sock4 = imsg->fd; - event_set(&conf->evsock4, conf->sock4, EV_READ|EV_PERSIST, - do_accept, conf); + addr->conf = conf; + addr->sock = imsg->fd; + event_set(&addr->evsock, addr->sock, EV_READ|EV_PERSIST, + do_accept, addr); + TAILQ_INSERT_HEAD(&conf->addrs, addr, addrs); break; - case IMSG_RECONF_SOCK6: - if (conf->sock6 != -1) - fatalx("socket ipv6 already recv'd"); - if (imsg->fd == -1) - fatalx("missing socket for IMSG_RECONF_SOCK6"); - conf->sock6 = imsg->fd; - event_set(&conf->evsock6, conf->sock6, EV_READ|EV_PERSIST, - do_accept, conf); - break; - case IMSG_RECONF_FCGI: IMSG_SIZE_CHECK(imsg, fcgi); fcgi = xcalloc(1, sizeof(*fcgi)); blob - bf3cae0f550c74887b455a5befa0fdeb4c821dd6 blob + 1d55b4a5dfde4805a1b1e78511e101f7a371ac1c --- fcgi.c +++ fcgi.c @@ -350,7 +350,7 @@ fcgi_req(struct client *c) struct tm tminfo; struct envlist *p; - e = getnameinfo((struct sockaddr*)&c->addr, sizeof(c->addr), + e = getnameinfo((struct sockaddr*)&c->raddr, c->raddrlen, addr, sizeof(addr), NULL, 0, NI_NUMERICHOST); blob - f45d9b2ce431ea1f1af8f847a89e3f8d7bfb9a05 blob + 4978c2cab70a81f1a85fad0ae6cda0cddb76ddc8 --- ge.c +++ ge.c @@ -159,10 +159,15 @@ static int serve(struct conf *conf, const char *host, int port, const char *dir) { struct addrinfo hints, *res, *res0; + struct vhost *vh = TAILQ_FIRST(&conf->hosts); + struct address *addr, *acp; int r, error, saved_errno, sock = -1; const char *cause = NULL; char service[32]; + int any = 0; + event_init(); + r = snprintf(service, sizeof(service), "%d", port); if (r < 0 || (size_t)r >= sizeof(service)) fatal("snprintf"); @@ -193,24 +198,34 @@ serve(struct conf *conf, const char *host, int port, c if (listen(sock, 5) == -1) fatal("listen"); - /* - * for the time being, we're happy as soon as - * something binds. - */ - break; + any = 1; + + addr = xcalloc(1, sizeof(*addr)); + addr->ai_flags = res->ai_flags; + addr->ai_family = res->ai_family; + addr->ai_socktype = res->ai_socktype; + addr->ai_protocol = res->ai_protocol; + addr->slen = res->ai_addrlen; + memcpy(&addr->ss, res->ai_addr, res->ai_addrlen); + + addr->conf = conf; + addr->sock = sock; + event_set(&addr->evsock, addr->sock, EV_READ|EV_PERSIST, + do_accept, addr); + + TAILQ_INSERT_HEAD(&conf->addrs, addr, addrs); + + acp = xcalloc(1, sizeof(*acp)); + memcpy(acp, addr, sizeof(*acp)); + acp->sock = -1; + memset(&acp->evsock, 0, sizeof(acp->evsock)); + TAILQ_INSERT_HEAD(&vh->addrs, addr, addrs); } - if (sock == -1) + if (!any) fatal("%s", cause); freeaddrinfo(res0); - event_init(); - - /* cheating */ - conf->sock4 = sock; - event_set(&conf->evsock4, conf->sock4, EV_READ|EV_PERSIST, - do_accept, conf); - server_init(NULL, NULL, NULL); if (server_configure_done(conf) == -1) fatalx("server configuration failed"); @@ -239,7 +254,7 @@ main(int argc, char **argv) struct location *loc; const char *errstr, *certs_dir = NULL, *hostname = "localhost"; char path[PATH_MAX]; - int ch; + int ch, port = 1965; setlocale(LC_CTYPE, ""); @@ -262,7 +277,7 @@ main(int argc, char **argv) usage(); break; case 'p': - conf->port = strtonum(optarg, 0, UINT16_MAX, &errstr); + port = strtonum(optarg, 0, UINT16_MAX, &errstr); if (errstr) fatalx("port number is %s: %s", errstr, optarg); @@ -316,5 +331,5 @@ main(int argc, char **argv) /* start the server */ signal(SIGPIPE, SIG_IGN); setproctitle("%s", loc->dir); - return serve(conf, hostname, conf->port, loc->dir); + return serve(conf, hostname, port, loc->dir); } blob - d3e4cb2d0d3f0d855fa6f743a4e11b55ac7a9ea3 blob + b85fd4a1bb6bc4ed3d4c9d671593916e04b71832 --- gmid.c +++ gmid.c @@ -484,9 +484,7 @@ main_print_conf(struct conf *conf) if (*conf->chroot != '\0') printf("chroot \"%s\"\n", conf->chroot); - printf("ipv6 %s\n", conf->ipv6 ? "on" : "off"); /* XXX: defined mimes? */ - printf("port %d\n", conf->port); printf("prefork %d\n", conf->prefork); /* XXX: protocols? */ if (*conf->user != '\0') blob - 644788bcdf6c52441e6cb6ed9a768ff3187332b1 blob + 8947e68a144d2bc7174694d25f5ccd307ba7b198 --- gmid.h +++ gmid.h @@ -102,6 +102,25 @@ struct parser { char *iri; struct iri *parsed; const char *err; +}; + +struct conf; +TAILQ_HEAD(addrhead, address); +struct address { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + struct sockaddr_storage ss; + socklen_t slen; + int16_t port; + + /* used in the server */ + struct conf *conf; + int sock; + struct event evsock; /* set if sock != -1 */ + + TAILQ_ENTRY(address) addrs; }; TAILQ_HEAD(fcgihead, fcgi); @@ -188,6 +207,8 @@ struct vhost { TAILQ_ENTRY(vhost) vhosts; + struct addrhead addrs; + /* * the first location rule is always '*' and holds the default * settings for the vhost, then follows the "real" location @@ -220,8 +241,6 @@ struct pki { struct conf { struct privsep *ps; - int port; - int ipv6; uint32_t protos; struct mime mime; char chroot[PATH_MAX]; @@ -229,15 +248,11 @@ struct conf { int prefork; int reload; int use_privsep_crypto; - - int sock4; - struct event evsock4; - int sock6; - struct event evsock6; struct fcgihead fcgi; struct vhosthead hosts; struct pkihead pkis; + struct addrhead addrs; }; extern const char *config_path; @@ -258,6 +273,7 @@ enum { struct client { struct conf *conf; + struct address *addr; uint32_t id; struct tls *ctx; char *req; @@ -324,9 +340,7 @@ enum imsg_type { IMSG_RECONF_START, /* 7 */ IMSG_RECONF_MIME, IMSG_RECONF_PROTOS, - IMSG_RECONF_PORT, - IMSG_RECONF_SOCK4, - IMSG_RECONF_SOCK6, + IMSG_RECONF_SOCK, IMSG_RECONF_FCGI, IMSG_RECONF_HOST, IMSG_RECONF_CERT, blob - 8ba06d3931c0035ae9d7310680e632944ca4c861 blob + 2f85a660d177ee24bd720b00b0195502c7766fc8 --- parse.y +++ parse.y @@ -37,6 +37,9 @@ struct conf *conf; +static const char *default_host = "*"; +static uint16_t default_port = 1965; + TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); static struct file { TAILQ_ENTRY(file) entry; @@ -96,6 +99,7 @@ void parsehp(char *, char **, const char **, const c int fastcgi_conf(const char *, const char *); void add_param(char *, char *); int getservice(const char *); +void listen_on(const char *, const char *); static struct vhost *host; static struct location *loc; @@ -125,7 +129,7 @@ typedef struct { %token FASTCGI FOR_HOST %token INCLUDE INDEX IPV6 %token KEY -%token LANG LOCATION LOG +%token LANG LISTEN LOCATION LOG %token OCSP OFF ON %token PARAM PORT PREFORK PROTO PROTOCOLS PROXY %token RELAY_TO REQUIRE RETURN ROOT @@ -220,8 +224,19 @@ option : CHROOT string { yyerror("chroot path too long"); free($2); } - | IPV6 bool { conf->ipv6 = $2; } - | PORT NUM { conf->port = check_port_num($2); } + | IPV6 bool { + yywarn("option `ipv6' is deprecated," + " please use `listen on'"); + if ($2) + default_host = "*"; + else + default_host = "0.0.0.0"; + } + | PORT NUM { + yywarn("option `port' is deprecated," + " please use `listen on'"); + default_port = $2; + } | PREFORK NUM { conf->prefork = check_prefork_num($2); } | PROTOCOLS string { if (tls_config_parse_protocols(&conf->protos, $2) == -1) @@ -259,6 +274,20 @@ vhost : SERVER string { host->key_path == NULL) yyerror("invalid vhost definition: %s", host->domain); + if (TAILQ_EMPTY(&host->addrs)) { + char portno[32]; + int r; + + r = snprintf(portno, sizeof(portno), "%d", + default_port); + if (r < 0 || (size_t)r >= sizeof(portno)) + fatal("snprintf"); + + yywarn("missing `listen on' in server %s," + " assuming %s port %d", $2, default_host, + default_port); + listen_on(default_host, portno); + } } | error '}' { yyerror("bad server directive"); } ; @@ -294,6 +323,22 @@ servopt : ALIAS string { } | PARAM string '=' string { add_param($2, $4); + } + | LISTEN ON STRING PORT STRING { + listen_on($3, $5); + free($3); + free($5); + } + | LISTEN ON STRING PORT NUM { + char portno[32]; + int r; + + r = snprintf(portno, sizeof(portno), "%d", $5); + if (r < 0 || (size_t)r >= sizeof(portno)) + fatal("snprintf"); + + listen_on($3, portno); + free($3); } | locopt ; @@ -515,6 +560,7 @@ static const struct keyword { {"ipv6", IPV6}, {"key", KEY}, {"lang", LANG}, + {"listen", LISTEN}, {"location", LOCATION}, {"log", LOG}, {"ocsp", OCSP}, @@ -913,6 +959,9 @@ parse_conf(struct conf *c, const char *filename) { struct sym *sym, *next; + default_host = "*"; + default_port = 1965; + conf = c; file = pushfile(filename, 0); @@ -1152,4 +1201,79 @@ getservice(const char *n) } return ((unsigned short)llval); +} + +static void +add_to_addr_queue(struct addrhead *a, struct addrinfo *ai) +{ + struct address *addr; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + + if (ai->ai_addrlen > sizeof(addr->ss)) + fatalx("ai_addrlen larger than a sockaddr_storage"); + + TAILQ_FOREACH(addr, a, addrs) { + if (addr->ai_flags == ai->ai_flags && + addr->ai_family == ai->ai_family && + addr->ai_socktype == ai->ai_socktype && + addr->ai_protocol == ai->ai_protocol && + addr->slen == ai->ai_addrlen && + !memcmp(&addr->ss, ai->ai_addr, addr->slen)) + return; + } + + addr = xcalloc(1, sizeof(*addr)); + addr->ai_flags = ai->ai_flags; + addr->ai_family = ai->ai_family; + addr->ai_socktype = ai->ai_socktype; + addr->ai_protocol = ai->ai_protocol; + addr->slen = ai->ai_addrlen; + memcpy(&addr->ss, ai->ai_addr, ai->ai_addrlen); + + /* for commodity */ + switch (addr->ai_family) { + case AF_INET: + sin = (struct sockaddr_in *)&addr->ss; + addr->port = ntohs(sin->sin_port); + break; + case AF_INET6: + sin6 = (struct sockaddr_in6 *)&addr->ss; + addr->port = ntohs(sin6->sin6_port); + break; + default: + fatalx("unknown socket family %d", addr->ai_family); + } + + addr->sock = -1; + + TAILQ_INSERT_HEAD(a, addr, addrs); } + +void +listen_on(const char *hostname, const char *servname) +{ + struct addrinfo hints, *res, *res0; + int error; + + if (!strcmp(hostname, "*")) + hostname = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + error = getaddrinfo(hostname, servname, &hints, &res0); + if (error) { + yyerror("listen on \"%s\" port %s: %s", hostname, servname, + gai_strerror(errno)); + return; + } + + for (res = res0; res; res = res->ai_next) { + add_to_addr_queue(&host->addrs, res); + add_to_addr_queue(&conf->addrs, res); + } + + freeaddrinfo(res0); +} blob - c61285b5f3837c4417ff98a79127247733bb8dab blob + 06cb7a7762eaabf6718673fa06c8ede41211fce2 --- regress/lib.sh +++ regress/lib.sh @@ -10,9 +10,7 @@ current_test= run_test() { ggflags= port=10965 - config_common="ipv6 off -port $port -" + config_common="" hdr= body= dont_check_server_alive=no @@ -64,6 +62,7 @@ server "localhost" { cert "$PWD/cert.pem" key "$PWD/key.pem" root "$PWD/testdata" + listen on localhost port $port $2 } EOF @@ -78,6 +77,7 @@ set_proxy() { server "localhost.local" { cert "$PWD/cert.pem" key "$PWD/key.pem" + listen on localhost port $port proxy { relay-to localhost port $port $1 blob - 5ebc33bdd15f4ea9e6c987995d6c00c0eb5ba344 blob + a38e4ba03a75bf901a4ef6e2a48ab9a6e9c64df3 --- regress/tests.sh +++ regress/tests.sh @@ -239,13 +239,13 @@ test_fastcgi() { test_macro_expansion() { cat < reg.conf pwd = "$PWD" -$config_common server "localhost" { # the quoting of \$ is for sh cert \$pwd "/cert.pem" key \$pwd "/key.pem" root \$pwd "/testdata" + listen on localhost port $port } EOF blob - 05c1d4bf94ed0e278f6b0013480346ad54dd4094 blob + 18e8c91350428e9f2187966fcc6e62397da4a3ca --- server.c +++ server.c @@ -489,7 +489,6 @@ strip_path(const char *path, int strip) static void fmt_sbuf(const char *fmt, struct client *c, const char *path) { - struct conf *conf = c->conf; size_t i; char buf[32]; @@ -519,7 +518,7 @@ fmt_sbuf(const char *fmt, struct client *c, const char strlcat(c->sbuf, c->iri.query, sizeof(c->sbuf)); break; case 'P': - snprintf(buf, sizeof(buf), "%d", conf->port); + snprintf(buf, sizeof(buf), "%d", c->addr->port); strlcat(c->sbuf, buf, sizeof(c->sbuf)); memset(buf, 0, sizeof(buf)); break; @@ -1313,7 +1312,7 @@ client_close(struct client *c) void do_accept(int sock, short et, void *d) { - struct conf *conf = d; + struct address *addr = d; struct client *c; struct sockaddr_storage raddr; struct sockaddr *sraddr; @@ -1332,7 +1331,8 @@ do_accept(int sock, short et, void *d) mark_nonblock(fd); c = xcalloc(1, sizeof(*c)); - c->conf = conf; + c->conf = addr->conf; + c->addr = addr; c->id = ++server_client_id; c->fd = fd; c->pfd = -1; @@ -1486,16 +1486,19 @@ server_init(struct privsep *ps, struct privsep_proc *p int server_configure_done(struct conf *conf) { + struct address *addr; + if (load_default_mime(&conf->mime) == -1) fatal("can't load default mime"); sort_mime(&conf->mime); setup_tls(conf); load_vhosts(conf); - if (conf->sock4 != -1) - event_add(&conf->evsock4, NULL); - if (conf->sock6 != -1) - event_add(&conf->evsock6, NULL); + TAILQ_FOREACH(addr, &conf->addrs, addrs) { + if (addr->sock != -1) + event_add(&addr->evsock, NULL); + } + return 0; } @@ -1509,9 +1512,7 @@ server_dispatch_parent(int fd, struct privsep_proc *p, case IMSG_RECONF_START: case IMSG_RECONF_MIME: case IMSG_RECONF_PROTOS: - case IMSG_RECONF_PORT: - case IMSG_RECONF_SOCK4: - case IMSG_RECONF_SOCK6: + case IMSG_RECONF_SOCK: case IMSG_RECONF_FCGI: case IMSG_RECONF_HOST: case IMSG_RECONF_CERT: blob - 43c1f4d86e007ab4d57004d52162954ce91b70d2 blob + bf28329eca544e589bb3ee70ceb7c34d78ae237b --- utils.c +++ utils.c @@ -346,6 +346,7 @@ new_vhost(void) struct vhost *h; h = xcalloc(1, sizeof(*h)); + TAILQ_INIT(&h->addrs); TAILQ_INIT(&h->locations); TAILQ_INIT(&h->params); TAILQ_INIT(&h->aliases);