Skip to content

Decode the whole route block in HTTPDSRV, and call it MOD - #153

Merged
mgrossmann merged 2 commits into
mainfrom
issue-146-dsrv-route-table
Aug 8, 2026
Merged

Decode the whole route block in HTTPDSRV, and call it MOD#153
mgrossmann merged 2 commits into
mainfrom
issue-146-dsrv-route-table

Conversation

@mgrossmann

Copy link
Copy Markdown
Contributor

Fixes #146

What changed

The route field table (/.dsrv?target=MOD). All ten HTTPCGI fields are named now, not just the first six. auth and resattr are spelled out rather than printed as bare numbers, because neither value reads the way it looks:

  • AUTH=DEFAULT (0) is not "no authentication" — it means the route carried no AUTH= keyword and inherits the global LOGIN policy.
  • resattr 0 is not "no access" — it is the unset value racf_auth() reads as READ.

login keeps its row (it is still in the block and still in the hex) but is labelled as the legacy field that no longer decides.

One correction to the issue's own analysis, worth recording. The issue reads the 01 at +0014 as the gate that makes /zosmf/info answer 401. HTTP_AUTH_NONE is 1 (httpd.h:257), and auth_gate() sets need_authn = 0 for that mode with no resclass, so that route is public as far as httpd is concerned — its 401 comes from mvsMF's own auth track. The defect and the fix are unchanged; if anything this is the sharper example, since a bare 01 in an unlabelled dump misled the person who filed the issue.

Vocabulary. ?target=MOD, headings "Route Array" / "Route #n (MOD|LOC)", row labels route->, statics renamed to display_route / display_route_row. No CGI alias, per the maintainer's comment on the issue.

One behavior change worth reviewing: target matches as a prefix (http_cmpn(target, …, strlen(target))), so ?target=M has always resolved to MGR. Putting the MOD test first would have silently stolen it. The MOD test therefore sits after the MGR test and ?target=M still means MGR.

