diff --git a/CLAUDE.md b/CLAUDE.md index 9b1f35c..4a199a7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -115,13 +115,13 @@ httpd.c (main / initialize) ### Data Structures -**HTTPD (288 bytes, 0x120):** Server-wide singleton. Listener socket, worker pool manager, CGI table, config values, UFS handle, Lua state, FTPD handle, MQTT telemetry handle, stats arrays. (Will shrink as confirmed removals are implemented.) +**HTTPD (320 bytes, 0x140):** Server-wide singleton. Listener socket, worker pool manager, route table, config values, UFS handle, docroot, codepage, keep-alive settings, credential key/array, stats counters. Slots freed by the 4.0.0 removals are kept as `unused_nn` placeholders so no offset moves. **HTTPC (4,096 bytes):** Per-client session. Allocated on accept(), freed on close. Fixed layout with 4,008-byte inline buffer (CBUFSIZE). Contains state machine position, socket, environment variables, file handles, credential. **HTTPX (~270 bytes):** Function vector table. CGI modules call all server functions through this vector — they never link directly to HTTPD code. **Never change existing offsets** — only append new function pointers at the end. -**HTTPCGI (20 bytes):** CGI path-to-program mapping. URL pattern → load module name. +**HTTPCGI (32 bytes):** One route. URL pattern → load module name (`MOD=`) or NULL for a program-less static prefix (`LOC=`), plus the per-route auth policy (`auth`, `resattr`, `resclass`, `resname`). ### Request Processing Pipeline @@ -287,7 +287,7 @@ each one is shown as hex plus a named field table: | `HTTPD` | the server singleton (320 bytes, `0x140`) | no | | `MGR` | `CTHDMGR`, the worker pool manager | no | | `FS` | `UFSSYS` handle (8 bytes with the libufs stub) | no | -| `CGI` | one `HTTPCGI` route, or the route array | **yes** | +| `MOD` | the route array — every `MOD=` / `LOC=` entry | **yes** | | `TASK` | a `CTHDTASK` | **yes** | | `FILE` | a `FILE` handle | **yes** | @@ -316,8 +316,8 @@ Note for anyone reading `jesst.c`: its output goes through `printf`, and in a module `stdout` reaches the HTTP client — so those `printf`s *are* the response body, not log output. -**Known display bug:** the `HTTPCGI` field table in `httpdsrv.c` predates the -per-route auth policy. It shows `login` (legacy) and omits `auth` / `resattr` / -`resclass` / `resname`, so on an authorization question it can say the opposite -of the truth — `/zosmf/info` reports `login 0` while answering 401. Read the hex -at `+14`. Tracked as issue #146. +**Reading the route table:** `?target=MOD` decodes the whole 32-byte `HTTPCGI`, +so `auth` (`+14`) is named and spelled out — that is the field the request is +gated on, not `login` (`+09`), which is the legacy byte and is labelled as such. +`AUTH=DEFAULT` means the route carried no `AUTH=` keyword and falls back to the +global `LOGIN` policy, which is not the same as "no authentication". diff --git a/src/httpdsrv.c b/src/httpdsrv.c index 5fb019c..a93af7a 100644 --- a/src/httpdsrv.c +++ b/src/httpdsrv.c @@ -9,7 +9,7 @@ static int getself(char *jobname, char *jobid); #endif static int display_httpd(HTTPD *httpd, HTTPC *httpc); -static int display_cgi(HTTPD *httpd, HTTPC *httpc); +static int display_route(HTTPD *httpd, HTTPC *httpc); static int display_file(HTTPD *httpd, HTTPC *httpc); static int display_fs(HTTPD *httpd, HTTPC *httpc); static int display_mgr(HTTPD *httpd, HTTPC *httpc); @@ -19,7 +19,7 @@ static int display_workers(HTTPD *httpd, HTTPC *httpc); static int display_ufs(HTTPD *httpd, HTTPC *httpc, UFS *ufs); static int display_ufssys(HTTPD *httpd, HTTPC *httpc, UFSSYS *sys); -static int display_cgi_row(HTTPD *httpd, HTTPC *httpc, HTTPCGI *cgi, unsigned n); +static int display_route_row(HTTPD *httpd, HTTPC *httpc, HTTPCGI *route, unsigned n); static int display_worker_row(HTTPD *httpd, HTTPC *httpc, CTHDWORK *worker, unsigned n); static int display_queue_data(HTTPD *httpd, HTTPC *httpc, CTHDQUE *q); #if 0 /* ufs370 internal types -- not available with libufs stub */ @@ -58,11 +58,6 @@ int main(int argc, char **argv) if (!target) target = "HTTPD"; len = strlen(target); - if (http_cmpn(target, "CGI", len)==0) { - display_cgi(httpd, httpc); - goto quit; - } - if (http_cmpn(target, "FILE", len)==0) { display_file(httpd, httpc); goto quit; @@ -78,6 +73,15 @@ int main(int argc, char **argv) goto quit; } + /* The route array (MOD= programs and LOC= static prefixes). Was + "target=CGI" before the Parmlib keywords replaced that vocabulary. + Tested after MGR on purpose: target matches as a prefix, so a bare + "?target=M" has always resolved to MGR and still does. */ + if (http_cmpn(target, "MOD", len)==0) { + display_route(httpd, httpc); + goto quit; + } + if (http_cmpn(target, "HTTPD", len)==0) { display_httpd(httpd, httpc); goto quit; @@ -222,6 +226,13 @@ display_httpd(HTTPD *httpd, HTTPC *httpc) "%p\n", 8, httpx, sizeof(HTTPX), httpx); + http_printf(httpc, + "+%04X" + "httpd->httpc" + "HTTP Client Array (%u)" + "%p\n", + O(httpc), array_count(&httpd->httpc), httpd->httpc); + u = (UCHAR*)&httpd->addr; http_printf(httpc, "+%04X" @@ -245,15 +256,29 @@ display_httpd(HTTPD *httpd, HTTPC *httpc) O(listen), httpd->listen); /* stats file handle removed in 4.0.0 — SMF recording */ + http_printf(httpc, + "+%04X" + "httpd->unused_1c" + "(reserved)" + "%p\n", + O(unused_1c), httpd->unused_1c); if (httpd->dbg) { - http_printf(httpc, + http_printf(httpc, "+%04X" "httpd->dbg" "HTTP Debug File Handle" - "%p\n", + "%p\n", O(dbg), httpd->dbg, httpd->dbg); } + else { + http_printf(httpc, + "+%04X" + "httpd->dbg" + "HTTP Debug File Handle" + "%p\n", + O(dbg), httpd->dbg); + } http_printf(httpc, "+%04X" @@ -327,14 +352,29 @@ display_httpd(HTTPD *httpd, HTTPC *httpc) } http_printf(httpc, "\n"); + http_printf(httpc, + "+%04X" + "httpd->unused" + "(reserved)" + "%02X\n", + O(unused), httpd->unused); + if (httpd->socket_thread) { - http_printf(httpc, + http_printf(httpc, "+%04X" "httpd->socket_thread" "Socket Thread Handle" - "%p\n", + "%p\n", O(socket_thread), httpd->socket_thread, httpd->socket_thread); } + else { + http_printf(httpc, + "+%04X" + "httpd->socket_thread" + "Socket Thread Handle" + "%p\n", + O(socket_thread), httpd->socket_thread); + } http_printf(httpc, "+%04X" @@ -352,8 +392,8 @@ display_httpd(HTTPD *httpd, HTTPC *httpc) http_printf(httpc, "+%04X" - "httpd->httpcgi" - "Common Gateway Interface Array" + "httpd->httpcgi" + "Route Array (MOD= programs, LOC= static prefixes)" "%p\n", O(httpcgi), httpd->httpcgi, httpd->httpcgi); @@ -364,6 +404,13 @@ display_httpd(HTTPD *httpd, HTTPC *httpc) "%-24.24s\n", O(uptime), ctime64(&httpd->uptime)); + http_printf(httpc, + "+%04X" + "httpd->unused_50" + "(reserved)" + "%p\n", + O(unused_50), httpd->unused_50); + http_printf(httpc, "+%04X" "httpd->ufssys" @@ -412,16 +459,69 @@ display_httpd(HTTPD *httpd, HTTPC *httpc) "Config Client Timeout Seconds" "%u\n", O(cfg_client_timeout), httpd->cfg_client_timeout); - + /* cfg_st_*_max removed in 4.0.0 — replaced by SMF + counters */ - - http_printf(httpc, + + http_printf(httpc, + "+%04X" + "httpd->smf_level" + "SMF Recording Level" + "%u %s\n", + O(smf_level), httpd->smf_level, + httpd->smf_level == SMF_LEVEL_NONE ? "NONE" : + httpd->smf_level == SMF_LEVEL_ERROR ? "ERROR" : + httpd->smf_level == SMF_LEVEL_AUTH ? "AUTH" : + httpd->smf_level == SMF_LEVEL_ALL ? "ALL" : "(unknown)"); + + http_printf(httpc, + "+%04X" + "httpd->smf_type" + "SMF Record Type" + "%u\n", + O(smf_type), httpd->smf_type); + + http_printf(httpc, + "+%04X" + "httpd->unused_69" + "(reserved)" + "%02X %02X\n", + O(unused_69), httpd->unused_69[0], httpd->unused_69[1]); + + http_printf(httpc, "+%04X" "httpd->cfg_cgictx" "Config CGI Context Pointers" - "%u\n", + "%u\n", O(cfg_cgictx), httpd->cfg_cgictx); - + + http_printf(httpc, + "+%04X" + "httpd->ufs_enabled" + "UFS Filesystem Enabled" + "%u\n", + O(ufs_enabled), httpd->ufs_enabled); + + http_printf(httpc, + "+%04X" + "httpd->dbg_enabled" + "Debug Output Enabled" + "%u\n", + O(dbg_enabled), httpd->dbg_enabled); + + http_printf(httpc, + "+%04X" + "httpd->bind_tries" + "Socket Bind Retry Count" + "%u\n", + O(bind_tries), httpd->bind_tries); + + http_printf(httpc, + "+%04X" + "httpd->bind_sleep" + "Socket Bind Retry Delay (seconds)" + "%u\n", + O(bind_sleep), httpd->bind_sleep); + http_printf(httpc, "+%04X" "httpd->total_requests" @@ -449,7 +549,14 @@ display_httpd(HTTPD *httpd, HTTPC *httpc) "Active Connections" "%u\n", O(active_connections), httpd->active_connections); - + + http_printf(httpc, + "+%04X" + "httpd->unused_80" + "(reserved)" + "%p\n", + O(unused_80), httpd->unused_80); + http_printf(httpc, "+%04X" "httpd->unused_84" @@ -477,7 +584,14 @@ display_httpd(HTTPD *httpd, HTTPC *httpc) "Unix \"like\" File System Handle" "%p\n", O(ufs), httpd->ufs); - + + http_printf(httpc, + "+%04X" + "httpd->unused_94" + "(reserved)" + "%p\n", + O(unused_94), httpd->unused_94); + http_printf(httpc, "+%04X" "httpd->self" @@ -492,6 +606,84 @@ display_httpd(HTTPD *httpd, HTTPC *httpc) "%p\n", O(cgictx), httpd->cgictx, (HTTPD_CGICTX_MAX+1)*4, httpd->cgictx); + http_printf(httpc, + "+%04X" + "httpd->docroot" + "UFS Document Root Prefix" + "\"%.128s\"\n", + O(docroot), httpd->docroot); + + http_printf(httpc, + "+%04X" + "httpd->listen_queue" + "Listen Backlog" + "%u\n", + O(listen_queue), httpd->listen_queue); + + http_printf(httpc, + "+%04X" + "httpd->unused_121" + "(reserved, alignment padding)" + "%02X %02X %02X\n", + O(unused_121), httpd->unused_121[0], httpd->unused_121[1], + httpd->unused_121[2]); + + /* An empty codepage is the default, not "none": set_defaults() leaves it + ** empty and http_xlate_init() reads that as CP037. Say so -- a bare "" + ** in the table would not tell a reader which tables are actually loaded. */ + if (httpd->codepage[0]) { + http_printf(httpc, + "+%04X" + "httpd->codepage" + "Codepage Name (CODEPAGE=)" + "\"%.16s\"\n", + O(codepage), httpd->codepage); + } + else { + http_printf(httpc, + "+%04X" + "httpd->codepage" + "Codepage Name (CODEPAGE=)" + "\"\" CP037 (default, no CODEPAGE keyword)\n", + O(codepage)); + } + + http_printf(httpc, + "+%04X" + "httpd->cfg_keepalive_timeout" + "Keep-Alive Idle Timeout (seconds)" + "%u\n", + O(cfg_keepalive_timeout), httpd->cfg_keepalive_timeout); + + http_printf(httpc, + "+%04X" + "httpd->cfg_keepalive_max" + "Max Requests per Connection" + "%u\n", + O(cfg_keepalive_max), httpd->cfg_keepalive_max); + + http_printf(httpc, + "+%04X" + "httpd->cfg_session_timeout" + "Credential Idle TTL (minutes, 0 = off)" + "%u\n", + O(cfg_session_timeout), httpd->cfg_session_timeout); + + /* credkey is the blowfish key: report the pointer, never a storage link */ + http_printf(httpc, + "+%04X" + "httpd->credkey" + "Credential Key Handle" + "%p\n", + O(credkey), httpd->credkey); + + http_printf(httpc, + "+%04X" + "httpd->credarr" + "Credential Array Handle" + "%p\n", + O(credarr), httpd->credarr); + http_printf(httpc, "\n"); send_last(httpd, httpc); @@ -1403,13 +1595,13 @@ struct ufs { #ifdef O #undef O #endif -#define O(a) ((unsigned)&(cgi->a) - (unsigned)cgi) +#define O(a) ((unsigned)&(route->a) - (unsigned)route) static int -display_cgi(HTTPD *httpd, HTTPC *httpc) +display_route(HTTPD *httpd, HTTPC *httpc) { int rc = 0; - HTTPCGI *cgi = NULL; + HTTPCGI *route = NULL; HTTPCGI **array = NULL; char *memory = NULL; unsigned n, count; @@ -1428,20 +1620,20 @@ display_cgi(HTTPD *httpd, HTTPC *httpc) array = (HTTPCGI**) strtoul(memory, NULL, 16); count = array_count(&array); - http_printf(httpc, "

