Commit Diff


commit - adbe6a6493c0e91fcfc918db8f4b5839a2867b1c
commit + fdea6aa0bca24f6f947e2126ce101fd59caa7a31
blob - fd9c39132abca70abee5a57a670e913e6b831021
blob + f6c4288ba7c6a77e42712f451cc92f9dc06a02dd
--- ChangeLog
+++ ChangeLog
@@ -1,3 +1,7 @@
+2021-04-30  Omar Polo  <op@omarpolo.com>
+
+	* gmid.c (load_vhosts): allow ``root'' rule to be specified per-location block
+
 2021-04-29  Omar Polo  <op@omarpolo.com>
 
 	* parse.y (servopt): added ``alias'' option to define hostname aliases for a server
blob - 6437a4233765bbddba92e87c77363406bc53bd58
blob + 6a5effe3c8d324d6ae4d3ef0af562482f5c53a95
--- ex.c
+++ ex.c
@@ -121,7 +121,8 @@ setenv_time(const char *var, time_t t)
 
 /* fd or -1 on error */
 static int
-launch_cgi(struct iri *iri, struct cgireq *req, struct vhost *vhost)
+launch_cgi(struct iri *iri, struct cgireq *req, struct vhost *vhost,
+    struct location *loc)
 {
 	int p[2];		/* read end, write end */
 
@@ -142,14 +143,14 @@ launch_cgi(struct iri *iri, struct cgireq *req, struct
 		if (dup2(p[1], 1) == -1)
 			goto childerr;
 
-		ex = xasprintf("%s/%s", vhost->dir, req->spath);
+		ex = xasprintf("%s/%s", loc->dir, req->spath);
 
 		serialize_iri(iri, iribuf, sizeof(iribuf));
 
 		safe_setenv("GATEWAY_INTERFACE", "CGI/1.1");
-		safe_setenv("GEMINI_DOCUMENT_ROOT", vhost->dir);
+		safe_setenv("GEMINI_DOCUMENT_ROOT", loc->dir);
 		safe_setenv("GEMINI_SCRIPT_FILENAME",
-		    xasprintf("%s/%s", vhost->dir, req->spath));
+		    xasprintf("%s/%s", loc->dir, req->spath));
 		safe_setenv("GEMINI_URL", iribuf);
 
 		strlcpy(path, "/", sizeof(path));
@@ -161,7 +162,7 @@ launch_cgi(struct iri *iri, struct cgireq *req, struct
 			strlcat(path, req->relpath, sizeof(path));
 			safe_setenv("PATH_INFO", path);
 
-			strlcpy(path, vhost->dir, sizeof(path));
+			strlcpy(path, loc->dir, sizeof(path));
 			strlcat(path, "/", sizeof(path));
 			strlcat(path, req->relpath, sizeof(path));
 			safe_setenv("PATH_TRANSLATED", path);
@@ -242,10 +243,25 @@ host_nth(size_t n)
 	return NULL;
 }
 
+static struct location *
+loc_nth(struct vhost *vhost, size_t n)
+{
+	struct location *loc;
+
+	TAILQ_FOREACH(loc, &vhost->locations, locations) {
+		if (n == 0)
+			return loc;
+		n--;
+	}
+
+	return NULL;
+}
+
 static void
 handle_imsg_cgi_req(struct imsgbuf *ibuf, struct imsg *imsg, size_t datalen)
 {
 	struct vhost	*h;
+	struct location	*l;
 	struct cgireq	 req;
 	struct iri	 iri;
 	int		 fd;
@@ -270,7 +286,10 @@ handle_imsg_cgi_req(struct imsgbuf *ibuf, struct imsg 
 	if ((h = host_nth(req.host_off)) == NULL)
 		abort();
 
-	fd = launch_cgi(&iri, &req, h);
+	if ((l = loc_nth(h, req.host_off)) == NULL)
+		abort();
+
+	fd = launch_cgi(&iri, &req, h, l);
 	imsg_compose(ibuf, IMSG_CGI_RES, imsg->hdr.peerid, 0, fd, NULL, 0);
 	imsg_flush(ibuf);
 }
blob - d28bae1cef3eb44cd32e6ebe6c7f5ad4d770efc4
blob + 852ee90c2bea8fd0efa3eca8a7faf0dc9b7befa7
--- gmid.1
+++ gmid.1
@@ -286,7 +286,7 @@ A
 .Ic location
 section may include most of the server configuration rules
 except
-.Ic alias , Ic cert , Ic env , Ic key , Ic root , Ic location ,
+.Ic alias , Ic cert , Ic env , Ic key , Ic location ,
 .Ic entrypoint No and Ic cgi .
 .It Ic root Pa directory
 Specify the root directory for this server.
blob - 0af53f3f5eb37e5466caec6042d9b2127bd9e760
blob + dda03832b0589c754d1d907b11520e5e6167eae1
--- gmid.c
+++ gmid.c
@@ -114,11 +114,16 @@ load_local_cert(const char *hostname, const char *dir)
 void
 load_vhosts(void)
 {
-	struct vhost *h;
+	struct vhost	*h;
+	struct location	*l;
 
 	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);
+		TAILQ_FOREACH(l, &h->locations, locations) {
+			if (l->dir == NULL)
+				continue;
+			if ((l->dirfd = open(l->dir, O_RDONLY | O_DIRECTORY)) == -1)
+				fatal("open %s for domain %s", l->dir, h->domain);
+		}
 	}
 }
 
