Skip to content

Releases: mvslovers/ftpd

FTPD 1.3.0

Choose a tag to compare

@github-actions github-actions released this 22 Oct 19:26
6b6367d

FTPD 1.3.0 is a minor release of the FTP server for MVS 3.8j (FTPD). It is a drop-in for an FTPD 1.2.0 that SMP installed: same data sets, same started task procedure, same APF entry, same FTPDPRM0. The FMID is TFTP130, and the install deletes TFTP120. Three things in it can stop an upgrade that is done without reading: the JES view now asks RAKF which jobs a user may see (without a profile, a user sees only the own jobs), the started task no longer switches to FTPD/USER at startup, and a text line longer than the record of the data set now fails the transfer instead of being cut. Besides that, it closes gaps in the SITE command set and repairs behaviour that was wrong, silent or hung.

FTPD 1.3.0 is built with LIBC/370 (libc370) 2.25.1 and CC/370 (cc370) 1.6.1 by the release workflow, which uses MBT (mbt) 3.0.0-rc1 as the build tool, and carries the UFSD client library (libufs) of UFSD 1.4.0, unchanged.

Read this first

Upgrading from 1.2.0 (installed by SMP). Install into the same data sets and skip the allocation job: the data sets exist, and the job would allocate them NEW. The APPLY ends COND CODE 0004 on HMA2461 SYSMOD TFTP120 NOT FOUND ON SMPSCDS LIBRARY. That is the expected result of a replacement, not a failure. MVS/CE is different: the FTPD 1.2.0 that comes with the MVS/CE image was not installed by SMP and has no FTPD.* data sets, so on MVS/CE the install is a first install as far as SMP and the data sets go. Run the allocation job, run the install job (its APPLY ends COND CODE 0000, no HMA2461), and replace the procedure of the image by the shipped one; the library of the image's procedure stays and is no longer used. From 1.1.0 or older, upgrade to 1.2.0 first (1.2.0 deletes TFTP110 only; on a system where TFTP100 still owns the load module the install copies nothing and ends RC 00).

Verify the install, not the return code. HMA2380 COPY SUCCESSFUL must be in the job log, and FTPD.LINKLIB must hold the member FTPD. After /S FTPD the startup messages must read:

FTPD000I FTPD 1.3.0 (6B6367D) STARTING
FTPD005I LIBC370 2.25.1 (FCDE4E8)

LIBC370 2.10.3 on the second line means the started task still runs 1.2.0.

What you must act on:

  • Decide who may see the jobs of other users. The JES view asks the RAKF class JESSPOOL. Before, every user could list, read and cancel the jobs of everybody. Now a user sees, reads and cancels only the own jobs, and the jobs of other users need a profile in JESSPOOL. The class exists from RAKF 2.0.0. On a RAKF without it (TK5 as shipped), FTPD 1.3.0 installs and runs, and each user sees only the own jobs. If an administrator or an operator relies on seeing all jobs, define the profiles on RAKF 2.0.0 or later before you upgrade. SITE JESOWNER with another user ID or * needs the same right and answers 501 JESOWNER is not permitted without it.
  • The started task keeps the identity RAKF gives it. FTPD 1.2.0 and earlier logged on as FTPD/USER at startup, whatever RAKF assigned. FTPD 1.3.0 does not, and says which identity it runs under in FTPD004I (or FTPD004W). On a RAKF without a mapping by started-task name, such as TK5 before Update 6, the task now runs as STC/STCGROUP. The group of the FTPD line in USERS must be allowed on FACILITY SVC244, or FTPD cannot obtain APF authorization (FTPD003W APF SETUP FAILED RC=4). The group USER of the 1.2.0 guide is not one the shipped profile allows.
  • A text line longer than the record of the data set fails. STOR and APPE answer 451 and remove a data set the command created. Before, FTPD cut the line without a word and answered 250. A script that relied on the silent cut now fails. SITE TRUNCATE brings the cutting back, and the 250 then says so.
  • APPE appends. It adds to a sequential data set and creates what is missing; it refuses an existing member of a PDS. Before, it replaced the data set like STOR.
  • SITE RDW, and a SITE SBSENDEOL other than CRLF, answer 501. Before, the answer was 200 and nothing changed.
  • Settings FTPD never used are now reported. AUTHUSER, JESINTERFACELEVEL and the lines of the form VOLSER,UNIT in FTPDPRM are reported as ignored, in the log and with one console message FTPD062W. CONFIG no longer writes FTPD047I and FTPD048I.
  • An FTPD that is not authorized answers PASS with 530. Before, PASS got no answer at all and the session was closed after the third one. FTPD003W at startup still means the install is not finished.
  • The started task procedure no longer needs the HASPCKPT and HASPACE1 DD statements. FTPD finds the JES2 checkpoint and the spool itself. Keep a statement only when it is needed (another DSNPRFX, or a checkpoint that is neither cataloged nor on an online volume). A statement that names the wrong data set or volume now breaks every JES command with 451.

Known issue: a comment card before the JOB statement takes the job's credentials (ftpd#277). When a job is stored to the JES view, FTPD takes the first card that has the word JOB between blanks for the JOB statement. A comment card such as //* RUN THE JOB NOW ahead of the real JOB statement matches, so NOTIFY, USER and PASSWORD are appended to the comment and the real JOB statement gets none. The job does not run under your identity. Workaround: put no card with the word JOB between blanks before the JOB statement, and write no comment with it. Planned for 1.4.0.

What changed

