Feature request, not a patch — the thing that is missing needs a design, and
part of it does not exist on the platform at all.
Where we are
Measured on mvsdev as MVSCE02 (group USER, no OPERATIONS), against
a5b1256:
> SITE FILETYPE=JES
> LIST -> 1 line (header only) — own jobs, correct default
> SITE JESOWNER=* -> 200 SITE command was accepted
> LIST -> 160 lines, 159 of them jobs owned by someone else
> RETR STC00563.1 -> 250, 1028 bytes
SYSTEM.FTPD.STC00563.D0000002.JESMSGLG
An unprivileged FTP user read the JES2 job log of the FTPD started task. The
sequence is one SITE command; no error, no log entry, no denial.
Two code facts behind it:
sess->jes_owner is the only scoping there is, it defaults to the session
user (good), and the client sets it (not good). job_matches_owner()
(ftpd#jes.c:372) treats * as "match all".
find_job() (ftpd#jes.c:628) resolves a JOBID with FILTER_JOBID and no
owner scoping whatsoever. It backs both the spool-retrieve path and the
delete path, so the filter is not consulted at the point of access at all —
only when building a listing.
Not tested, because it is destructive: whether DELE <jobid> can purge
another user's job. After find_job() the only remaining gate is whatever
jescanj() itself refuses (the CANJ_ICAN branch suggests JES2 declines
some cases). Worth establishing before this is designed.
Why this is a feature and not a bug fix
There is nothing to delegate to. RAKF has no JESSPOOL class — the profile set
on a stock system is DASDVOL, DATASET, FACILITY, TAPEVOL, TERMINAL — and
MVS 3.8j/JES2 do not protect spool by owner. So FTPD is not failing to call an
existing check; it is the only component in a position to have a policy, and
it currently has none beyond a default that the client can switch off.
That makes this a question of what policy to invent, which is exactly what
z/OS answers with JESINTERFACELEVEL:
- Level 1 — a session may only reach jobs whose name is its userid plus at
most one character. Crude, needs no security product, and would work here
today.
- Level 2 — access is delegated to SAF profiles in class JESSPOOL
(nodeid.userid.jobname.jobid.dsid). Needs a class RAKF does not have.
Sketch, to be argued rather than adopted
JESOWNER may only ever narrow: the effective owner filter stays the
session user; a different value is refused rather than honoured.
- Enforce at the access, not in the listing —
find_job()'s callers must
check job->owner before RETR or DELE touches anything.
- An explicit widening path for operators:
racf_auth() against a FACILITY
resource (FTPD.JESSPOOL or similar), default deny. This is the piece that
substitutes for a JESSPOOL class we do not have.
- Then
JESINTERFACELEVEL means something again, and the inert FTPDPRM key
of that name (and cfg->jes_level / sess->jes_level, written but never
read) becomes the switch between 1 and 2 instead of dead weight.
Until then, ftpd no longer pretends: #89 makes SITE JESINTERFACELEVEL=n
answer "Unrecognized parameter", as z/OS does.
Operational note for anyone reading this before it is fixed
Any authenticated FTP user can read any job's spool on the system. On a
closed development LPAR that may be acceptable; anywhere else it is worth
knowing before FTPD is reachable from a network that matters.
Feature request, not a patch — the thing that is missing needs a design, and
part of it does not exist on the platform at all.
Where we are
Measured on mvsdev as
MVSCE02(group USER, no OPERATIONS), againsta5b1256:An unprivileged FTP user read the JES2 job log of the FTPD started task. The
sequence is one SITE command; no error, no log entry, no denial.
Two code facts behind it:
sess->jes_owneris the only scoping there is, it defaults to the sessionuser (good), and the client sets it (not good).
job_matches_owner()(
ftpd#jes.c:372) treats*as "match all".find_job()(ftpd#jes.c:628) resolves a JOBID withFILTER_JOBIDand noowner scoping whatsoever. It backs both the spool-retrieve path and the
delete path, so the filter is not consulted at the point of access at all —
only when building a listing.
Not tested, because it is destructive: whether
DELE <jobid>can purgeanother user's job. After
find_job()the only remaining gate is whateverjescanj()itself refuses (theCANJ_ICANbranch suggests JES2 declinessome cases). Worth establishing before this is designed.
Why this is a feature and not a bug fix
There is nothing to delegate to. RAKF has no JESSPOOL class — the profile set
on a stock system is DASDVOL, DATASET, FACILITY, TAPEVOL, TERMINAL — and
MVS 3.8j/JES2 do not protect spool by owner. So FTPD is not failing to call an
existing check; it is the only component in a position to have a policy, and
it currently has none beyond a default that the client can switch off.
That makes this a question of what policy to invent, which is exactly what
z/OS answers with JESINTERFACELEVEL:
most one character. Crude, needs no security product, and would work here
today.
(
nodeid.userid.jobname.jobid.dsid). Needs a class RAKF does not have.Sketch, to be argued rather than adopted
JESOWNERmay only ever narrow: the effective owner filter stays thesession user; a different value is refused rather than honoured.
find_job()'s callers mustcheck
job->ownerbefore RETR or DELE touches anything.racf_auth()against a FACILITYresource (
FTPD.JESSPOOLor similar), default deny. This is the piece thatsubstitutes for a JESSPOOL class we do not have.
JESINTERFACELEVELmeans something again, and the inertFTPDPRMkeyof that name (and
cfg->jes_level/sess->jes_level, written but neverread) becomes the switch between 1 and 2 instead of dead weight.
Until then, ftpd no longer pretends: #89 makes
SITE JESINTERFACELEVEL=nanswer "Unrecognized parameter", as z/OS does.
Operational note for anyone reading this before it is fixed
Any authenticated FTP user can read any job's spool on the system. On a
closed development LPAR that may be acceptable; anywhere else it is worth
knowing before FTPD is reachable from a network that matters.