@@ -265,6 +270,11 @@ free_config(void)
 			free((char*)l->default_mime);
 			free((char*)l->index);
 			free((char*)l->block_fmt);
+			free((char*)l->dir);
+
+			if (l->dirfd != -1)
+				close(l->dirfd);
+
 			free(l);
 		}
 
@@ -279,6 +289,12 @@ free_config(void)
 			free(a);
 		}
 
+		free((char*)h->domain);
+		free((char*)h->cert);
+		free((char*)h->key);
+		free((char*)h->cgi);
+		free((char*)h->entrypoint);
+
 		TAILQ_REMOVE(&hosts, h, vhosts);
 		free(h);
 	}
@@ -388,17 +404,17 @@ serve(int argc, char **argv, struct imsgbuf *ibuf)
 
 		switch (argc) {
 		case 0:
-			h->dir = getcwd(path, sizeof(path));
+			l->dir = getcwd(path, sizeof(path));
 			break;
 		case 1:
-			h->dir = absolutify_path(argv[0]);
+			l->dir = absolutify_path(argv[0]);
 			break;
 		default:
 			usage(getprogname());
 			return 1;
 		}
 
-		log_notice(NULL, "serving %s on port %d", h->dir, conf.port);
+		log_notice(NULL, "serving %s on port %d", l->dir, conf.port);
 	}
 
 	/* setup tls before dropping privileges: we don't want user
blob - 5eec8af079d3151b5a57d86bb012bcc24acafeed
blob + 818f8d76d76bb58230bf2f5747cface67fd28203
--- gmid.h
+++ gmid.h
@@ -69,6 +69,9 @@ struct location {
 	int		 strip;
 	X509_STORE	*reqca;
 	int		 disable_log;
+
+	const char	*dir;
+	int		 dirfd;
 
 	TAILQ_ENTRY(location) locations;
 };
@@ -91,10 +94,8 @@ struct vhost {
 	const char	*domain;
 	const char	*cert;
 	const char	*key;
-	const char	*dir;
 	const char	*cgi;
 	const char	*entrypoint;
-	int		 dirfd;
 
 	TAILQ_ENTRY(vhost) vhosts;
 
@@ -234,6 +235,7 @@ struct cgireq {
 	time_t		notafter;
 
 	size_t		host_off;
+	size_t		loc_off;
 };
 
 enum {
blob - 59e1a6c42461c4968a2ab87b961ae7926c89839b
blob + c93e471d26dda69c44b91a928567c124bd8b0541
--- parse.y
+++ parse.y
@@ -108,8 +108,7 @@ vhost		: TSERVER TSTRING {
 			}
 		} '{' servopts locations '}' {
 
-			if (host->cert == NULL || host->key == NULL ||
-			    host->dir == NULL)
+			if (host->cert == NULL || host->key == NULL)
 				yyerror("invalid vhost definition: %s", $2);
 		}
 		| error '}'		{ yyerror("error in server directive"); }
@@ -155,7 +154,6 @@ servopt		: TALIAS TSTRING {
 				TAILQ_INSERT_TAIL(&host->env, e, envs);
 		}
 		| TKEY TSTRING		{ host->key  = ensure_absolute_path($2); }
-		| TROOT TSTRING		{ host->dir  = ensure_absolute_path($2); }
 		| locopt
 		;
 
@@ -221,6 +219,12 @@ locopt		: TAUTO TINDEX TBOOL	{ loc->auto_index = $3 ? 
 			if ((loc->reqca = load_ca($4)) == NULL)
 				yyerror("couldn't load ca cert: %s", $4);
 			free($4);
+		}
+		| TROOT TSTRING		{
+			if (loc->dir != NULL)
+				yyerror("`root' specified more than once");
+
+			loc->dir  = ensure_absolute_path($2);
 		}
 		| TSTRIP TNUM		{ loc->strip = check_strip_no($2); }
 		;
@@ -236,7 +240,11 @@ new_vhost(void)
 static struct location *
 new_location(void)
 {
-	return xcalloc(1, sizeof(struct location));
+	struct location *l;
+
+	l = xcalloc(1, sizeof(*l));
+	l->dirfd = -1;
+	return l;
 }
 
 void
blob - d24ac08e71e386d883506b38dcb1d58d510c0a4f
blob + 53f154981a51d0c6f424563b4272a62d4410f025
--- regress/runtime
+++ regress/runtime
@@ -304,3 +304,16 @@ eq "$(head /)"		"61 certificate not authorised"	"Unexp
 echo OK GET / with invalid client certificate
 
 ggflags=''
+
+
+# test with root inside a location
+
+config '' 'location "/foo/*" { root "'$PWD'/testdata" strip 1 }'
+checkconf
+restart
+
+# XXX: this fails, because /foo isn't matched by /foo/*, but it would
+# be nice if it worked.
+#eq "$(head /foo)"	"30 /foo/"		"Unexpected head for /foo"
+
+eq "$(head /foo/)"	"20 text/gemini"	"Unexpected head for /foo/"
blob - b689a27ba8a2ae4af33d3c87b4955370e27940c8
blob + 4e107392d2ac379331c4a93820e852aa076e3d64
--- sandbox.c
+++ sandbox.c
@@ -280,11 +280,19 @@ sandbox_logger_process(void)
 void
 sandbox_server_process(void)
 {
-	struct vhost *h;
+	struct vhost	*h;
+	struct location	*l;
 
 	TAILQ_FOREACH(h, &hosts, vhosts) {
-		if (unveil(h->dir, "r") == -1)
-			fatal("unveil %s for domain %s", h->dir, h->domain);
+		TAILQ_FOREACH(l, &h->locations, locations) {
+			if (l->dir == NULL)
+				continue;
+
+			if (unveil(l->dir, "r") == -1)
+				fatal("unveil %s for domain %s",
+				    l->dir,
+				    h->domain);
+		}
 	}
 
 	if (pledge("stdio recvfd rpath inet", NULL) == -1)
@@ -295,12 +303,18 @@ void
 sandbox_executor_process(void)
 {
 	struct vhost	*h;
+	struct location	*l;
 
 	TAILQ_FOREACH(h, &hosts, vhosts) {
-		/* r so we can chdir into the correct directory */
-		if (unveil(h->dir, "rx") == -1)
-			err(1, "unveil %s for domain %s",
-			    h->dir, h->domain);
+		TAILQ_FOREACH(l, &h->locations, locations) {
+			if (l->dir == NULL)
+				continue;
+
+			/* r so we can chdir into the correct directory */
+			if (unveil(l->dir, "rx") == -1)
+				fatal("unveil %s for domain %s",
+				    l->dir, h->domain);
+		}
 	}
 
 	/* rpath to chdir into the correct directory */