CGI Array %p

", array); + http_printf(httpc, "

Route Array %p

", array); #if 0 - http_printf(httpc, + http_printf(httpc, "\n", - "CGI%20Array", array, count*sizeof(HTTPCGI*)); + "Route%20Array", array, count*sizeof(HTTPCGI*)); #endif - display_memory(httpd, httpc, "CGI Array", array, count*sizeof(HTTPCGI*), 16); - + display_memory(httpd, httpc, "Route Array", array, count*sizeof(HTTPCGI*), 16); + for(n=0; n < count; n++) { - cgi = array[n]; - - if (!cgi) continue; - display_cgi_row(httpd, httpc, cgi, n); + route = array[n]; + + if (!route) continue; + display_route_row(httpd, httpc, route, n); } done: @@ -1451,16 +1643,46 @@ display_cgi(HTTPD *httpd, HTTPC *httpc) return 0; } -static int -display_cgi_row(HTTPD *httpd, HTTPC *httpc, HTTPCGI *cgi, unsigned n) +/* auth_mode_text() - decode HTTPCGI.auth (HTTP_AUTH_*). DEFAULT is the value +** a route carries when it had no AUTH= keyword, so it is not "no auth" -- the +** request still runs through the legacy global LOGIN policy. */ +static const char * +auth_mode_text(UCHAR auth) +{ + switch (auth) { + case HTTP_AUTH_DEFAULT: return "DEFAULT (inherits the global LOGIN policy)"; + case HTTP_AUTH_NONE: return "NONE (public, never challenged)"; + case HTTP_AUTH_FORM: return "FORM (HTML login form)"; + case HTTP_AUTH_BASIC: return "BASIC (401 WWW-Authenticate)"; + default: return "(unknown)"; + } +} + +/* racf_attr_text() - decode HTTPCGI.resattr. 0 is the common value: httpprm +** only sets resattr when RES= is present, and racf_auth() assumes READ for 0. */ +static const char * +racf_attr_text(UCHAR attr) +{ + switch (attr) { + case 0: return "READ (assumed, RESATTR not set)"; + case RACF_ATTR_READ: return "READ"; + case RACF_ATTR_UPDATE: return "UPDATE"; + case RACF_ATTR_CONTROL: return "CONTROL"; + case RACF_ATTR_ALTER: return "ALTER"; + default: return "(unknown)"; + } +} + +static int +display_route_row(HTTPD *httpd, HTTPC *httpc, HTTPCGI *route, unsigned n) { char title[40]; - - sprintf(title, "CGI #%u", n); + + sprintf(title, "Route #%u (%s)", n, route->pgm ? "MOD" : "LOC"); http_printf(httpc, "

