commit fdea6aa0bca24f6f947e2126ce101fd59caa7a31 from: Omar Polo date: Fri Apr 30 17:16:34 2021 UTC allow ``root'' rule to be specified per-location block commit - adbe6a6493c0e91fcfc918db8f4b5839a2867b1c commit + fdea6aa0bca24f6f947e2126ce101fd59caa7a31 blob - fd9c39132abca70abee5a57a670e913e6b831021 blob + f6c4288ba7c6a77e42712f451cc92f9dc06a02dd --- ChangeLog +++ ChangeLog @@ -1,3 +1,7 @@ +2021-04-30 Omar Polo + + * gmid.c (load_vhosts): allow ``root'' rule to be specified per-location block + 2021-04-29 Omar Polo * 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) {