HTTPD block table audit (the issue's "not affected" section asked for this in the same pass). The struct is 320 bytes; the table stopped at +009C. Added: httpc, docroot, codepage, listen_queue, the keep-alive and session-timeout settings, smf_level/smf_type, ufs_enabled/dbg_enabled, bind_tries/bind_sleep, credkey/credarr, and every reserved slot, so the table can be walked against the hex without a gap. credkey is a plain pointer and deliberately not a /.dm?m= link — that would be a one-click dump of the blowfish key. dbg and socket_thread used to vanish from the table when NULL; they are always present now, linked only when there is something to link to.

Docs (CLAUDE.md). The "Known display bug" paragraph citing this issue is removed, replaced by a note on reading auth vs login. Target table says MOD. Struct sizes corrected: HTTPD 288/0x120 → 320/0x140, HTTPCGI 20 → 32 bytes.

Verification

make is clean under -Wall -Werror, all six modules link. That is compile-only — no assertion about the rendered output, and CI will not check it either.

Manual check on the live system after deploy:

  1. GET /.dsrv → read httpd->httpcgi at +44, follow the link.
  2. GET /.dsrv?target=MOD&m=<addr> → each route shows route->auth decoded, plus resattr / resclass / resname, and route->login marked legacy.
  3. GET /.dsrv?target=M → still MGR, not the route array.
  4. GET /.dsrv → walk the HTTPD table against the hex dump above it; every offset from +00 to +13C should have a row.

(Write curl's -u flag inline, not via a shell variable — zsh does not word-split it and every request 401s.)

Left out of scope

/.dsrv's HTTPCGI field table stopped at +0010, so the four fields the
per-route auth policy added in #98 -- auth, resattr, resclass, resname --
were in the hex dump but never named. The row it did show, login, is the
legacy byte, which leaves the table unable to answer the one question it
would be opened for: /zosmf/info reads "Login Required 0" and answers
401, and nothing in the table says what decides that.

How thoroughly unreadable the block was without labels is visible in the
issue itself: the dump there has 01 at +0014, which is HTTP_AUTH_NONE --
that route is public as far as httpd's gate is concerned, and its 401
comes from mvsMF's own auth, not from here. The byte was read as "auth
required" because a bare 01 in a hex dump does not say otherwise.

All ten fields are named now. auth and resattr are spelled out rather
than printed as bare numbers, because neither value means what it looks
like: AUTH=DEFAULT is not "no authentication", it means the route
carried no AUTH= keyword and inherits the global LOGIN policy, and
resattr 0 is not "no access", it is the unset value racf_auth() reads as
READ. login keeps its row -- it is still in the block and still in the
hex -- but is labelled as the legacy field that no longer decides.

The vocabulary follows. The Parmlib keywords have been MOD= and LOC=
since the Parmlib configuration went in, so the target is ?target=MOD,
the headings read "Route Array" and "Route #n (MOD|LOC)", and the row
labels are route->. No CGI alias: the maintainer confirmed there are no
consumers of the old spelling. One ordering detail: target matches as a
prefix, so the MOD test sits after the MGR test and ?target=M keeps
resolving to MGR as it always has.

The HTTPD block table had the same drift, so it is audited in the same
pass. The struct is 320 bytes and the table stopped at +009C: docroot,
codepage, the keep-alive and session-timeout settings, the SMF level and
type, the UFS/debug enable flags, the bind retry counts, the client
array and the credential handles were all dumped and none was named.
The reserved slots left by the 4.0.0 removals are listed too, so the
table can be walked against the hex without a gap. credkey is a plain
pointer on purpose and not a /.dm link -- that link would be a one-click
dump of the blowfish key.

Two rows used to disappear when their field was NULL (dbg,
socket_thread), which is the same defect in miniature: an offset that
vanishes from a table claiming to explain the block. They are always
present now, linked only when there is something to link to.

Docs: CLAUDE.md's display-module section loses the known-bug paragraph
this fixes and gains a note on reading auth vs login, the target table
says MOD, and the two struct sizes are corrected -- HTTPD is 320 bytes
(0x140), not 288, and HTTPCGI is 32, not 20.

Fixes #146
@mgrossmann

Copy link
Copy Markdown
Contributor Author

Live verification — deployed to mvsdev, STC restarted

HTTPD block (/.dsrv)

Table now runs +0000+013C with no gap; counted against the hex dump above it, every offset has a row.

+000C  httpd->httpc                  HTTP Client Array (0)           00000000
+002F  httpd->unused                 (reserved)                      00
+0067  httpd->smf_level              SMF Recording Level             0 NONE
+00A0  httpd->docroot                UFS Document Root Prefix        "/wwwroot"
+0120  httpd->listen_queue           Listen Backlog                  5
+0121  httpd->unused_121             (reserved, alignment padding)   00 00 00
+0134  httpd->cfg_keepalive_timeout  Keep-Alive Idle Timeout         5
+0135  httpd->cfg_keepalive_max      Max Requests per Connection     100
+0136  httpd->cfg_session_timeout    Credential Idle TTL             30
+0138  httpd->credkey                Credential Key Handle           0011CF98
+013C  httpd->credarr                Credential Array Handle         000CEBC0

The httpd->httpcgi row links to ?target=MOD&m=000CE894.

Route table (?target=MOD&m=000CE894)

Eight routes, all ten fields named:

Route #0 (MOD)  "/zosmf/info"                   MVSMF     auth 1 NONE (public, never challenged)
Route #1 (MOD)  "/zosmf/services/authenticate"  MVSMF     auth 1 NONE (public, never challenged)
Route #2 (MOD)  "/zosmf/*"                      MVSMF     auth 0 DEFAULT (inherits the global LOGIN policy)
Route #3 (MOD)  "/.dsrv"                        HTTPDSRV  auth 0 DEFAULT
Route #4 (MOD)  "/.dm"                          HTTPDM    auth 0 DEFAULT
Route #5 (MOD)  "/.dmtt"                        HTTPDMTT  auth 0 DEFAULT
Route #6 (MOD)  "/dsl/*"                        HTTPDSL   auth 0 DEFAULT
Route #7 (MOD)  "/jes/*"                        HTTPJES2  auth 0 DEFAULT

The /zosmf/info question, settled empirically

Route #0 reports auth = 1 NONE (public, never challenged), and an unauthenticated request still answers 401:

$ curl -si http://…:8080/zosmf/info
HTTP/1.1 401 Unauthorized
Server: HTTPD Server

So httpd does not gate that route — the 401 is mvsMF's own auth track. This is what the PR description claimed from reading the constant; it now has a measurement behind it. The old table said Login Required 0 and left the 01 at +14 unlabelled in the hex, which is how it came to be read as "auth required" in the first place.

Prefix resolution

?target=M    -> HTTPD Thread Manager 000CE968
?target=MG   -> HTTPD Thread Manager 000CE968
?target=MGR  -> HTTPD Thread Manager 000CE968
?target=MO   -> route array (asks for &m=)
?target=MOD  -> route array (asks for &m=)
?target=CGI  -> HTTPDSRV Help

?target=M still means MGR, and the retired spelling falls through to the help page as intended.

Not covered

No LOC= route is configured on this system, so the Route #n (LOC) heading and the pgm "(none)" rendering are unexercised — the code path is a ternary on route->pgm, but I have not seen it run.

The row added a commit ago printed httpd->codepage verbatim, and on a
system with no CODEPAGE keyword that is "" -- which reads as "none set"
when it means CP037. set_defaults() leaves the field empty
(httpprm.c:137), http_prm passes that through as NULL
(httpprm.c:62), and http_xlate_init() maps NULL to the CP037 tables
(httpxlat.c:395).

That is the same shape as the login row this branch is fixing: a raw
value that does not say what is in force. An empty codepage now renders
as CP037 with the reason, the same way resattr 0 renders as READ.

Relates to #146
@mgrossmann

mgrossmann commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

Addendum: the httpd->codepage row was changed after the verification run above (27f4fe6) — an empty codepage now renders as "" CP037 (default, no CODEPAGE keyword) instead of a bare "", since empty means CP037 rather than "none". That one row is not covered by the measurements in the previous comment; it needs a redeploy to confirm.

Follow-up filed for the second surface: #155 (http_debug() / ?debug=cgi still prints Login=%u and omits the auth fields).

@mgrossmann
mgrossmann merged commit 8a4df50 into main Aug 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HTTPDSRV shows a stale HTTPCGI field map (the per-route auth fields are missing) and still calls modules CGIs

1 participant