blob - 86c8d0886707548054ba07f1a8004df3328bcee7
blob + 60d1da89e8af270710e78b6adaf856ed25f9a56f
--- server.c
+++ server.c
@@ -203,6 +203,25 @@ vhost_block_return(struct vhost *v, const char *path, 
 }
 
 int
+vhost_dirfd(struct vhost *v, const char *path)
+{
+	struct location *loc;
+
+	if (v == NULL || path == NULL)
+		return -1;
+
+	loc = TAILQ_FIRST(&v->locations);
+	while ((loc = TAILQ_NEXT(loc, locations)) != NULL) {
+		if (loc->dirfd != -1)
+			if (matches(loc->match, path))
+				return loc->dirfd;
+	}
+
+	loc = TAILQ_FIRST(&v->locations);
+	return loc->dirfd;
+}
+
+int
 vhost_strip(struct vhost *v, const char *path)
 {
 	struct location *loc;
@@ -265,22 +284,30 @@ check_path(struct client *c, const char *path, int *fd
 {
 	struct stat sb;
 	const char *p;
-	int flags;
+	int flags, dirfd, strip;
 
 	assert(path != NULL);
 
-	if (*path == '\0')
+	/*
+	 * in send_dir we add an initial / (to be redirect-friendly),
+	 * but here we want to skip it
+	 */
+	if (*path == '/')
+		path++;
+
+	strip = vhost_strip(c->host, path);
+	p = strip_path(path, strip);
+
+	if (*p == '/')
+		p = p+1;
+	if (*p == '\0')
 		p = ".";
-	else if (*path == '/')
-		/* in send_dir we add an initial / (to be
-		 * redirect-friendly), but here we want to skip it */
-		p = path+1;
-	else
-		p = path;
 
+	dirfd = vhost_dirfd(c->host, path);
+	log_debug(c, "check_path: strip=%d path=%s original=%s",
+	    strip, p, path);
 	flags = O_RDONLY | O_NOFOLLOW;
-
-	if (*fd == -1 && (*fd = openat(c->host->dirfd, p, flags)) == -1)
+	if (*fd == -1 && (*fd = openat(dirfd, p, flags)) == -1)
 		return FILE_MISSING;
 
 	if (fstat(*fd, &sb) == -1) {