Commit Diff


commit - e0ebdf2d94159db669a67972b760d1920f11310b
commit + b8e64ccd44290cdd34bdcd3fd85fb1a9cb7486dd
blob - 5e619082646bc5c2701c976732b3310ebc1ee273
blob + 9ad4b87c204fb4c66b2cb25cfa94fef1440566d3
--- ChangeLog
+++ ChangeLog
@@ -1,5 +1,7 @@
 2021-03-31  Omar Polo  <op@omarpolo.com>
 
+	* gmid.h (struct vhost): remove limits on the number of vhosts and location blocks
+
 	* gmid.c (mkdirs): fix recursive mkdirs for configless mode
 
 2021-03-29  Omar Polo  <op@omarpolo.com>
blob - d553fe7252c39ca6f915e92ad243c1cda2ab2623
blob + e08da7be14cfa7abdbfba2d3a8116e6f008be459
--- ex.c
+++ ex.c
@@ -210,12 +210,27 @@ childerr:
 	_exit(1);
 }
 
+static struct vhost *
+host_nth(size_t n)
+{
+	struct vhost *h;
+
+	TAILQ_FOREACH(h, &hosts, vhosts) {
+		if (n == 0)
+			return h;
+		n--;
+	}
+
+	return NULL;
+}
+
 static void
 handle_imsg_cgi_req(struct imsgbuf *ibuf, struct imsg *imsg, size_t datalen)
 {
-	struct cgireq	req;
-	struct iri	iri;
-	int		fd;
+	struct vhost	*h;
+	struct cgireq	 req;
+	struct iri	 iri;
+	int		 fd;
 
 	if (datalen != sizeof(req))
 		abort();
@@ -234,10 +249,10 @@ handle_imsg_cgi_req(struct imsgbuf *ibuf, struct imsg 
 	if (*iri.query == '\0')
 		iri.query = NULL;
 
-	if (req.host_off > HOSTSLEN || hosts[req.host_off].domain == NULL)
+	if ((h = host_nth(req.host_off)) == NULL)
 		abort();
 
-	fd = launch_cgi(&iri, &req, &hosts[req.host_off]);
+	fd = launch_cgi(&iri, &req, h);
 	imsg_compose(ibuf, IMSG_CGI_RES, imsg->hdr.peerid, 0, fd, NULL, 0);
 	imsg_flush(ibuf);
 }
blob - 120459636dc2d4ffd321617a3cf1efcddca0adb8
blob + e125d90c1af96db04be1dc75ba1abe6242c6516d
--- gmid.c
+++ gmid.c
@@ -26,7 +26,7 @@
 #include <signal.h>
 #include <string.h>
 
-struct vhost hosts[HOSTSLEN];
+struct vhosthead hosts;
 
 int sock4, sock6;
 
@@ -95,6 +95,7 @@ void
 load_local_cert(const char *hostname, const char *dir)
 {
 	char *cert, *key;
+	struct vhost *h;
 
 	if (asprintf(&cert, "%s/%s.cert.pem", dir, hostname) == -1)
 		errx(1, "asprintf");
@@ -104,9 +105,10 @@ load_local_cert(const char *hostname, const char *dir)
 	if (access(cert, R_OK) == -1 || access(key, R_OK) == -1)
 		gen_certificate(hostname, cert, key);
 
-	hosts[0].cert = cert;
-	hosts[0].key = key;
-	hosts[0].domain = hostname;
+	h = TAILQ_FIRST(&hosts);
+	h->cert = cert;
+	h->key = key;
+	h->domain = hostname;
 }
 
 void
@@ -114,7 +116,7 @@ load_vhosts(void)
 {
 	struct vhost *h;
 
-	for (h = hosts; h->domain != NULL; ++h) {
+	TAILQ_FOREACH(h, &hosts, vhosts) {
 		if ((h->dirfd = open(h->dir, O_RDONLY | O_DIRECTORY)) == -1)
 			fatal("open %s for domain %s", h->dir, h->domain);
 	}
@@ -193,12 +195,14 @@ setup_tls(void)
 	if ((ctx = tls_server()) == NULL)
 		fatal("tls_server failure");
 
+	h = TAILQ_FIRST(&hosts);
+
 	/* we need to set something, then we can add how many key we want */
-	if (tls_config_set_keypair_file(tlsconf, hosts->cert, hosts->key))
+	if (tls_config_set_keypair_file(tlsconf, h->cert, h->key))
 		fatal("tls_config_set_keypair_file failed for (%s, %s)",
-		    hosts->cert, hosts->key);
+		    h->cert, h->key);
 
-	for (h = &hosts[1]; h->domain != NULL; ++h) {
+	while ((h = TAILQ_NEXT(h, vhosts)) != NULL) {
 		if (tls_config_add_keypair_file(tlsconf, h->cert, h->key) == -1)
 			fatal("failed to load the keypair (%s, %s)",
 			    h->cert, h->key);
@@ -221,11 +225,7 @@ listener_main(struct imsgbuf *ibuf)
 void
 init_config(void)
 {
-	size_t i;
-
-	bzero(hosts, sizeof(hosts));
-	for (i = 0; i < HOSTSLEN; ++i)
-		hosts[i].dirfd = -1;
+	TAILQ_INIT(&hosts);
 
 	conf.port = 1965;
 	conf.ipv6 = 0;
@@ -236,37 +236,34 @@ init_config(void)
 	conf.chroot = NULL;
 	conf.user = NULL;
 
-	/* we'll change this to 0 when running without config. */
 	conf.prefork = 3;
 }
 
 void
 free_config(void)
 {
-	struct vhost *h;
-	struct location *l;
+	struct vhost *h, *th;
+	struct location *l, *tl;
 
 	free(conf.chroot);
 	free(conf.user);
 	memset(&conf, 0, sizeof(conf));
 
-	for (h = hosts; h->domain != NULL; ++h) {
-		free((char*)h->domain);
-		free((char*)h->cert);
-		free((char*)h->key);
-		free((char*)h->dir);
-		free((char*)h->cgi);
-		free((char*)h->entrypoint);
+	TAILQ_FOREACH_SAFE(h, &hosts, vhosts, th) {
+		TAILQ_FOREACH_SAFE(l, &h->locations, locations, tl) {
+			TAILQ_REMOVE(&h->locations, l, locations);
 
-		for (l = h->locations; l->match != NULL; ++l) {
 			free((char*)l->match);
 			free((char*)l->lang);
 			free((char*)l->default_mime);
 			free((char*)l->index);
 			free((char*)l->block_fmt);
+			free(l);
 		}
+
+		TAILQ_REMOVE(&hosts, h, vhosts);
+		free(h);
 	}
-	memset(hosts, 0, sizeof(hosts));
 
 	tls_free(ctx);
 	tls_config_free(tlsconf);
@@ -352,8 +349,10 @@ logger_init(void)
 static int
 serve(int argc, char **argv, struct imsgbuf *ibuf)
 {
-	char	path[PATH_MAX];
-	int	i, p[2];
+	char		 path[PATH_MAX];
+	int		 i, p[2];
+	struct vhost	*h;
+	struct location	*l;
 
         if (config_path == NULL) {
 		if (hostname == NULL)
@@ -362,23 +361,26 @@ serve(int argc, char **argv, struct imsgbuf *ibuf)
 			certs_dir = data_dir();
 		load_local_cert(hostname, certs_dir);
 
-		hosts[0].domain = "*";
-		hosts[0].locations[0].auto_index = 1;
-		hosts[0].locations[0].match = "*";
+		h = TAILQ_FIRST(&hosts);
+		h->domain = "*";
 
+		l = TAILQ_FIRST(&h->locations);
+		l->auto_index = 1;
+		l->match = "*";
+
 		switch (argc) {
 		case 0:
-			hosts[0].dir = getcwd(path, sizeof(path));
+			h->dir = getcwd(path, sizeof(path));
 			break;
 		case 1:
-			hosts[0].dir = absolutify_path(argv[0]);
+			h->dir = absolutify_path(argv[0]);
 			break;
 		default:
 			usage(getprogname());
 			return 1;
 		}
 
-		log_notice(NULL, "serving %s on port %d", hosts[0].dir, conf.port);
+		log_notice(NULL, "serving %s on port %d", h->dir, conf.port);
 	}
 
 	/* setup tls before dropping privileges: we don't want user
@@ -409,12 +411,31 @@ serve(int argc, char **argv, struct imsgbuf *ibuf)
 	_exit(executor_main(ibuf));
 }
 
+static void
+setup_configless(int argc, char **argv, const char *cgi)
+{
+	struct vhost	*host;
+	struct location	*loc;
+
+	host = xcalloc(1, sizeof(*host));
+	host->cgi = cgi;
+	TAILQ_INSERT_HEAD(&hosts, host, vhosts);
+
+	loc = xcalloc(1, sizeof(*loc));
+	TAILQ_INSERT_HEAD(&host->locations, loc, locations);
+
+	serve(argc, argv, NULL);
+	imsg_compose(&logibuf, IMSG_QUIT, 0, 0, -1, NULL, 0);
+	imsg_flush(&logibuf);
+}
+
 int
 main(int argc, char **argv)
 {
 	struct imsgbuf exibuf;
 	int ch, conftest = 0, configless = 0;
 	int old_ipv6, old_port;
+	const char *cgi = NULL;
 
 	init_config();
 
@@ -464,7 +485,7 @@ main(int argc, char **argv)
 			/* drop the starting / (if any) */
 			if (*optarg == '/')
 				optarg++;
-			hosts[0].cgi = optarg;
+			cgi = optarg;
 			configless = 1;
 			break;
 
@@ -511,13 +532,10 @@ main(int argc, char **argv)
 		sock6 = make_socket(conf.port, AF_INET6);
 
 	if (configless) {
-		serve(argc, argv, NULL);
-		imsg_compose(&logibuf, IMSG_QUIT, 0, 0, -1, NULL, 0);
-		imsg_flush(&logibuf);
+		setup_configless(argc, argv, cgi);
 		return 0;
 	}
 
-
 	/* Linux seems to call the event handlers even when we're
 	 * doing a sigwait.  These dummy handlers is here to avoid
 	 * being terminated on SIGHUP, SIGTERM or SIGINFO. */
blob - 17a1315ce7bd43cc62af145c4801b1a3756bfb0c
blob + 040913630bc5b85e04b377347b640d1605bd6e3b
--- gmid.h
+++ gmid.h
@@ -51,15 +51,13 @@
 
 #define MAX_USERS	64
 
-#define HOSTSLEN	64
-#define LOCLEN		32
-
 /* maximum hostname and label length, +1 for the NUL-terminator */
 #define DOMAIN_NAME_LEN	(253+1)
 #define LABEL_LEN	(63+1)
 
 #define PROC_MAX	16
 
+TAILQ_HEAD(lochead, location);
 struct location {
 	const char	*match;
 	const char	*lang;
@@ -71,8 +69,11 @@ struct location {
 	int		 strip;
 	X509_STORE	*reqca;
 	int		 disable_log;
+
+	TAILQ_ENTRY(location) locations;
 };
 
+extern TAILQ_HEAD(vhosthead, vhost) hosts;
 struct vhost {
 	const char	*domain;
 	const char	*cert;
@@ -81,15 +82,15 @@ struct vhost {
 	const char	*cgi;
 	const char	*entrypoint;
 	int		 dirfd;
+
+	TAILQ_ENTRY(vhost) vhosts;
 
 	/* the first location rule is always '*' and holds the default
-	 * settings for the vhost, from locations[1] onwards there are
-	 * the "real" location rules specified in the configuration. */
-	struct location	 locations[LOCLEN];
+	 * settings for the vhost, then follows the "real" location
+	 * rules as specified in the configuration. */
+	struct lochead	 locations;
 };
 
-extern struct vhost hosts[HOSTSLEN];
-
 struct etm {			/* extension to mime */
 	const char	*mime;
 	const char	*ext;
@@ -321,6 +322,7 @@ int		 ends_with(const char*, const char*);
 ssize_t		 filesize(int);
 char		*absolutify_path(const char*);
 char		*xstrdup(const char*);
+void		*xcalloc(size_t, size_t);
 void		 gen_certificate(const char*, const char*, const char*);
 X509_STORE	*load_ca(const char*);
 int		 validate_against_ca(X509_STORE*, const uint8_t*, size_t);
blob - 717bcdcd6f01bf609b59e1d4cc6bef832e73b4fa
blob + 0646f137fff423e407a151e744895d056b12169a
--- parse.y
+++ parse.y
@@ -30,12 +30,13 @@
  */
 
 struct vhost *host;
-size_t ihost;
 struct location *loc;
-size_t iloc;
 
 int goterror = 0;
 
+static struct vhost	*new_vhost(void);
+static struct location	*new_location(void);
+
 void		 yyerror(const char*, ...);
 int		 parse_portno(const char*);
 void		 parse_conf(const char*);
@@ -90,8 +91,14 @@ vhosts		: /* empty */
 		| vhosts vhost
 		;
 
-vhost		: TSERVER TSTRING '{' servopts locations '}' {
-			host->locations[0].match = xstrdup("*");
+vhost		: TSERVER TSTRING {
+			host = new_vhost();
+			TAILQ_INSERT_HEAD(&hosts, host, vhosts);
+
+			loc = new_location();
+			TAILQ_INSERT_HEAD(&host->locations, loc, locations);
+
+			loc->match = xstrdup("*");
 			host->domain = $2;
 
 			if (strstr($2, "xn--") != NULL) {
@@ -99,17 +106,11 @@ vhost		: TSERVER TSTRING '{' servopts locations '}' {
 				    "you should use the decoded hostname.",
 				    config_path, yylineno, $2);
 			}
+		} '{' servopts locations '}' {
 
 			if (host->cert == NULL || host->key == NULL ||
 			    host->dir == NULL)
 				yyerror("invalid vhost definition: %s", $2);
-
-			if (++ihost == HOSTSLEN)
-				errx(1, "too much vhosts defined");
-
-			host++;
-			loc = &host->locations[0];
-			iloc = 0;
 		}
 		| error '}'		{ yyerror("error in server directive"); }
 		;
@@ -204,7 +205,19 @@ locopt		: TAUTO TINDEX TBOOL	{ loc->auto_index = $3 ? 
 		;
 
 %%
+
+static struct vhost *
+new_vhost(void)
+{
+	return xcalloc(1, sizeof(struct vhost));
+}
 
+static struct location *
+new_location(void)
+{
+	return xcalloc(1, sizeof(struct location));
+}
+
 void
 yyerror(const char *msg, ...)
 {
@@ -234,11 +247,6 @@ parse_portno(const char *p)
 void
 parse_conf(const char *path)
 {
-	host = &hosts[0];
-	ihost = 0;
-	loc = &hosts[0].locations[0];
-	iloc = 0;
-
 	config_path = path;
 	if ((yyin = fopen(path, "r")) == NULL)
 		fatal("cannot open config: %s: %s", path, strerror(errno));
@@ -247,6 +255,9 @@ parse_conf(const char *path)
 
 	if (goterror)
 		exit(1);
+
+	if (TAILQ_FIRST(&hosts)->domain == NULL)
+		fatal("no vhost defined in %s", path);
 }
 
 char *
@@ -307,7 +318,6 @@ check_prefork_num(int n)
 void
 advance_loc(void)
 {
-	if (++iloc == LOCLEN)
-		errx(1, "too much location rules defined");
-	loc++;
+	loc = new_location();
+	TAILQ_INSERT_TAIL(&host->locations, loc, locations);
 }
blob - 1a0dc9a4253a1896cf6f058905221d02c6e11def
blob + b689a27ba8a2ae4af33d3c87b4955370e27940c8
--- sandbox.c
+++ sandbox.c
@@ -282,7 +282,7 @@ sandbox_server_process(void)
 {
 	struct vhost *h;
 
-	for (h = hosts; h->domain != NULL; ++h) {
+	TAILQ_FOREACH(h, &hosts, vhosts) {
 		if (unveil(h->dir, "r") == -1)
 			fatal("unveil %s for domain %s", h->dir, h->domain);
 	}
@@ -294,13 +294,13 @@ sandbox_server_process(void)
 void
 sandbox_executor_process(void)
 {
-	struct vhost	*vhost;
+	struct vhost	*h;
 
-	for (vhost = hosts; vhost->domain != NULL; ++vhost) {
+	TAILQ_FOREACH(h, &hosts, vhosts) {
 		/* r so we can chdir into the correct directory */
-		if (unveil(vhost->dir, "rx") == -1)
+		if (unveil(h->dir, "rx") == -1)
 			err(1, "unveil %s for domain %s",
-			    vhost->dir, vhost->domain);
+			    h->dir, h->domain);
 	}
 
 	/* rpath to chdir into the correct directory */
blob - 0080b17b42ba540f8d10b754089ad1129bee9027
blob + effc2f73eac85d3e2d7e7c9fa9c74ae75eae74e5
--- server.c
+++ server.c
@@ -50,6 +50,7 @@ static int	 apply_require_ca(struct client*);
 static void	 handle_open_conn(int, short, void*);
 static void	 start_reply(struct client*, int, const char*);
 static void	 handle_start_reply(int, short, void*);
+static size_t	 host_nth(struct vhost*);
 static void	 start_cgi(const char*, const char*, struct client*);
 static void	 open_dir(struct client*);
 static void	 redirect_canonical_dir(struct client*);
@@ -99,14 +100,15 @@ vhost_lang(struct vhost *v, const char *path)
 	if (v == NULL || path == NULL)
 		return NULL;
 
-	for (loc = &v->locations[1]; loc->match != NULL; ++loc) {
+	loc = TAILQ_FIRST(&v->locations);
+	while ((loc = TAILQ_NEXT(loc, locations)) != NULL) {
 		if (loc->lang != NULL) {
 			if (matches(loc->match, path))
 				return loc->lang;
 		}
 	}
 
-	return v->locations[0].lang;
+	return TAILQ_FIRST(&v->locations)->lang;
 }
 
 const char *
@@ -118,15 +120,17 @@ vhost_default_mime(struct vhost *v, const char *path)
 	if (v == NULL || path == NULL)
 		return default_mime;
 
-	for (loc = &v->locations[1]; loc->match != NULL; ++loc) {
+	loc = TAILQ_FIRST(&v->locations);
+	while ((loc = TAILQ_NEXT(loc, locations)) != NULL) {
 		if (loc->default_mime != NULL) {
 			if (matches(loc->match, path))
 				return loc->default_mime;
 		}
 	}
 
-	if (v->locations[0].default_mime != NULL)
-		return v->locations[0].default_mime;
+	loc = TAILQ_FIRST(&v->locations);
+	if (loc->default_mime != NULL)
+		return loc->default_mime;
 	return default_mime;
 }
 
@@ -139,15 +143,17 @@ vhost_index(struct vhost *v, const char *path)
 	if (v == NULL || path == NULL)
 		return index;
 
-	for (loc = &v->locations[1]; loc->match != NULL; ++loc) {
+	loc = TAILQ_FIRST(&v->locations);
+	while ((loc = TAILQ_NEXT(loc, locations)) != NULL) {
 		if (loc->index != NULL) {
 			if (matches(loc->match, path))
 				return loc->index;
 		}
 	}
 
-	if (v->locations[0].index != NULL)
-		return v->locations[0].index;
+	loc = TAILQ_FIRST(&v->locations);
+	if (loc->index != NULL)
+		return loc->index;
 	return index;
 }
 
@@ -159,14 +165,16 @@ vhost_auto_index(struct vhost *v, const char *path)
 	if (v == NULL || path == NULL)
 		return 0;
 
-	for (loc = &v->locations[1]; loc->match != NULL; ++loc) {
+	loc = TAILQ_FIRST(&v->locations);
+	while ((loc = TAILQ_NEXT(loc, locations)) != NULL) {
 		if (loc->auto_index != 0) {
 			if (matches(loc->match, path))
 				return loc->auto_index == 1;
 		}
 	}
 
-	return v->locations[0].auto_index == 1;
+	loc = TAILQ_FIRST(&v->locations);
+	return loc->auto_index == 1;
 }
 
 int
@@ -177,7 +185,8 @@ vhost_block_return(struct vhost *v, const char *path, 
 	if (v == NULL || path == NULL)
 		return 0;
 
-	for (loc = &v->locations[1]; loc->match != NULL; ++loc) {
+	loc = TAILQ_FIRST(&v->locations);
+	while ((loc = TAILQ_NEXT(loc, locations)) != NULL) {
 		if (loc->block_code != 0) {
 			if (matches(loc->match, path)) {
 				*code = loc->block_code;
@@ -187,9 +196,10 @@ vhost_block_return(struct vhost *v, const char *path, 
 		}
 	}
 
-	*code = v->locations[0].block_code;
-	*fmt = v->locations[0].block_fmt;
-	return v->locations[0].block_code != 0;
+	loc = TAILQ_FIRST(&v->locations);
+	*code = loc->block_code;
+	*fmt = loc->block_fmt;
+	return loc->block_code != 0;
 }
 
 int
@@ -200,14 +210,16 @@ vhost_strip(struct vhost *v, const char *path)
 	if (v == NULL || path == NULL)
 		return 0;
 
-	for (loc = &v->locations[1]; loc->match != NULL; ++loc) {
+	loc = TAILQ_FIRST(&v->locations);
+	while ((loc = TAILQ_NEXT(loc, locations)) != NULL) {
 		if (loc->strip != 0) {
 			if (matches(loc->match, path))
 				return loc->strip;
 		}
 	}
 
-	return v->locations[0].strip;
+	loc = TAILQ_FIRST(&v->locations);
+	return loc->strip;
 }
 
 X509_STORE *
@@ -218,14 +230,16 @@ vhost_require_ca(struct vhost *v, const char *path)
 	if (v == NULL || path == NULL)
 		return NULL;
 
-	for (loc = &v->locations[1]; loc->match != NULL; ++loc) {
+	loc = TAILQ_FIRST(&v->locations);
+	while ((loc = TAILQ_NEXT(loc, locations)) != NULL) {
 		if (loc->reqca != NULL) {
 			if (matches(loc->match, path))
 				return loc->reqca;
 		}
 	}
 
-	return v->locations[0].reqca;
+	loc = TAILQ_FIRST(&v->locations);
+	return loc->reqca;
 }
 
 int
@@ -236,12 +250,14 @@ vhost_disable_log(struct vhost *v, const char *path)
 	if (v == NULL || path == NULL)
 		return 0;
 
-	for (loc = &v->locations[1]; loc->match != NULL; ++loc) {
+	loc = TAILQ_FIRST(&v->locations);
+	while ((loc = TAILQ_NEXT(loc, locations)) != NULL) {
 		if (loc->disable_log && matches(loc->match, path))
 				return 1;
 	}
 
-	return v->locations[0].disable_log;
+	loc = TAILQ_FIRST(&v->locations);
+	return loc->disable_log;
 }
 
 static int
@@ -399,7 +415,7 @@ handle_handshake(int fd, short ev, void *d)
 		goto err;
 	}
 
-	for (h = hosts; h->domain != NULL; ++h) {
+	TAILQ_FOREACH(h, &hosts, vhosts) {
 		if (matches(h->domain, c->domain))
 			break;
 	}
@@ -407,9 +423,9 @@ handle_handshake(int fd, short ev, void *d)
 	log_debug(c, "handshake: SNI: \"%s\"; decoded: \"%s\"; matched: \"%s\"",
 	    servname != NULL ? servname : "(null)",
 	    c->domain,
-	    h->domain != NULL ? h->domain : "(null)");
+	    h != NULL ? h->domain : "(null)");
 
-	if (h->domain != NULL) {
+	if (h != NULL) {
 		c->host = h;
 		handle_open_conn(fd, ev, c);
 		return;
@@ -633,6 +649,21 @@ handle_start_reply(int fd, short ev, void *d)
 		c->next(fd, ev, c);
 }
 
+static size_t
+host_nth(struct vhost *h)
+{
+	struct vhost	*v;
+	size_t		 i = 0;
+
+	TAILQ_FOREACH(v, &hosts, vhosts) {
+		if (v == h)
+			return i;
+		i++;
+	}
+
+	abort();
+}
+
 static void
 start_cgi(const char *spath, const char *relpath, struct client *c)
 {
@@ -675,7 +706,7 @@ start_cgi(const char *spath, const char *relpath, stru
 	req.notbefore = tls_peer_cert_notbefore(c->ctx);
 	req.notafter = tls_peer_cert_notafter(c->ctx);
 
-	req.host_off = c->host - hosts;
+	req.host_off = host_nth(c->host);
 
 	imsg_compose(&exibuf, IMSG_CGI_REQ, c->id, 0, -1, &req, sizeof(req));
 	imsg_flush(&exibuf);
blob - 5d831f18895f38d4cdd94cd7c66d36aeb8bb8a02
blob + 1fda993c183f58bc7681c1c603f6a173e993c987
--- utils.c
+++ utils.c
@@ -93,6 +93,16 @@ xstrdup(const char *s)
 
 	if ((d = strdup(s)) == NULL)
 		err(1, "strdup");
+	return d;
+}
+
+void *
+xcalloc(size_t nmemb, size_t size)
+{
+	void *d;
+
+	if ((d = calloc(nmemb, size)) == NULL)
+		err(1, "calloc");
 	return d;
 }