%s

\n", title); - display_memory(httpd, httpc, title, cgi, sizeof(HTTPCGI), 16); + display_memory(httpd, httpc, title, route, sizeof(HTTPCGI), 16); http_printf(httpc, "\n"); @@ -1468,42 +1690,66 @@ display_cgi_row(HTTPD *httpd, HTTPC *httpc, HTTPCGI *cgi, unsigned n) "" "" "\n"); - + http_printf(httpc, "" - "" + "" "" - "\n", - O(eye), cgi->eye); + "\n", + O(eye), route->eye); http_printf(httpc, "" - "" + "" "" - "\n", - O(wild), cgi->wild); + "\n", + O(wild), route->wild); http_printf(httpc, "" - "" - "" - "\n", - O(login), cgi->login); + "" + "" + "\n", + O(login), route->login); http_printf(httpc, "" - "" + "" "" - "\n", - O(len), cgi->len); + "\n", + O(len), route->len); http_printf(httpc, "" - "" + "" "" - "\n", - O(path), cgi->path); + "\n", + O(path), route->path ? route->path : "(none)"); + + http_printf(httpc, "" + "" + "" + "\n", + O(pgm), route->pgm ? route->pgm : "(none)"); + + http_printf(httpc, "" + "" + "" + "\n", + O(auth), route->auth, auth_mode_text(route->auth)); + + http_printf(httpc, "" + "" + "" + "\n", + O(resattr), route->resattr, racf_attr_text(route->resattr)); http_printf(httpc, "" - "" - "" + "" + "" "\n", - O(pgm), cgi->pgm ? cgi->pgm : "(none)"); + O(resclass), route->resclass ? route->resclass : "(none)"); + + http_printf(httpc, "" + "" + "" + "\n", + O(resname), route->resname ? route->resname : "(none)"); #if 0 http_printf(httpc, "" @@ -2355,7 +2601,7 @@ display_help(HTTPD *httpd, HTTPC *httpc) http_printf(httpc, "

