From ca2b42d318fc7b055c1fa52255326c0a70495e5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Gro=C3=9Fmann?= Date: Wed, 12 Aug 2026 09:42:02 +0200 Subject: [PATCH 1/2] Upper case the console messages and stamp the banner (#98) The operator console output was mixed case, named a version hardcoded in ftpd.h, and reused message IDs for unrelated events. All three are now aligned with UFSD. Build stamp. Bump the mbt submodule to mbt#64 so .mbt/buildstamp.h is generated, and use it for the banner: FTPD000I FTPD 1.0.0-DEV (CF14987) STARTING FTPD005I LIBC370 1.0.2-DEV (5C0DEEB) FTPD006W BUILT FROM A MODIFIED WORKING TREE ... FTPD001I FTPD 1.0.0-DEV READY A deploy/relink mismatch -- sysroot says X, the STC runs Y -- can no longer hide, and a build carrying uncommitted tracked changes says so instead of passing itself off as the commit it was branched from. buildstamp.h is included per translation unit (ftpd.c, ftpd#con.c, ftpd#cmd.c) and deliberately not from ftpd.h: -MMD would otherwise make a new commit rebuild every object. FTPD_VERSION and FTPD_VERSION_STR are gone; FTPD_PRODUCT holds the name the STAT reply still needs. Case. Every ftpd_log_wto() message is upper case; values -- data set names, VOLSERs, the banner, every %s/%d substitution -- keep their original case. ftpd_upcase() (ftpd#log.c) handles the three strings that arrive lower case at runtime: MBT_VERSION, MBT_COMMIT and libc370_version(). It uses the libc370 toupper(), so the mapping is EBCDIC-correct. ftpd_session_reply() is untouched: those are FTP protocol replies read by clients, not console output. So is ftpd_log(), which goes to SYSOUT. Message IDs. Six were ambiguous or in the wrong slot: FTPD097I shutting down -> FTPD098I FTPD098I shutdown complete -> FTPD099I FTPD099E COM area not available -> FTPD090E (was sharing 099E) FTPD099E initialization failed -> FTPD091E (was sharing 099E) FTPD090E unknown command / TRACE -> FTPD021E (UFSD's 020I/021E pair) FTPD005W SSLPROXY (x2) -> FTPD007W (005 is libc370 now) The two APF success messages are gone -- UFSD reports only the failure, and one of them was a second FTPD000I competing with the banner. The warn-and-continue behaviour on APF failure (FTPD003W) is unchanged. /F FTPD,VERSION now repeats the banner identity instead of a static string, which is what an operator asking it after a deploy wants. --- doc/FTPD_RAKF_SETUP.md | 8 ++-- include/ftpd#log.h | 14 ++++++ include/ftpd.h | 13 +++-- mbt | 2 +- src/ftpd#cfg.c | 8 ++-- src/ftpd#cmd.c | 7 ++- src/ftpd#con.c | 41 ++++++++++++---- src/ftpd#log.c | 23 +++++++++ src/ftpd#mvs.c | 2 +- src/ftpd#ses.c | 16 +++---- src/ftpd#sit.c | 4 +- src/ftpd.c | 105 +++++++++++++++++++++++++---------------- 12 files changed, 168 insertions(+), 75 deletions(-) diff --git a/doc/FTPD_RAKF_SETUP.md b/doc/FTPD_RAKF_SETUP.md index 3de90e5..1ad56b8 100644 --- a/doc/FTPD_RAKF_SETUP.md +++ b/doc/FTPD_RAKF_SETUP.md @@ -178,14 +178,14 @@ default STC identity (PROD/PRDGROUP) to `FTPD/USER`. This requires: On success, the console shows: ``` -FTPD004I STC identity set to FTPD/USER via RACINIT +FTPD004I STC IDENTITY SET TO FTPD/USER VIA RACINIT ``` If the RACINIT fails (e.g., user not defined), the STC continues under the default identity with a warning: ``` -FTPD004W RACINIT ENVIR=CREATE failed RC=nn +FTPD004W RACINIT ENVIR=CREATE FAILED RC=nn ``` ### 5.1 SECURITY INVARIANT — `FTPD/USER` must be least-privilege @@ -236,7 +236,7 @@ The `USER` group is shared. 4. (Optional) Define DATASET profiles for access control 5. Reload RAKF: `/F RAKF,RELOAD` 6. Start FTPD: `/S FTPD` -7. Verify: `FTPD004I STC identity set to FTPD/USER via RACINIT` +7. Verify: `FTPD004I STC IDENTITY SET TO FTPD/USER VIA RACINIT` 8. Test login: `ftp 2121` with a valid RAKF user --- @@ -246,7 +246,7 @@ The `USER` group is shared. | Symptom | Cause | Fix | |---------|-------|-----| | `RAKF0010I STC FTPD STARTED USING DEFAULT STC ACCOUNT` | FTPD user not defined in RAKF | Add FTPD user, `/F RAKF,RELOAD` | -| `FTPD004W RACINIT ENVIR=CREATE failed` | FTPD user not defined or APF issue | Check RAKF users, verify APF auth | +| `FTPD004W RACINIT ENVIR=CREATE FAILED` | FTPD user not defined or APF issue | Check RAKF users, verify APF auth | | `530 Login incorrect` | Wrong password or user not in RAKF | Verify user exists, password is correct | | `530 Not authorized for FTP access` | FTPAUTH profile denies access | Grant READ to user's group | | `550 Access denied to ` | DATASET profile denies access | Check DATASET profiles in RAKF | diff --git a/include/ftpd#log.h b/include/ftpd#log.h index 374fdad..d6189e5 100644 --- a/include/ftpd#log.h +++ b/include/ftpd#log.h @@ -21,6 +21,20 @@ */ void ftpd_log_wto(const char *fmt, ...) asm("FTPLOGW"); +/* +** Copy `src` into `dst` in upper case, NUL-terminated, writing at most +** `n` bytes including the NUL. Returns `dst` so a call can be used +** directly as a ftpd_log_wto() argument. +** +** The console house style is upper case, but the build stamp arrives in +** lower case: MBT_VERSION and MBT_COMMIT carry the project version and a +** hex commit hash, and libc370_version() returns a whole sentence of its +** own ("libc370 v1.0.2-dev (22b4870)"). toupper() is the libc370 one, so +** the mapping is EBCDIC-correct -- do not hand-roll a range test. +*/ +const char *ftpd_upcase(char *dst, unsigned n, const char *src) + asm("FTPUPCAS"); + /* ** Write a log message to STDOUT with timestamp and level. ** This is the general-purpose logging function. diff --git a/include/ftpd.h b/include/ftpd.h index 51d4812..1d1fc31 100644 --- a/include/ftpd.h +++ b/include/ftpd.h @@ -31,9 +31,16 @@ typedef unsigned char UCHAR; -/* --- Version --- */ -#define FTPD_VERSION "1.0.0-dev" -#define FTPD_VERSION_STR "MVS 3.8j FTPD Server " FTPD_VERSION +/* --- Product name --- +** +** The version is NOT here: it comes from MBT_VERSION in the generated +** , so it cannot drift from VERSION the way a second copy +** would. buildstamp.h stays out of this header on purpose -- -MMD makes +** it a prerequisite of every object that includes it, and a new commit +** would then rebuild the whole project instead of the few translation +** units that actually name the version (mbt#59). +*/ +#define FTPD_PRODUCT "MVS 3.8j FTPD Server" /* --- Filesystem / filetype modes --- */ #define FT_SEQ 0 /* sequential dataset mode */ diff --git a/mbt b/mbt index bf0e081..8b8a2f0 160000 --- a/mbt +++ b/mbt @@ -1 +1 @@ -Subproject commit bf0e081d0372489a312ef14de64d49c30ec7de3c +Subproject commit 8b8a2f09d34f65e310d6f677316aa05f2c24e7cd diff --git a/src/ftpd#cfg.c b/src/ftpd#cfg.c index ccec0dd..e0086f0 100644 --- a/src/ftpd#cfg.c +++ b/src/ftpd#cfg.c @@ -322,16 +322,16 @@ ftpdcfg_dump(const ftpd_config_t *cfg) { int i; - ftpd_log_wto("FTPD040I Configuration:"); + ftpd_log_wto("FTPD040I CONFIGURATION:"); ftpd_log_wto("FTPD041I SRVPORT=%d SRVBIND=%s%s", cfg->port, cfg->bind_ip, - cfg->bind_ip_alias ? " (set as SRVIP)" : ""); + cfg->bind_ip_alias ? " (SET AS SRVIP)" : ""); /* PASVADR=ANY is not an address the client could use -- say what it ** resolves to instead, the operator is reading this to find out why a ** client connects where it does. */ ftpd_log_wto("FTPD042I PASVADR=%s PASVPORTS=%d-%d PASVBIND=%s", strcmp(cfg->pasv_addr, "ANY") == 0 - ? "ANY (control connection)" : cfg->pasv_addr, + ? "ANY (CONTROL CONNECTION)" : cfg->pasv_addr, cfg->pasv_lo, cfg->pasv_hi, cfg->pasv_bind); ftpd_log_wto("FTPD043I MAXSESSIONS=%d IDLETIMEOUT=%d", @@ -344,7 +344,7 @@ ftpdcfg_dump(const ftpd_config_t *cfg) cfg->defaults.blksize); ftpd_log_wto("FTPD046I DEFUNIT=%s DEFVOLUME=%s", cfg->defaults.unit, cfg->defaults.volume); - ftpd_log_wto("FTPD047I DASD volumes=%d:", cfg->num_dasd); + ftpd_log_wto("FTPD047I DASD VOLUMES=%d:", cfg->num_dasd); for (i = 0; i < cfg->num_dasd; i++) { ftpd_log_wto("FTPD048I %s,%s", cfg->dasd[i].volser, cfg->dasd[i].unit); diff --git a/src/ftpd#cmd.c b/src/ftpd#cmd.c index 7e15789..836be91 100644 --- a/src/ftpd#cmd.c +++ b/src/ftpd#cmd.c @@ -5,6 +5,10 @@ ** Response strings match z/OS FTP Server behavior ** (see doc/ZOS_FTP_REFERENCE.md). */ +/* Build stamp for the STAT reply -- see the note in ftpd.c on why this is +** included per translation unit and not from ftpd.h. */ +#include + #include "ftpd.h" #include "ftpd#ses.h" #include "ftpd#cmd.h" @@ -162,7 +166,8 @@ cmd_noop(ftpd_session_t *sess) static int cmd_stat(ftpd_session_t *sess) { - ftpd_session_reply(sess, FTP_211, "%s", FTPD_VERSION_STR); + /* Client-facing protocol text: stays mixed case, unlike the console. */ + ftpd_session_reply(sess, FTP_211, "%s", FTPD_PRODUCT " " MBT_VERSION); return 0; } diff --git a/src/ftpd#con.c b/src/ftpd#con.c index d980b1c..eb88738 100644 --- a/src/ftpd#con.c +++ b/src/ftpd#con.c @@ -19,7 +19,12 @@ ** /F FTPD,SHUTDOWN - graceful shutdown ** /P FTPD - stop (graceful shutdown) */ +/* Build stamp for the VERSION command -- see the note in ftpd.c on why +** this is included per translation unit and not from ftpd.h. */ +#include + #include "ftpd.h" +#include "clibver.h" /* Forward declarations for command handlers */ static void cmd_stats(ftpd_server_t *server); @@ -97,7 +102,7 @@ ftpd_process_cib(ftpd_server_t *server, CIB *cib) cmd_shutdown(server); } else { - ftpd_log_wto("FTPD090E Unknown command: %s", arg); + ftpd_log_wto("FTPD021E UNKNOWN COMMAND: %s", arg); } break; @@ -116,7 +121,7 @@ cmd_stats(ftpd_server_t *server) { ftpd_log_wto("FTPD010I STATUS: %s", (server->flags & FTPD_ACTIVE) ? "ACTIVE" : "INACTIVE"); - ftpd_log_wto("FTPD011I SESSIONS: %d active, %ld total", + ftpd_log_wto("FTPD011I SESSIONS: %d ACTIVE, %ld TOTAL", server->num_sessions, server->total_sessions); ftpd_log_wto("FTPD012I BYTES IN: %ld", server->total_bytes_in); ftpd_log_wto("FTPD013I BYTES OUT: %ld", server->total_bytes_out); @@ -130,7 +135,7 @@ cmd_stats(ftpd_server_t *server) static void cmd_sessions(ftpd_server_t *server) { - ftpd_log_wto("FTPD015I Active sessions: %d / %d", + ftpd_log_wto("FTPD015I ACTIVE SESSIONS: %d / %d", server->num_sessions, server->config.max_sessions); } @@ -145,12 +150,28 @@ cmd_config(ftpd_server_t *server) /* ==================================================================== ** VERSION -- display version string +** +** Repeats the startup banner: which FTPD, built from which commit, +** against which C runtime. An operator asking VERSION after a deploy +** wants exactly the identity the banner scrolled off with. ** ================================================================= */ static void cmd_version(ftpd_server_t *server) { + char vers[24]; + char commit[24]; + char stamp[48]; + (void)server; - ftpd_log_wto("FTPD016I %s", FTPD_VERSION_STR); + + ftpd_log_wto("FTPD016I FTPD %s (%s)", + ftpd_upcase(vers, sizeof(vers), MBT_VERSION), + ftpd_upcase(commit, sizeof(commit), MBT_COMMIT)); + ftpd_log_wto("FTPD016I %s", + ftpd_upcase(stamp, sizeof(stamp), libc370_version())); +#if MBT_COMMIT_DIRTY + ftpd_log_wto("FTPD006W BUILT FROM A MODIFIED WORKING TREE"); +#endif } /* ==================================================================== @@ -167,18 +188,18 @@ cmd_trace(ftpd_server_t *server, const char *arg) if (strcmp(arg, "ON") == 0) { ftpd_trace_enable(1); - ftpd_log_wto("FTPD080I Trace enabled"); + ftpd_log_wto("FTPD080I TRACE ENABLED"); } else if (strcmp(arg, "OFF") == 0) { ftpd_trace_enable(0); - ftpd_log_wto("FTPD081I Trace disabled"); + ftpd_log_wto("FTPD081I TRACE DISABLED"); } else if (strcmp(arg, "DUMP") == 0) { int n = ftpd_trace_dump(); - ftpd_log_wto("FTPD082I Trace dumped, %d entries", n); + ftpd_log_wto("FTPD082I TRACE DUMPED, %d ENTRIES", n); } else { - ftpd_log_wto("FTPD090E TRACE: expected ON, OFF, or DUMP"); + ftpd_log_wto("FTPD021E TRACE: SYNTAX: TRACE ON|OFF|DUMP"); } } @@ -189,7 +210,7 @@ static void cmd_help(ftpd_server_t *server) { (void)server; - ftpd_log_wto("FTPD020I Commands: STATS, SESSIONS, CONFIG, " + ftpd_log_wto("FTPD020I COMMANDS: STATS, SESSIONS, CONFIG, " "VERSION, TRACE, HELP, SHUTDOWN"); } @@ -199,7 +220,7 @@ cmd_help(ftpd_server_t *server) static void cmd_shutdown(ftpd_server_t *server) { - ftpd_log_wto("FTPD097I FTPD shutting down..."); + ftpd_log_wto("FTPD098I FTPD SHUTTING DOWN"); server->flags &= ~FTPD_ACTIVE; server->flags |= FTPD_QUIESCE; ecb_post(&server->wakeup_ecb, 0); diff --git a/src/ftpd#log.c b/src/ftpd#log.c index 22f1c18..b2f1327 100644 --- a/src/ftpd#log.c +++ b/src/ftpd#log.c @@ -10,6 +10,7 @@ #include #include #include +#include #include "clibwto.h" #include "ftpd#log.h" @@ -45,6 +46,28 @@ ftpd_log_wto(const char *fmt, ...) wto(buf); } +/* ==================================================================== +** Upper case a string into a caller-supplied buffer +** +** The console house style is upper case; only values keep their original +** case. Everything that reaches a WTO as a literal is already written in +** upper case -- this is for the strings that arrive lower case at runtime: +** the build stamp (MBT_VERSION, MBT_COMMIT) and libc370_version(). +** ================================================================= */ +const char * +ftpd_upcase(char *dst, unsigned n, const char *src) +{ + unsigned i; + + if (n == 0) return dst; + + for (i = 0; i + 1U < n && src[i]; i++) + dst[i] = (char)toupper((unsigned char)src[i]); + dst[i] = '\0'; + + return dst; +} + /* ==================================================================== ** General logging to STDOUT ** ================================================================= */ diff --git a/src/ftpd#mvs.c b/src/ftpd#mvs.c index c9e5a77..1ec0e46 100644 --- a/src/ftpd#mvs.c +++ b/src/ftpd#mvs.c @@ -1316,7 +1316,7 @@ ftpd_mvs_retr(ftpd_session_t *sess, const char *arg) if (sess->debug_abend_xfer) { volatile int *trap = (volatile int *)0; sess->debug_abend_xfer = 0; - ftpd_log_wto("FTPD072W DEBUG ABEND=XFER firing mid-RETR socket=%d", + ftpd_log_wto("FTPD072W DEBUG ABEND=XFER FIRING MID-RETR SOCKET=%d", sess->ctrl_sock); *trap = 0; /* force S0C4 */ } diff --git a/src/ftpd#ses.c b/src/ftpd#ses.c index e99ecd2..149073c 100644 --- a/src/ftpd#ses.c +++ b/src/ftpd#ses.c @@ -314,16 +314,16 @@ ftpd_session_recover(ftpd_session_t *sess, unsigned abcode, const char *verb) ** stays observable in the operator log. */ sess->server->total_recover++; if (abcode == 0) - ftpd_log_wto("FTPD070E ABEND recovery (ESTAE create failed) " - "cmd=%s socket=%d total=%u", + ftpd_log_wto("FTPD070E ABEND RECOVERY (ESTAE CREATE FAILED) " + "CMD=%s SOCKET=%d TOTAL=%u", verb, sess->ctrl_sock, sess->server->total_recover); else if (abcode > 0xFFF) - ftpd_log_wto("FTPD070E ABEND S%03X recovered cmd=%s socket=%d " - "total=%u", (abcode >> 12) & 0xFFF, verb, + ftpd_log_wto("FTPD070E ABEND S%03X RECOVERED CMD=%s SOCKET=%d " + "TOTAL=%u", (abcode >> 12) & 0xFFF, verb, sess->ctrl_sock, sess->server->total_recover); else - ftpd_log_wto("FTPD070E ABEND U%04u recovered cmd=%s socket=%d " - "total=%u", abcode, verb, sess->ctrl_sock, + ftpd_log_wto("FTPD070E ABEND U%04u RECOVERED CMD=%s SOCKET=%d " + "TOTAL=%u", abcode, verb, sess->ctrl_sock, sess->server->total_recover); /* 4. Tell the client before touching in-flight resources, so a @@ -461,8 +461,8 @@ ftpd_session_run(void *udata, CTHDWORK *work) ** corrupt state); stop recovering and close it cleanly. ** The worker survives and serves the next connection. */ if (recover_count >= FTPD_MAX_RECOVER) { - ftpd_log_wto("FTPD071E session socket=%d closed after " - "%d consecutive ABENDs", sess->ctrl_sock, + ftpd_log_wto("FTPD071E SESSION SOCKET=%d CLOSED AFTER " + "%d CONSECUTIVE ABENDS", sess->ctrl_sock, recover_count); break; } diff --git a/src/ftpd#sit.c b/src/ftpd#sit.c index 0a7df67..73efd3f 100644 --- a/src/ftpd#sit.c +++ b/src/ftpd#sit.c @@ -291,7 +291,7 @@ ftpd_site_dispatch(ftpd_session_t *sess, const char *arg) /* ABEND=XFER: arm, do not ABEND now. */ if (arg[5] == '=' && (arg[6] == 'X' || arg[6] == 'x')) { sess->debug_abend_xfer = 1; - ftpd_log_wto("FTPD072W DEBUG ABEND=XFER armed socket=%d", + ftpd_log_wto("FTPD072W DEBUG ABEND=XFER ARMED SOCKET=%d", sess->ctrl_sock); ftpd_session_reply(sess, FTP_200, "DEBUG: next RETR will ABEND mid-transfer"); @@ -302,7 +302,7 @@ ftpd_site_dispatch(ftpd_session_t *sess, const char *arg) if (arg[5] == '=' && (arg[6] == 'L' || arg[6] == 'l')) ftpd_acee_enter(sess); - ftpd_log_wto("FTPD072W DEBUG ABEND injection: SITE %s", arg); + ftpd_log_wto("FTPD072W DEBUG ABEND INJECTION: SITE %s", arg); *trap = 0; /* force S0C4 */ } #endif diff --git a/src/ftpd.c b/src/ftpd.c index 2e005ef..adedc2f 100644 --- a/src/ftpd.c +++ b/src/ftpd.c @@ -9,11 +9,20 @@ ** - Flat MODIFY commands: STATS, SESSIONS, CONFIG, VERSION, TRACE, HELP ** - Flag-based shutdown (FTPD_ACTIVE / FTPD_QUIESCE) */ +/* Build stamp. Generated by mbt at every build: MBT_VERSION from the +** project version, MBT_COMMIT from `git rev-parse --short HEAD` (with +** "-dirty" appended when a TRACKED file differs from HEAD, and "unknown" +** outside a git checkout), MBT_COMMIT_DIRTY as the matching 0/1 flag. +** Included here rather than in ftpd.h so a new commit restamps the banner +** without rebuilding every object (mbt#59). */ +#include + #include "ftpd.h" #include "ftpd#ses.h" #include "clibppa.h" #include "clibos.h" #include "clibenq.h" +#include "clibver.h" /* Global server state */ ftpd_server_t *ftpd_server = NULL; @@ -33,6 +42,7 @@ main(int argc, char **argv) COM *com; CIB *cib; int rc; + char vers[24]; /* MBT_VERSION, upper case: FTPD000I+001I */ memset(&server, 0, sizeof(server)); strcpy(server.eye, FTPD_EYE); @@ -50,17 +60,36 @@ main(int argc, char **argv) */ com = __gtcom(); if (!com) { - ftpd_log_wto("FTPD099E COM area not available"); + ftpd_log_wto("FTPD090E COM AREA NOT AVAILABLE"); return 8; } __cibset(5); - ftpd_log_wto("FTPD000I FTPD Server %s starting", FTPD_VERSION); + /* --- Startup banner ------------------------------------------ + ** Which FTPD, built from which source, against which C runtime. A + ** deploy/relink mismatch (sysroot says X, the STC runs Y) then cannot + ** hide, and a build carrying uncommitted changes says so instead of + ** passing itself off as the commit it was branched from. The commit + ** buffers are scoped: they are dead the moment the banner is out. + */ + ftpd_upcase(vers, sizeof(vers), MBT_VERSION); + { + char commit[24]; + char stamp[48]; + + ftpd_log_wto("FTPD000I FTPD %s (%s) STARTING", + vers, ftpd_upcase(commit, sizeof(commit), MBT_COMMIT)); + ftpd_log_wto("FTPD005I %s", + ftpd_upcase(stamp, sizeof(stamp), libc370_version())); +#if MBT_COMMIT_DIRTY + ftpd_log_wto("FTPD006W BUILT FROM A MODIFIED WORKING TREE"); +#endif + } /* Initialize server (config, trace, socket thread, worker pool) */ rc = initialize(&server, argc, argv); if (rc != 0) { - ftpd_log_wto("FTPD099E Initialization failed, rc=%d", rc); + ftpd_log_wto("FTPD091E INITIALIZATION FAILED RC=%d", rc); return rc; } @@ -81,7 +110,7 @@ main(int argc, char **argv) __asm__("STIMER WAIT,BINTVL==F'10'"); } - ftpd_log_wto("FTPD001I Server is READY"); + ftpd_log_wto("FTPD001I FTPD %s READY", vers); /* ---------------------------------------------------------------- ** Main event loop @@ -119,7 +148,7 @@ main(int argc, char **argv) terminate(&server); - ftpd_log_wto("FTPD098I FTPD shutdown complete"); + ftpd_log_wto("FTPD099I FTPD SHUTDOWN COMPLETE"); return 0; } @@ -132,19 +161,13 @@ initialize(ftpd_server_t *server, int argc, char **argv) { int rc; - /* APF authorize task + STEPLIB */ - { - CLIBCRT *crt = __crtget(); - rc = clib_apf_setup(argv[0]); - if (rc) { - ftpd_log_wto("FTPD003W APF setup failed RC=%d", rc); - } else { - if (crt->crtauth & CRTAUTH_ON) - ftpd_log_wto("FTPD000I FTPD was APF authorized " - "via SVC 244"); - if (crt->crtauth & CRTAUTH_STEPLIB) - ftpd_log_wto("FTPD003I STEPLIB is now APF authorized"); - } + /* APF authorize task + STEPLIB. Only the failure is worth a line on + ** the console: a successful authorization is the normal case and says + ** nothing an operator has to act on. This warns and continues -- the + ** commands that need authorization fail individually and say so. */ + rc = clib_apf_setup(argv[0]); + if (rc) { + ftpd_log_wto("FTPD003W APF SETUP FAILED RC=%d", rc); } /* STC identity switch: RACINIT ENVIR=CREATE for FTPD/USER @@ -159,8 +182,8 @@ initialize(ftpd_server_t *server, int argc, char **argv) int racf_rc = 0; if (__super(PSWKEY0, &savekey)) { - ftpd_log_wto("FTPD004W RACINIT failed: " - "cannot enter supervisor state"); + ftpd_log_wto("FTPD004W RACINIT FAILED: " + "CANNOT ENTER SUPERVISOR STATE"); } else { /* Delete current security environment */ old_acee = racf_get_acee(); @@ -171,11 +194,11 @@ initialize(ftpd_server_t *server, int argc, char **argv) new_acee = racf_login("FTPD", NULL, "USER", &racf_rc); if (new_acee) { racf_set_acee(new_acee); - ftpd_log_wto("FTPD004I STC identity set to " - "FTPD/USER via RACINIT"); + ftpd_log_wto("FTPD004I STC IDENTITY SET TO " + "FTPD/USER VIA RACINIT"); } else { ftpd_log_wto("FTPD004W RACINIT ENVIR=CREATE " - "failed RC=%d", racf_rc); + "FAILED RC=%d", racf_rc); } __prob(savekey, NULL); @@ -203,10 +226,10 @@ initialize(ftpd_server_t *server, int argc, char **argv) ** operator gets told at every start -- a server reachable directly ** with this on promises its clients confidentiality it has not got. */ if (server->config.sslproxy) { - ftpd_log_wto("FTPD005W SSLPROXY=YES: PBSZ/PROT are acknowledged " - "but FTPD encrypts nothing"); - ftpd_log_wto("FTPD005W A TLS terminating proxy must be in front " - "of port %d", server->config.port); + ftpd_log_wto("FTPD007W SSLPROXY=YES: PBSZ/PROT ARE ACKNOWLEDGED " + "BUT FTPD ENCRYPTS NOTHING"); + ftpd_log_wto("FTPD007W A TLS TERMINATING PROXY MUST BE IN FRONT " + "OF PORT %d", server->config.port); } /* Refuse a second instance on the same port. @@ -232,8 +255,8 @@ initialize(ftpd_server_t *server, int argc, char **argv) /* rc=4 is the expected "another address space holds it"; anything ** else goes to the trace, the operator message stays plain. */ ftpd_trace("startup ENQ %s rc=%d", server->enq_rname, rc); - ftpd_log_wto("FTPD002E FTPD is already active on port %d, " - "this instance ends", server->config.port); + ftpd_log_wto("FTPD002E FTPD IS ALREADY ACTIVE ON PORT %d, " + "THIS INSTANCE ENDS", server->config.port); return 8; } @@ -282,7 +305,7 @@ socket_thread(void *arg1, void *arg2) */ sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { - ftpd_log_wto("FTPD050E socket() failed, errno=%d", errno); + ftpd_log_wto("FTPD050E SOCKET() FAILED, ERRNO=%d", errno); return 8; } @@ -297,7 +320,7 @@ socket_thread(void *arg1, void *arg2) ** (issue #76). */ if (ftpd_adr_parse(server->config.bind_ip, NULL, &bindaddr) == FTPD_ADR_BAD) { - ftpd_log_wto("FTPD055E SRVBIND %s is not an address, not listening", + ftpd_log_wto("FTPD055E SRVBIND %s IS NOT AN ADDRESS, NOT LISTENING", server->config.bind_ip); closesocket(sock); return 8; @@ -321,8 +344,8 @@ socket_thread(void *arg1, void *arg2) if (getsockname(si, &sa, &salen) == 0) { sin = (struct sockaddr_in *)&sa; if (sin->sin_port == htons(server->config.port)) { - ftpd_log_wto("FTPD053I Closing stale socket %d " - "on port %d", si, server->config.port); + ftpd_log_wto("FTPD053I CLOSING STALE SOCKET %d " + "ON PORT %d", si, server->config.port); closesocket(si); __asm__("STIMER WAIT,BINTVL==F'200'"); break; @@ -333,13 +356,13 @@ socket_thread(void *arg1, void *arg2) if (bind(sock, &saddr, sizeof(saddr)) < 0) { int err = errno; - ftpd_log_wto("FTPD051E bind() failed on %s port %d, errno=%d", + ftpd_log_wto("FTPD051E BIND() FAILED ON %s PORT %d, ERRNO=%d", server->config.bind_ip, server->config.port, err); if (err == EADDRINUSE) { - ftpd_log_wto("FTPD051I EADDRINUSE, retrying in 10s"); + ftpd_log_wto("FTPD051I EADDRINUSE, RETRYING IN 10S"); __asm__("STIMER WAIT,BINTVL==F'1000'"); if (bind(sock, &saddr, sizeof(saddr)) < 0) { - ftpd_log_wto("FTPD051E bind() retry failed, errno=%d", + ftpd_log_wto("FTPD051E BIND() RETRY FAILED, ERRNO=%d", errno); closesocket(sock); return 8; @@ -351,7 +374,7 @@ socket_thread(void *arg1, void *arg2) } if (listen(sock, 10) < 0) { - ftpd_log_wto("FTPD052E listen() failed, errno=%d", errno); + ftpd_log_wto("FTPD052E LISTEN() FAILED, ERRNO=%d", errno); closesocket(sock); return 8; } @@ -360,7 +383,7 @@ socket_thread(void *arg1, void *arg2) /* Name the address, not just the port: ANY and one interface look the ** same from the console otherwise, and that is exactly the difference ** an operator who set SRVBIND wants confirmed (issue #76). */ - ftpd_log_wto("FTPD054I Listening for FTP connections on %s port %d", + ftpd_log_wto("FTPD054I LISTENING ON %s PORT %d", server->config.bind_ip, server->config.port); /* Accept loop — non-blocking socket + ecb_timed_wait. @@ -463,13 +486,13 @@ terminate(ftpd_server_t *server) int i; for (i = 0; (i < 10) && (!(server->sock_task->termecb & 0x40000000U)); i++) { if (i) { - ftpd_log_wto("FTPD095I Waiting for socket thread " - "to terminate (%d)", i); + ftpd_log_wto("FTPD095I WAITING FOR SOCKET THREAD " + "TO TERMINATE (%d)", i); } __asm__("STIMER WAIT,BINTVL==F'100'"); } if (!(server->sock_task->termecb & 0x40000000U)) { - ftpd_log_wto("FTPD095W socket thread did not terminate"); + ftpd_log_wto("FTPD095W SOCKET THREAD DID NOT TERMINATE"); } cthread_delete(&server->sock_task); server->sock_task = NULL; From db28e70aff90693d619f542e85d01a3947aeb7c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Gro=C3=9Fmann?= Date: Wed, 12 Aug 2026 09:47:06 +0200 Subject: [PATCH 2/2] APF authorize before the first WTO so the banner loses its '+' (#98) MCS prefixes every message from an unauthorized problem program with '+'. clib_apf_setup() ran inside initialize(), i.e. after the banner had already gone out, so the startup showed one line marked up differently from all the others: +FTPD000I FTPD Server 1.0.0-dev starting FTPD000I FTPD was APF authorized via SVC 244 Hoist the call into main() between __cibset() and the banner, which is where UFSD has it. The rc is carried past the banner rather than reported at the call site: a failure is worth a line, but not one ahead of the message saying which server is starting. FTPD still warns and continues where UFSD gives up -- that behaviour is unchanged. initialize() no longer needs argc/argv and drops both parameters. --- src/ftpd.c | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/ftpd.c b/src/ftpd.c index adedc2f..da59e13 100644 --- a/src/ftpd.c +++ b/src/ftpd.c @@ -29,7 +29,7 @@ ftpd_server_t *ftpd_server = NULL; /* Forward declarations */ static int socket_thread(void *arg1, void *arg2); -static int initialize(ftpd_server_t *server, int argc, char **argv); +static int initialize(ftpd_server_t *server); static void terminate(ftpd_server_t *server); /* ==================================================================== @@ -42,8 +42,11 @@ main(int argc, char **argv) COM *com; CIB *cib; int rc; + int apf_rc; char vers[24]; /* MBT_VERSION, upper case: FTPD000I+001I */ + (void)argc; + memset(&server, 0, sizeof(server)); strcpy(server.eye, FTPD_EYE); server.flags |= FTPD_ACTIVE; @@ -65,6 +68,24 @@ main(int argc, char **argv) } __cibset(5); + /* --- APF authorization ---------------------------------------- + ** Before the first WTO on purpose. MCS prefixes every message from + ** an unauthorized problem program with '+', so a banner issued ahead + ** of this would be the one startup line marked up differently from + ** all the others -- which is exactly what the old order produced: + ** + ** +FTPD000I FTPD Server 1.0.0-dev starting + ** FTPD000I FTPD was APF authorized via SVC 244 + ** + ** UFSD orders it the same way (__gtcom, __cibset, clib_apf_setup, + ** then the banner). The rc is carried past the banner rather than + ** reported here: a failure is worth a line, but not one ahead of the + ** message saying which server is starting. FTPD warns and continues + ** where UFSD gives up -- the commands that need authorization fail + ** individually and say so. + */ + apf_rc = clib_apf_setup(argv[0]); + /* --- Startup banner ------------------------------------------ ** Which FTPD, built from which source, against which C runtime. A ** deploy/relink mismatch (sysroot says X, the STC runs Y) then cannot @@ -86,8 +107,11 @@ main(int argc, char **argv) #endif } + if (apf_rc) + ftpd_log_wto("FTPD003W APF SETUP FAILED RC=%d", apf_rc); + /* Initialize server (config, trace, socket thread, worker pool) */ - rc = initialize(&server, argc, argv); + rc = initialize(&server); if (rc != 0) { ftpd_log_wto("FTPD091E INITIALIZATION FAILED RC=%d", rc); return rc; @@ -157,19 +181,10 @@ main(int argc, char **argv) ** Initialize -- config, logging, trace, socket thread, worker pool ** ================================================================= */ static int -initialize(ftpd_server_t *server, int argc, char **argv) +initialize(ftpd_server_t *server) { int rc; - /* APF authorize task + STEPLIB. Only the failure is worth a line on - ** the console: a successful authorization is the normal case and says - ** nothing an operator has to act on. This warns and continues -- the - ** commands that need authorization fail individually and say so. */ - rc = clib_apf_setup(argv[0]); - if (rc) { - ftpd_log_wto("FTPD003W APF SETUP FAILED RC=%d", rc); - } - /* STC identity switch: RACINIT ENVIR=CREATE for FTPD/USER ** Replaces the default STC security environment (PROD/PRDGROUP) ** with a dedicated FTPD identity. Requires APF authorization.