before (1.2.0) now (1.3.0)
jobs in the JES view every user lists, reads and cancels every job own jobs only; others by JESSPOOL profile (#90)
identity of the started task switched to FTPD/USER at startup (#179) keeps the identity RAKF gives it, named in FTPD004I
MAXSESSIONS of 1, 2 or 3 no session ever starts: connections queue, no worker wakes (#168) one worker per allowed session; the sessions run
connection refused by the worker pool control connection stays open, session never freed (#177) 421 and the connection is closed
burst of clients one connection accepted per look, one look a second: ten clients greeted over about ten seconds (#209) every waiting connection accepted at each look: greeted within about a second
STOR/APPE into a data set under retention session waits for an operator reply to IEC507D (#198) 550 with the expiration date
same, expiration date 99000 the same wait (#204) the same 550
STOR/APPE of a member into a PDS with a full directory 451, and a retry fails the same way (#173) 552 with the remedy
after a STOR failed on a full PDS directory the old name stays allocated to the started task; MKD of it answers 550; P FTPD ends with abend SA0A (#194, #223) data set can be renamed, its old name created again, and P FTPD ends cleanly
listing that runs out of storage empty list (#118) 451 Insufficient storage to list ...
STATS a transfer of a data set counted two to three times, running sessions included (#206) each byte once, ended sessions only
download of more than 256 bytes per call exposed to the X'75' restart defect on the send side (#137) fixed by LIBC/370 2.24.0 and later
text STOR into a VB/VBA data set record boundaries lost (#185) one record per line
text STOR into a RECFM=U data set all lines stored as one record (#245) one block per line; RETR sends one line per block
STOR that ends in E37 the DD numbers stay allocated, a batch job with DISP=OLD on the data set waits until FTPD ends, and P FTPD logs further IEC032I E37-04 (#265) DCB, allocation and ENQ are released and the session goes on
UFS create without free inodes 452 552, the condition is permanent (#151)
SITE keywords PRI=5 is an unknown word; keywords are case-sensitive shortest forms accepted; case does not matter (#130, #189)
SITE JESSTATUS any value stored, every job listed ALL, INPUT, ACTIVE or OUTPUT filter the list, RETR and DELE
get or delete of a job ID such as JOB* 550 Jobid ... not found ... from JES2 501 Invalid job ID before JES2 is asked (#267)

Measured on MVS by the acceptance run of the release candidate, on TK5 and MVS/CE systems started for the test: a burst of clients was greeted within 0.97 seconds at most; STATS counted 80000 bytes for a transfer of 80000 bytes; a SITE RETPD above MAXRETPD was answered 501; after an E37 on STOR the DCB, the allocation and the ENQ were released and the session went on. The E37 row of 1.2.0 was measured on a throwaway system with 1.2.0 and with the same data set filled up. The rows for the connection refused by the worker pool (#177), the listing that runs out of storage (#118), the UFS inodes (#151), FTPD077E and the download of more than 256 bytes per call (the X'75' defect, #137, fixed in LIBC/370) are covered by the unit tests of the release; they were not exercised on a running system.

1.2.0 1.3.0
FMID TFTP120 TFTP130, deletes TFTP120
Libraries FTPD.LINKLIB, FTPD.SAMPLIB, FTPD.AFTPDLOD unchanged
C library LIBC/370 2.10.3 LIBC/370 2.25.1, CC/370 1.6.1
libufs (UFSD) 1.4.0 1.4.0, unchanged
RAKF 1.2.x or 2.0.0 1.2.x or 2.0.0; 2.0.0 or later to let users see the jobs of others (JESSPOOL)

Install

The release has three assets (the files you install from):

Asset What it is
ftpd-1.3.0-dist.zip, ftpd-1.3.0-dist.tar.gz the distribution archive, the same content in two formats; download this one
ftpd-1.3.0-load.xmit the FTPD load module as a TSO RECEIVE-ready XMIT (also inside the archive)

Unpack the archive and follow its README.md, which is the installation guide of this release. The archive holds:

| Fi...

Read more

v1.2.0

Choose a tag to compare

@github-actions github-actions released this 13 Oct 09:32

FTPD 1.2.0 moves FTPD to LIBC/370 (libc370) 2.10.3 and CC/370 (cc370) 1.6.1, and fixes an upload that reported success for a member that was never stored. The port to libc370 2.10.3 required that fix. With the earlier C library, a member upload that ran out of space in a PDS ended in an ABEND SD37: FTPD answered 552, and the old member survived. With 2.10.3 the same condition comes back as an error code at close, and without the fix the port alone would have answered 250 Transfer completed successfully for every such upload. For a system running 1.1.0 this is a drop-in: the same data sets, the same procedure, the same APF entry and the same FTPDPRM0. Stop the server, run the install job, start it again. Built against libc370 2.10.3 and libufs from UFSD 1.4.0. The FMID is TFTP120 and it deletes TFTP110.

Read this first

  • Coming from 1.1.0: /P FTPD, run the install job, /S FTPD. Skip the allocation job, because the data sets already exist.
  • Coming from 1.0.x: install 1.1.0 first (v1.1.0, section 2b of the guide), then 1.2.0. TFTP120 deletes only TFTP110. On a system where TFTP100 still owns the load module, the 1.2.0 install copies nothing and still ends RC 00.
  • On an upgrade from 1.1.0 the APPLY ends COND CODE 0004 on HMA2461 SYSMOD TFTP110 NOT FOUND ON SMPSCDS LIBRARY. That is expected, and the install job's ACCEPT is gated to run through it. A fresh install ends RC 00.
  • Verify by the banner and the member list, not by the condition codes. After the restart FTPD005I must read LIBC370 2.10.3 (A790DC4); 1.1.0 showed LIBC370 V1.0.6. FTPD.LINKLIB must hold FTPD.
  • A member upload into a full PDS now answers 552, and the member is not stored. If you replaced an existing member, its old content is still there.
  • LIST/NLST of a PDS under a storage shortage answers 451 Insufficient storage instead of 550 No data sets found. This path was not reproduced on MVS: a storage shortage in the middle of a directory read cannot be triggered on purpose.

Fixed

before now
a member upload into a full PDS (#170) with the C library change and without this fix: 250 Transfer completed successfully, member not stored 552, member not in the directory
replacing a member that no longer fits (#170) the same 250 552; the old member keeps its content
LIST/NLST of a PDS when storage runs out (#158) 550 No data sets found, as if the PDS were empty 451 Insufficient storage to list <dsn>
three log messages (#155) each dash logged as three junk characters a plain hyphen

Measured on MVS with a small PDS (FB 80, BLKSIZE 19040, two tracks, five directory blocks), filled with 47-record members until one fails:

build C library result
the previous development build LIBC/370 2.6.3 4/4: ABEND SD37 in close, answered 552, old member kept
the port without the fix LIBC/370 2.10.3 59 uploads after the 21st answered 250, none of them stored
1.2.0 LIBC/370 2.10.3 4/4, twice: 552, failed member not in the directory, old member kept with all 47 records

The test is section 8 of test/test_ftpd.sh.

1.1.0 1.2.0
FMID TFTP110 TFTP120, deletes TFTP110
Libraries FTPD.LINKLIB, … unchanged
C library LIBC/370 1.0.6 LIBC/370 2.10.3, CC/370 1.6.1
libufs (UFSD) 1.3.0 1.4.0

UFSD stays optional at runtime. UFSD 1.4.0 changed only its C library; its server and the libufs interface are the same as in 1.3.0.

Install

Download ftpd-1.2.0-dist.zip (or .tar.gz); the guide and every file you need are inside:

README.md the installation guide
ftpd-1.2.0-load.xmit the FTPD load module
ftpd-1.2.0-samplib.xmit the STC procedure and the configuration pattern
ftpd-1.2.0-alloc.jcl allocate the data sets: first install only
ftpd-1.2.0-inst.jcl receive, apply, accept

Upload the two XMITs in binary. Then submit the install job, after the allocation job on a first install. The only thing to edit is the two data set names you uploaded to.

The documentation in the repository:

Changes

  • Port to LIBC/370 2.x (#157), now 2.10.3 with CC/370 1.6.1 (#169)
  • STOR reports a failed close as a failed transfer (#170)
  • LIST/NLST tell a storage shortage from an empty PDS (#158)
  • Log messages without characters EBCDIC cannot encode (#155)
  • Build: mbt 3 (#167)

v1.1.0

Choose a tag to compare

@github-actions github-actions released this 14 Sep 10:33

FTPD 1.1.0 is about uploads: how much space they get, what happens when they run out of it, and where the data set lands. Eleven issues closed — #100, #121, #122, #124, #127, #128, #129, #133, #135, #143 and #148 — three of them reported from outside, and one change that touches every installation. It is not a drop-in for 1.0.x: the data set names lose their version qualifier, so the started task's STEPLIB and the APF entry are re-pointed once. Your FTPDPRM0 needs no change — no keyword changed meaning, and the three new ones default to the old behaviour. Built against libc370 1.0.6 and libufs from ufsd 1.3.0 (the build requires ufsd >= 1.3.0; UFSD itself stays optional at runtime, see the guide's prerequisites). The FMID is TFTP110 and it deletes TFTP100.

This release was published once and withdrawn. If you installed the
earlier build, or tried to, read Upgrading from 1.0.x below — the procedure
has changed and is now shorter, not longer.

Read this first: what an operator or a client notices

  • The data sets are FTPD.LINKLIB, FTPD.SAMPLIB and FTPD.AFTPDLOD now (#121), not FTPD.V1R0Mx.*. After the install, scratch the old libraries, re-point STEPLIB and update the APF entry — the install job cannot do that for you, and until STEPLIB moves you are still running 1.0.x with no error anywhere.
  • No migration step before the install (#143). The SYSMOD carries ++VER(Z038) DELETE(TFTP100); SMP transfers ownership of the module itself during the APPLY.
  • On an upgrade the APPLY step ends COND CODE 0004 on HMA2461 SYSMOD TFTP100 NOT FOUND ON SMPSCDS LIBRARY. Expected; the install job gates its ACCEPT accordingly. A fresh install ends RC 00.
  • An upload that runs out of space answers 552, not 451 (#129, #135). 552 is permanent: a conforming client no longer retries it. A genuine write error still answers 451.
  • A failed upload no longer leaves its data set behind (#129). A data set FTPD allocated for the transfer is scratched (FTPD074I); an upload into an existing data set is never scratched.
  • Unused space is released at close (#128) for data sets FTPD allocated itself.
  • DEFUNIT and DEFVOLUME are honoured now (#133), and so are SITE UNIT= and SITE VOLUME=. If your parmlib names a volume, new data sets land there from this release on; a volume that is not online is reported at startup (FTPD059W) and ignored.
  • STOR space is configurable (#100): DEFPRIMARY, DEFSECONDARY and DEFSPACETYPE in FTPDPRM0, SITE PRIMARY=/SECONDARY=/TRACKS/CYLINDERS/BLOCKS per session. The defaults are the old hardcoded TRK(10,5).
  • A UFS upload that failed no longer answers 226 (#148): 552 for out of space, the UFS error for any other write failure, 426 for a broken data connection. Expect the 552 at about 4.06 MB per file — a property of the UFS on-disk format, not a full disk.
  • An IEAAPF00 entry for FTPD.LINKLIB dictates its volume. If you already have one, put VOL=SER= on the allocation job before you run it (see Install).

Fixed, and measured on MVS

before now
an upload past the allocation (#127, #129, #135) SB37/SD37/SE37, answered 451 — retryable, and every retry met the same full data set 552, permanent, with advice on how to allocate more
STOR space (#100) SPACE=TRK(10,5), fixed — about 85 tracks, ~4 MB at FB/80/3120 DEFPRIMARY/DEFSECONDARY/DEFSPACETYPE, overridable by SITE
an unparseable space value silently 10 in one code path, 100 in another refused, with a message naming the default it fell back to
the session after an SD37 (#129) 300 seconds to a client timeout; the DD left allocated, so the scratch got IDCAMS RC=8 ends promptly; the data set is gone; a retry from a fresh allocation works
the data set of a failed upload (#129) left catalogued, holding the partial upload scratched, FTPD074I
unused space after a small upload (#128) kept released at close
TYPE A upload, more than 256 bytes per receive (#122, #124) lines missing, fragments spliced together 5 rounds of 2 MB: byte-identical every time
DEFUNIT, DEFVOLUME, SITE UNIT=, SITE VOLUME= (#133) parsed and ignored honoured; configured values checked at startup
SITE VOLUME= naming a volume that is not mounted (#133) session hung, IEF238D outstanding on the console a reply to the client
a failed UFS upload (#148) 226 Transfer complete 552, the UFS error, or 426
data set names (#121) FTPD.V1R0M2.LINKLIB, … — new names every patch FTPD.LINKLIB, … — one APF entry, one IPL, ever
replacing the previous FMID (#143) the withdrawn first build: a manual step before the install, silent when skipped DELETE(TFTP100) during the APPLY
1.0.x 1.1.0
FMID TFTP100 TFTP110
Libraries FTPD.V1R0M2.LINKLIB, … FTPD.LINKLIB, …
libc370 1.0.4 1.0.6
libufs (ufsd) 1.2.2 1.3.0

Install

Download ftpd-1.1.0-dist.zip (or .tar.gz); the guide and every file you need are inside:

README.md the installation guide
ftpd-1.1.0-load.xmit the FTPD load module
ftpd-1.1.0-samplib.xmit the STC procedure and the configuration pattern
ftpd-1.1.0-alloc.jcl allocate the data sets — run once
ftpd-1.1.0-inst.jcl receive, apply, accept

Upload the two XMITs in binary, submit the allocation job once, then the install job — the only thing to edit is the two data set names you uploaded to. The SYSMOD travels inline in the job, so there is no third file to transfer. FMID is TFTP110.

The documentation in the repository:

One thing to check before you run the allocation job. On MVS 3.8j an IEAAPF00 entry is dsname volser, not a name alone. If you have already added FTPD.LINKLIB to the APF list, that entry does not merely permit a volume — it dictates one, and the allocation job says UNIT=SYSDA with no VOL=SER. Put the volume in:

//LINKLIB  DD  DSN=FTPD.LINKLIB,DISP=(NEW,CATLG,DELETE),
//            UNIT=SYSDA,VOL=SER=your-apf-volume,

Get it wrong and you have a library that exists, holds the right module, passes every check — and is not authorised. Nothing reports it, because FTPD falls back to the SVC 244 route and starts anyway. The startup message is the only place the two are distinguishable: on a system where you added an APF entry, FTPD008I AUTHORIZED BY SVC (MODULE KEY 8) means the entry is not covering the library. Found the hard way during an install of UFSD 1.3.0 on a development system.

The two caveats from 1.0.x still apply to the shipped material:

  • On TK5, SYS2.PARMLIB does not exist — put FTPDPRM0 in SYS1.PARMLIB and start with /S FTPD,D='SYS1.PARMLIB'.
  • The two JES DDs in the procedure, HASPCKPT and HASPACE1, carry the unit and volume of the system FTPD was built on. Point them at your own JES2 checkpoint and spool, or delete both if you do not want the JES interface. Both are required together: with HASPACE1 missing, every JES command answers 451.

Not using SMP? Receive ftpd-1.1.0-load.xmit into a library and point the procedure's STEPLIB at it.

Upgrading from 1.0.x

Install it. The FMID takes care of itself — that is #143, described below.

What the install job cannot do for you is the data sets, and the reason is worth thirty seconds. SMP records the ddname an element was installed through, never the data set name. Both FTPD.V1R0M2.LINKLIB and FTPD.LINKLIB end in LINKLIB, so both installs use ddname LINKLIB — and when 1.1.0's APPLY deletes the old load module it resolves that ddname in its own job, which points at the new library. FTPD.V1R0M2.LINKLIB is never opened.

So after the install:

  1. Scratch the old libraries. Look them up rather than assuming the patch level — ISPF 3.4 on FTPD.*, or LISTCAT LEVEL(FTPD). Expect three: FTPD.V1R0M?.LINKLIB, .AFTPDLOD, .SAMPLIB. They are invisible to SMP now, so nothing will ever mention them again.
  2. Repoint the started task's STEPLIB at FTPD.LINKLIB. This is the step that decides whether you are running 1.1.0 at all. If you skipped step 1 the old library still exists and still holds a working module: FTPD starts, serves, and logs no error — it is simply the old one.
  3. Update the APF entry. The last time it will need changing, because the name no longer carries a version. One IPL ever, not one per release.

Stop FTPD before you begin: FTPD.LINKLIB is the library it loads from.

One return code that looks wrong and is not. On an upgrade the APPLY step ends COND CODE 0004, not 0000:

HMA2270 APPLY PROCESSING SUCCESSFULLY COMPLETED FOR SYSMOD TFTP110
HMA2461 SYSMOD TFTP100 NOT FOUND ON SMPSCDS LIBRARY
HMA2050 APPLY PROCESSING COMPLETED - HIGHEST RETURN CODE IS 04

HMA2461 is SMP noting that the deleted level has no backup entry to keep — it never will, because it was deleted rather than superseded. HMA2270 is the line that says the APPLY worked. The install job expects this and gates its ACCEPT accordingly. A fresh install has nothing to delete and ends RC 00.

Your FTPDPRM0 and your RAKF definitions are unaffected. No keyword changed meaning; the three new ones default to the old behaviour.

What is running

FTPD005I stamps the library the module was actually linked against, so a deploy that did not take cannot hide:

FTPD000I FTPD 1.1.0 (23011CD) STARTING
FTPD005I LIBC370 1.0.6

...

Read more

v1.0.2

Choose a tag to compare

@github-actions github-actions released this 05 Sep 13:47

FTPD 1.0.2 is a relink: the release artifacts are built against libc370
1.0.4 instead of 1.0.3. One issue closed, no new feature, no breaking change —
and one behaviour change that is not labelled breaking but should be read.

If you saw an occasional ABEND S001 under parallel transfers, this is the
release that fixes it.
If you also run UFSD, take ufsd 1.2.2 with it: the
same library fixes reach it, and it is the same kind of relink.

The headline: #117, an S001 that was never in the transfer path

Rob Prins reported an ABEND S001 that hit
occasionally during uploads to UFS, mostly under parallel transfers, and sent
the dump. The dump was all it took — and the answer was somewhere else
entirely.

The abend is not in the UFS path, or the upload path, or FTPD at all. It is a
locking defect in libc370, the C runtime statically linked into every one of
our servers. vvprintf()'s %s and %eEgGfF branches called the public
fputs(), and every __examin() conversion went through the public fputc().
Those wrappers lock and unconditionally unlock the same FILE, and ENQ/DEQ
RET=HAVE carries no nesting count — so the inner unlock released the caller's
hold at the first conversion, and everything after it ran unserialized,
including the \n-triggered QSAM PUT.

Two session threads writing a log line at the same moment therefore merged and
truncated records, and the broken PUT reached the JES2 spool as an I/O error —
which MVS, with no SYNAD exit on the DCB, turned into S001 and took the whole
address space with it. Rob's dump shows the collision itself: the last log
record before the abend is two log lines merged into one truncated record. That
is why it was occasional, and why parallel transfers made it likely.

Fixed as libc370#145 and #147,
and measured there on demand: a writer wedged after 8 of 400 lines against the
unfixed library, and 800 of 800 records intact against the fixed one. libc370
ships through the cc370 sysroot rather than as a pinned dependency, so this
relink is the entire delivery mechanism
— the fix reaches a running FTPD no
other way. Thank you, Rob.

fprintf() was never affected: it formats into a private buffer and issues one
fwrite().

Three neighbours came with it: puts() locked twice and could be split at its
newline by a concurrent printf; fclose() flushed, closed the DCB and freed
both buffer and FILE with no hold on the lock; and __enqdeq()'s DEQ branch
overwrote pl.opt instead of OR-ing into it, so every DEQ went out
SCOPE=STEP and could never release a SCOPE=SYSTEM ENQ.

Not labelled breaking, but read it (#119)

libc370 1.0.4 now plants a SYNAD on the DCBs its stdio layer opens, so an
uncorrectable I/O error — bad track, wrong-length record, device error — becomes
ferror() + errno EIO instead of ABEND S001. For a server that is plainly the
right trade; it is what turns #117's corrupted PUT from a dead address space
into a recoverable condition.

The other side of it is that feof() is deliberately not set, so a loud
failure becomes a silent one wherever a read loop treats the resulting
NULL/0 as end of data. FTPD had three such places. None of them is a new
defect — each used to end in S001, and would now have ended in a wrong answer:

Before Now
Read error on DD:FTPDPRM S001 FTPD009E, start abandoned
Read error during RETR S001 451, with the byte count actually sent
Write error during STOR S001 451, and the transfer stops at the failing record

Automation or a runbook that reads a 250 as "the transfer is complete" now
has a second outcome to know about
, and it is the one that matters: before
this release a bad track mid-RETR ended the send loop exactly like the end of
the data set and fell through to 250 Transfer completed successfully. The
client stored a fragment and was told it had the whole data set, with nothing on
the wire to tell the difference. STOR was the same in the other direction, and
worse in one respect: after the first failed write it kept issuing PUTs against
a DCB that had already failed, then reported 250 over an incomplete data set
on DASD.

The parmlib case is quieter and reaches further. A read error stopped the
fgets() loop indistinguishably from a clean end of member, kept whatever had
been parsed, left the rest at its default, and still reported a successful load
— so FTPD came up looking healthy with DASD volumes missing from its list and
SITE/JES settings silently back at their defaults. It now abandons the start:

FTPD009E PARMLIB (DD:FTPDPRM) READ FAILED -- CONFIGURATION INCOMPLETE,
         SHUTTING DOWN

Measured on MVS, with the error vector libc370's own probe established — the
same data set behind two DDs that disagree about BLKSIZE, so the first READ
meets a 3120-byte block with an 80-byte buffer. TSTPRM runs it on both the
batch and the TSO leg:

guard removed   round 2: rc=0 errno=5 port=2121 maxsess=10
shipped         round 2: rc=4 errno=5 port=2121 maxsess=10

The first line is the defect in one row. Both rounds read the same member;
SRVPORT is its first keyword and comes back parsed, MAXSESSIONS is its
second and comes back at the default 10 rather than the 17 that is in the file.
A truncated parse, reported as a successful load. The shipped build refuses it.

The RETR and STOR guards are not measured. The lying DD cannot be
injected through the fopen() those paths use, and building a DSCB that lies
about its own blocks would be guesswork. They are a reviewed ferror() check,
and this is the honest description of them.

Deliberately not fixed here

Both recorded in #119:

  • FTPD's log goes to STDOUT through printf(). With the SYNAD in place, an I/O
    error on SYSOUT now leaves the started task running with logging silently
    dead
    rather than abending. Fixing that properly needs the fail-fast /
    clearerr() decision that is still open as libc370#149.
  • In STOR, a negative return from the data connection also still ends in
    250. Pre-existing, unrelated to this relink, and a different fix.

What is running

FTPD005I stamps the library the module was actually linked against, so a
deploy that did not take cannot hide:

FTPD000I FTPD 1.0.2 (6CCB125) STARTING
FTPD005I LIBC370 1.0.4 (1CA9E04)

If that second line still says 1.0.3, the STC is running the old module —
the point of the relink is exactly that line.

Installing

Download ftpd-1.0.2-dist.zip (or .tar.gz); the guide and every file you
need are inside, and the procedure is unchanged from 1.0.1:

README.md the installation guide
ftpd-1.0.2-load.xmit the FTPD load module
ftpd-1.0.2-samplib.xmit the STC procedure and the configuration pattern
ftpd-1.0.2-alloc.jcl allocate the datasets — run once
ftpd-1.0.2-inst.jcl receive, apply, accept

Upload the two XMITs in binary, submit the allocation job once, then the
install job — the only thing to edit is the two dataset names you uploaded to.
The SYSMOD travels inline in the job, so there is no third file to transfer.
Everything lands under FTPD.V1R0M2.*, so it does not collide with an existing
V1R0M0 or V1R0M1 installation. FMID is TFTP100.

The two caveats from 1.0.1 still apply to the shipped material:

  • On TK5, SYS2.PARMLIB does not exist — put FTPDPRM0 in SYS1.PARMLIB
    and start with /S FTPD,D='SYS1.PARMLIB'.
  • The two JES DDs in the procedure, HASPCKPT and HASPACE1, carry the unit
    and volume of the system FTPD was built on. Point them at your own JES2
    checkpoint and spool, or delete both if you do not want the JES interface.
    Both are required together: with HASPACE1 missing, every JES command
    answers 451.

Upgrading from 1.0.1

The same shape as 1.0.0 → 1.0.1, for the same reason. An FMID names a
functional level, one per minor release, so 1.0.2 ships as TFTP100 again —
the id 1.0.1 already applied and accepted, and SMP will not apply a function
SYSMOD that is already applied.

Keeping it SMP-managed Free the FMID first with the UCLIN job in doc/uninstall.md, then run the 1.0.2 allocation and install jobs as for a fresh install
Without SMP Receive ftpd-1.0.2-load.xmit into a library and point the procedure's STEPLIB at it

Run only the UCLIN step of that page, not its DELETE step. The libraries
are versioned per patch — FTPD.V1R0M2.* here — so the new install never
touches 1.0.1's, and the DELETE names the old release's datasets. Scratching
them is a choice about the predecessor, not a step of this upgrade; after the
UCLIN they are simply orphans with no inventory record, and leaving them is
fine.

Check the inventory before and after with the zone operand — it is mandatory,
LIST SYSMODS . is SMP/E syntax and gets HMA2033:

LIST CDS  SYSMOD(TFTP100) .
LIST ACDS SYSMOD(TFTP100) .

RC 04 with an empty list means the id is free.

Either way, copy the 1.0.2 procedure from the new SAMPLIB into your PROCLIB —
it is what points STEPLIB at the new LINKLIB. On the APF route, remember
the list is read at IPL only: until FTPD.V1R0M2.LINKLIB is in IEAAPF00 and
the system has IPLed, FTPD falls back to SVC 244 rather than failing. Your
FTPDPRM0 and your RAKF definitions are unaffected — no configuration keyword
changed in this release.

A correction shipped with it. doc/uninstall.md said <vrm> was V1R0M0
for all of 1.0.x. It is not: the qualifier carries the patch level, and that
page is exactly where a patch upgrade is sent. Following the old sentence
scratched the predecessor's libraries and left the live insta...

Read more

v1.0.1

Choose a tag to compare

@github-actions github-actions released this 23 Aug 15:12

FTPD 1.0.1 is a patch release: six defects fixed since 1.0.0, two new
configuration keywords and one new startup message. Nothing about what FTPD
reaches has changed and the install procedure is the one 1.0.0 shipped — but
1.0.1 does not install over 1.0.0, see Upgrading from 1.0.0 below.

If FTPD.V1R0M0.LINKLIB is in your APF list, 1.0.0 cannot start at all, and
this release is the fix. If FTPD authorizes itself through SVC 244 — the stock
TK4-/TK5 case — you were never exposed to that one, and the rest of this release
is about a start that gives up honestly and a transfer that stops when the
client does.

The S0C4 at startup from an APF-authorized library (#101)

FTPD is link-edited AC(1). Fetched from an APF-authorized library the job
step is already authorized when program fetch runs, so MVS obtains the job pack
area in subpool 252, key 0 — authorized code must not be patchable by
problem-key code. The started task runs problem state key 8, so any store into
the module's own storage takes a protection exception.

FTPD's first such store was the first statement of main(), which is why the
entire symptom is one line:

12.34.30 STC 1124  IEF450I FTPD FTPD - ABEND S0C4 U0000 - TIME=12.34.30

No FTPD000I, no banner — the abend lands ahead of the first WTO. The
self-authorizing route hides this rather than avoiding it: SVC 244 sets
JSCBAUTH after the fetch and cannot relabel storage that is already
allocated, so the module stays key 8 and the very same store goes through
unnoticed. That is why a stock TK4-/TK5 never saw it.

The state moved out of the module. ftpd_server — written once, never read — is
deleted; the log level and the whole trace ring, which was the larger half, now
live in main()'s storage, hence key 8, published through the C runtime's
process anchor. The eleven-function logging API is unchanged, so all 84 call
sites are untouched. The translate tables are const now.
tools/check-module-data.py rejects mutable file-scope data and function-local
statics in the AC(1) module, as its own CI job, so this cannot come back
quietly.

Verified on the route that killed 1.0.0. Started from an APF-authorized
LINKLIB, 1.0.1 comes up and runs normally — the same start that gave 1.0.0 the
IEF450I above and nothing else. Alongside that: the fix checked at assembler
level (cc370 -S before and after), and tools/check-module-data.py clean on
the fixed tree while still reporting all 13 pre-fix sites on the commit before
it.

The original was found in UFSD by Rob Prins,
who did most of the diagnosis himself
(ufsd#64, fixed in UFSD 1.2.1).
FTPD's own sites came out of running ufsd's checker across the ecosystem
afterwards — FTPD was the worst affected of the projects checked, because its
first store happens before its first message. Thank you, Rob.

A start that reported READY without listening (#111, #109, #113)

Three defects, one story: what FTPD does when the bind does not succeed.

main() waited five seconds for the socket thread to publish the listener and
then issued FTPD001I READY whether or not it ever appeared. The loop was a
timeout, not a check. Measured on a server in that state, F FTPD,SESSIONS
answered FTPD015I ACTIVE SESSIONS: 0 / 10 — a dead address space reading
exactly like a quiet one.

The wait now ends on whichever of the two things actually happened — the
listener was published, or the socket thread gave up — and an FTPD that cannot
listen ends rather than sitting there. No console command retries a bind, so
/P and /S was the only way back either way; what changes is that the step
ends COND CODE 0008 instead of 0000, the one part of a start an automation
product can read without parsing the console.

Measured with a throwaway STC bound to an address the host does not have, and
with no BINDTRIES line in its config member, so these are the shipped
defaults:

04.17.13 FTPD051E BIND() FAILED ON 10.99.99.99 PORT 2122, ERRNO=49
04.17.13 FTPD051I RETRYING BIND IN 5S (1 OF 3)
04.17.18 FTPD051I RETRYING BIND IN 5S (2 OF 3)
04.17.23 FTPD051I RETRYING BIND IN 5S (3 OF 3)
04.17.28 FTPD051E BIND() STILL FAILING AFTER 3 TRIES, ERRNO=49
04.17.28 FTPD056E FTPD IS NOT LISTENING ON PORT 2122, THIS INSTANCE ENDS
         IEF142I FTPDT FTPDT - STEP WAS EXECUTED - COND CODE 0008

Because giving up ends the address space, how long FTPD waits first became a
site decision: BINDTRIES and BINDWAIT, both 1–100, defaulting to 3 and 5
— fifteen seconds from /S to a verdict, and FTPD057I reports both in the
CONFIG dump. What gets retried changed too: EADDRNOTAVAIL is retried now as
well as EADDRINUSE. The first is the one that matters at IPL, where FTPD can
start before the stack has an address and where raising BINDTRIES is the
answer. Everything else is refused immediately.

The stale-port sweep — the recovery that closes sockets a dead FTPD left
holding the port — is fixed with it. It used to run in front of every bind,
where the only thing it could do was close a socket belonging to something
alive, and it stopped at the first match, although a dead FTPD leaves its
listener and every connection it had accepted, each one holding the port. It
now runs only when a bind has actually failed with EADDRINUSE, closes every
match, and rebinds after a single 2 second settle. FTPD051E moved behind it,
so a start that recovers this way shows the FTPD053I lines and FTPD054I with
nothing alarming in front of them.

Worth knowing if something other than FTPD may hold that port. The socket
table lives in Hercules and is global to the emulator — which is why a dead
STC's leftovers are reachable at all — and it exposes no owner. What bounds the
sweep is the FTPD.PORT.nnnnn ENQ, the EADDRINUSE, and the address comparison
(SRVBIND can name one interface, and a leftover socket on another cannot be
what made our bind fail). Past that it is indiscriminate: a live foreign
listener
on FTPD's port does lose its socket.

Transfers, and noticing that the client is gone

RETR of an MVS data set read on to EOF after the client had gone (#106).
The three send loops ignored what the data connection told them, and the
completion path replied 250 Transfer completed successfully unconditionally —
so an aborted transfer looked like a good one to the client and in the log, and
the byte counters included records that never left the machine. Measured on a
2693-track (~51 MB) data set, client stops reading and drops the connection:

before after
session active after client vanished ~90 s 0.7 s
control reply 250 Transfer completed successfully. 426 Transfer aborted: data connection lost after 228672 bytes.
remaining data set read to EOF from DASD not read

The control connection survives an aborted transfer — QUIT still answers 221
— which is correct: a dead data connection is not a dead session. Normal
transfers still report 250. This covers MVS data sets; the UFS path stops
correctly on a send error but still replies 226, which is the same flaw and is
not fixed here.

A failed write on the control connection was discarded (#104). The four
places that actually write to the control socket now funnel through one sink,
which completes short writes, logs the failure once per session — a dead
control connection otherwise produces one message per queued reply — and moves
the session to closing. That flag is deliberately separate from the session
state, because the 220 greeting used to overwrite the state right after a failed
reply: the guarantee cannot rest on ~200 handlers behaving. The trace line moved
behind the success check, so a reply that never reached the client no longer
appears in the trace as if it had.

SITE SUBMIT read the jobid from freed storage (#102). The post-close read
fetched it from the VSFILE that had already been released; jesircl2() hands it
out legally between ENDREQ and close.

New: FTPD008I says which route authorized the started task

Neither route announced itself, and the route is exactly what decides whether
FTPD's own module storage is writable. Startup now reports it on the line after
the version banner:

FTPD008I AUTHORIZED BY LIBRARY (MODULE KEY 0)     APF list entry
FTPD008I AUTHORIZED BY SVC (MODULE KEY 8)         SVC 244, from RAKF

Neither line is a warning — both routes end in an authorized started task, and
which one you want is a site decision. The key is inferred from the route rather
than measured: an authorized job step has its module fetched key 0, an
unauthorized one key 8, and SVC 244 arrives too late to change either.

Installing

Download ftpd-1.0.1-dist.zip (or .tar.gz); the guide and every file you
need are inside, and the procedure is unchanged from 1.0.0:

README.md the installation guide
ftpd-1.0.1-load.xmit the FTPD load module
ftpd-1.0.1-samplib.xmit the STC procedure and the configuration pattern
ftpd-1.0.1-alloc.jcl allocate the datasets — run once
ftpd-1.0.1-inst.jcl receive, apply, accept

Upload the two XMITs in binary, submit the allocation job once, then the
install job — the only thing to edit is the two dataset names you uploaded to.
The SYSMOD travels inline in the job, so there is no third file to transfer.
Everything lands under FTPD.V1R0M1.*, so it does not collide with an existing
V1R0M0 installation. FMID is TFTP100.

Two things in the shipped material are still not ready to run as they are:

  • On TK5, SYS2.PARMLIB does not exist — put FTPDPRM0 in SYS1.PARMLIB
    and start with /S FTPD,D='SYS1.PARMLIB'.
  • The two JES DDs in the procedure, HASPCKPT and HASPACE1, carry the unit
    and volume of the system FTPD was built on. Point them at your own JES2
    checkpoint and spoo...
Read more

v1.0.0

Choose a tag to compare

@github-actions github-actions released this 14 Aug 07:46

FTPD 1.0.0 is the first release of the standalone FTP server for MVS 3.8j. It
installs through SMP 4 — the SMP that ships with MVS 3.8j, not SMP/E — so
the system keeps a record of what is installed, and there is a defined way back
out again.

Installing

Download ftpd-1.0.0-dist.zip (or .tar.gz). It contains everything you
need and a step-by-step guide:

README.md the installation guide
ftpd-1.0.0-load.xmit the FTPD load module
ftpd-1.0.0-samplib.xmit the STC procedure and the configuration pattern
ftpd-1.0.0-alloc.jcl allocate the datasets — run once
ftpd-1.0.0-inst.jcl receive, apply, accept

Upload the two XMITs in binary, submit the allocation job once, then the
install job — the only thing to edit is the two dataset names you uploaded to.
The SYSMOD travels inline in the job, so there is no third file to transfer.

Everything lands under FTPD.V1R0M0.*. Copying the procedure and the
configuration pattern into your PROCLIB and PARMLIB stays a manual step, by
design: the product owns the patterns, your system owns the copies, and an
update that silently replaced a running procedure would be worse than one that
leaves it alone. The guide says which member goes where.

Two things in the shipped material are not ready to run as they are:

  • On TK5, SYS2.PARMLIB does not exist — put FTPDPRM0 in SYS1.PARMLIB
    and start with /S FTPD,D='SYS1.PARMLIB'.
  • The two JES DDs in the procedure, HASPCKPT and HASPACE1, carry the unit
    and volume of the system FTPD was built on. Point them at your own JES2
    checkpoint and spool, or delete both if you do not want the JES interface.
    Both are required together: with HASPACE1 missing, every JES command
    answers 451.

FMID is TFTP100.

Correction — removing FTPD again

The README.md inside the archive is wrong about this, and it cannot be
fixed in a file already downloaded.
It says to remove FTPD with RESTORE
followed by REJECT. Measured after this release went out: neither works.
The install job accepts the FMID in the same run as the APPLY, and an accepted
function SYSMOD refuses RESTORE because it was accepted, then refuses
REJECT because accepting removed the control statements REJECT works from.

A UCLIN job does work. The procedure, with the job and the messages to check,
is at
doc/uninstall.md.

Nothing about installing is affected — only taking it off again.

What FTPD reaches

Three namespaces, selected per session, on port 2121 by default:

  • MVS data sets — sequential data sets and PDS members, with
    z/OS-compatible SITE keywords for RECFM, LRECL, BLKSIZE, SPACE,
    UNIT and VOLUME.
  • JES — quote site filetype=jes: submit a job, list your jobs, retrieve
    and purge spool output.
  • UFS — the UFSD filesystem, reached by cd to a UFS path.

UFSD is optional. libufs is linked statically and FTPD asks UFSD for a
session only when a client actually uses a UFS path, so on a system without the
UFSD started task those commands answer 550 UFS service not available and
everything else works normally. The SYSMOD declares no prerequisite — install
FTPD now and add UFSD later if
you want it.

RAKF is not optional

FTPD has no user database of its own. Every USER/PASS is verified by RAKF
(RACINIT, SVC 244), and every data set access is checked against the RAKF
DATASET class under the logged-in user's identity, not the server's. On a
system without RAKF nobody can log in, and no configuration option turns that
off. There is no INSECURE mode; the one that existed during development was
removed on purpose.

The setup is one user, one group and an optional FTPAUTH FACILITY profile —
step 8 of the guide, with the full reference in
FTPD_RAKF_SETUP.md.

Next: 1.1.0 is about authorization

1.0.0 authenticates every session and checks every data set access, but the
identity model around those checks still has known gaps, and that is what the
next release is for. The open work:

  • The resting identity is too powerful (#97).
    Between sessions the STC sits on STC/STCGROUP, which holds ALTER on every
    data set. The recovery path can transiently put a session on that identity,
    which is fail-closed only while it is least-privilege — so FTPD needs to log
    on to a low-privilege userid at startup instead.
  • JES access has no authorization model at all
    (#90). JESOWNER is a
    client-set display filter, not a control.
  • The ACEE switch is not fully serialized
    (#64). ASXBSENV is
    address-space-wide while sessions run as concurrent subtasks.

Until those are closed, keep the FTPD identity scoped to what the started task
itself needs — never give it broad data set authority. The reasoning is spelled
out as a security invariant in the RAKF guide.


Full Changelog: https://github.com/mvslovers/ftpd/commits/v1.0.0