HTTPDSRV Help

\n"); http_printf(httpc, "

Usage: http:/%s%s?target=name[&m=nnnnnnnn]

\n", host, path); - http_printf(httpc, "

This CGI program uses the QUERY variable " + http_printf(httpc, "

This module uses the QUERY variable " "\"target\" value to control which HTTPD storage area to display.

\n"); http_printf(httpc, "

\n"); @@ -2364,9 +2610,6 @@ display_help(HTTPD *httpd, HTTPC *httpc) http_printf(httpc, "
Display the HTTPD storage areas. " "This is the default if ?target is omitted.

\n"); - http_printf(httpc, "
?target=CGI&m=nnnnnnnn
\n"); - http_printf(httpc, "
Display the Common Gateway Interface array at the memory address.

\n"); - http_printf(httpc, "
?target=FILE&m=nnnnnnnn
\n"); http_printf(httpc, "
Display the File Handle at the memory address.

\n"); @@ -2379,6 +2622,10 @@ display_help(HTTPD *httpd, HTTPC *httpc) http_printf(httpc, "
?target=MGR&m=nnnnnnnn
\n"); http_printf(httpc, "
Display the Thread Manager Handle at the memory address.

\n"); + http_printf(httpc, "
?target=MOD&m=nnnnnnnn
\n"); + http_printf(httpc, "
Display the route array (MOD= programs and LOC= static " + "prefixes) at the memory address. Was ?target=CGI.

\n"); + http_printf(httpc, "
?target=TASK&m=nnnnnnnn
\n"); http_printf(httpc, "
Display the Thread Task Handle at the memory address.

\n");
Data NameDescriptionContents
+%04Xcgi->eyeroute->eyeEye Catcher\"%s\"
\"%s\"
+%04Xcgi->wildroute->wildIs Wildcard%u
%u
+%04Xcgi->loginLogin Required%u
route->loginLogin Required (legacy -- route->auth decides)%u
+%04Xcgi->lenroute->lenPath Length%u
%u
+%04Xcgi->pathroute->pathPath Name\"%s\"
\"%s\"
+%04Xroute->pgmProgram Name (NULL = LOC static route)\"%s\"
+%04Xroute->authAuth Mode (AUTH=)%u %s
+%04Xroute->resattrRACF Access Attribute%02X %s
+%04Xcgi->pgmProgram Nameroute->resclassRACF Class (RES=, NULL = no resource gate)\"%s\"
+%04Xroute->resnameRACF Resource Name (RES=)\"%s\"
----------