Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 27 additions & 11 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,33 @@ serves a file or dispatches a CGI:
> for that route rather than locking it. `DEBUG 1` traces every such check, so
> verify a new `RES=` route against the debug log before relying on it.

A route that carries an auth policy is registered or the server does not start.
If the policy cannot be built or the route cannot be added (both only happen when
the region runs out of storage while the Parmlib is read), the server issues
`HTTPD418E`/`HTTPD419E` naming the route, then `HTTPD420E` and terminates before
the listener is bound. Silently dropping the route would serve its requests under
the global `LOGIN` default instead — for a `LOC=` prefix under `LOGIN NONE` that
means handing out the whole protected subtree. A route that fails to register
with no policy at all, or with `AUTH=NONE` only, stays a warning and the server
continues — the fallback can only be stricter than what it asked for. The one
exception is a `MOD=`/`LOC=` line the parser could not even tokenize: whether it
carried a policy is then unknowable, so it is treated as if it did.
**A route that carries an auth policy is registered or the server does not
start.** Dropping it is not a safe fallback: the route does not disappear, its
requests are served under the global `LOGIN` default instead — and for a `LOC=`
prefix under `LOGIN NONE` that hands out the whole subtree it was protecting. So
whenever such a route cannot be built, the server issues `HTTPD418E`/`HTTPD419E`
naming it, then `HTTPD420E`, and terminates before the listener is bound. Three
things reach that path:

- the policy itself could not be built, or the route could not be added (both
only when the region runs out of storage while the Parmlib is read)
- the line could not be tokenized at all — whether it carried a policy is then
unknowable, so it is assumed to have
- **the positional token is missing and an option stands in its place** — the
typo case, no allocation failure needed:

```
LOC=AUTH=BASIC RES=FACILITY:HTTPD.ADMIN path forgotten
MOD=AUTH=BASIC /zosmf/* program name forgotten
```

The first once published exactly the subtree it named; the second folded the
option into an 8-character module name (`AUTH=BAS`) and registered a route
that could never load.

A route that fails to register with no policy at all, or with `AUTH=NONE` only,
stays a warning and the server continues — the fallback can only be stricter
than what it asked for.

| Option | Values | Description |
|--------|--------|-------------|
Expand Down
44 changes: 44 additions & 0 deletions src/httpprm.c
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,34 @@ route_policy_lost(HTTPD *httpd, const char *kind, const char *path)
httpd->flag |= HTTPD_FLAG_CFGERR;
}

/* route_malformed() - a MOD=/LOC= line was rejected before a route could be
** built, because the positional token it needs (program name / path) is missing
** and an AUTH=/RES= option stands in its place. A dropped line is not
** harmless: the prefix it was meant to gate is then served under the global
** LOGIN default, so `LOC=AUTH=BASIC RES=FACILITY:HTTPD.ADMIN` -- the path
** forgotten -- publishes exactly the subtree it named (issue #164). Unlike the
** allocation failures this shares its reporting with, a typo reaches it.
**
** The remaining tokens are parsed only to classify the line: a binding policy
** makes it a configuration error, AUTH=NONE or no policy stays a warning
** (see policy_binds()). Nothing is registered either way, so any RES= storage
** the parse allocated is released again. */
static void
route_malformed(HTTPD *httpd, const char *kind, char **tok, int ntok,
const char *value)
{
ROUTE_POLICY pol;
int binds;

memset(&pol, 0, sizeof(pol)); /* auth = HTTP_AUTH_DEFAULT */
parse_kv_tail(httpd, tok, 0, ntok, &pol); /* every token is an option */
binds = policy_binds(&pol);
apply_policy(NULL, &pol); /* release RES= strings */

if (binds)
route_policy_lost(httpd, kind, value);
}

/* ====================================================================
** Parse MOD=PROGRAM [pattern] [AUTH=mode] [RES=class:resource]
** If pattern is omitted, derive *.<lowercase program> and use DOCROOT.
Expand Down Expand Up @@ -590,6 +618,20 @@ parse_mod(HTTPD *httpd, const char *value)

ntok = tokenize(tmp, tok, 8);
if (ntok < 1) { /* no program name */
wtof("HTTPD421W MOD= requires a program name "
"(e.g. MOD=MVSMF /zosmf/* AUTH=BASIC)");
free(tmp);
return;
}

/* An AUTH=/RES= option where the program name belongs means the name was
omitted. Without this the option would be folded into an 8-char module
name ("AUTH=BAS") and registered against a derived "*.auth=bas" pattern
-- a route that can never load, built out of a typo. */
if (is_route_kv(tok[0])) {
wtof("HTTPD421W MOD= requires a program name "
"(e.g. MOD=MVSMF /zosmf/* AUTH=BASIC)");
route_malformed(httpd, "MOD", tok, ntok, value);
free(tmp);
return;
}
Expand Down Expand Up @@ -674,6 +716,8 @@ parse_loc(HTTPD *httpd, const char *value)
ntok = tokenize(tmp, tok, 8);
if (ntok < 1 || is_route_kv(tok[0])) { /* first token must be a path */
wtof("HTTPD415W LOC= requires a path (e.g. LOC /admin/* AUTH=BASIC)");
if (ntok >= 1)
route_malformed(httpd, "LOC", tok, ntok, value);
free(tmp);
return;
}
Expand Down