Skip to content

Read every address parameter through one parser (#76) - #77

Merged
mgrossmann merged 2 commits into
mainfrom
fix/address-parsing-76
Aug 6, 2026
Merged

Read every address parameter through one parser (#76)#77
mgrossmann merged 2 commits into
mainfrom
fix/address-parsing-76

Conversation

@mgrossmann

Copy link
Copy Markdown
Contributor

Closes #76

Was war

SRVIP, PASVADR und PASVBIND wurden auf drei Arten gelesen, mit drei
Vorstellungen davon, was ein unlesbarer Wert bedeutet. SRVIP war die
schlechteste: der Wert wurde unverändert kopiert, die Kommaform, die die
beiden anderen akzeptieren, ließ sscanf("%u.%u.%u.%u") eine statt vier
Konversionen melden, der else-Zweig tat nichts, und s_addr behielt die 0
aus dem memset. SRVIP=127,0,0,1 band jedes Interface des Hosts, ohne ein
Wort — genau das Gegenteil dessen, was der Operator wollte, und genau der
Aufbau, den das Sample für den Betrieb hinter einem TLS-Proxy (#59)
empfiehlt. Ein Tippfehler (SRVIP=127.0.0, SRVIP=localhost) war ebenso
still.

Was jetzt

ftpd_adr_parse() (src/ftpd#adr.c) liest alle drei: ANY, dotted quad
oder Kommaform, vier Oktette, 0-255, nichts dahinter. Nebenbei schließt das
die Löcher, die die sscanf-Variante auch für PASVBIND offen ließ:
999.1.2.3 und 1.2.3.4junk galten als vier Konversionen, und ein zu
langer Wert wurde in eine gültige andere Adresse abgeschnitten
(192.168.100.2000 -> 192.168.100.200). Ein abgelehnter Wert wird mit
einer Meldung abgewiesen, die den einspringenden Default benennt; der
Listener startet lieber nicht, als auf INADDR_ANY durchzufallen.

  • SRVBIND ist der neue kanonische Name. SRVIP wird weiter gelesen,
    und der CONFIG-Dump schreibt (set as SRVIP) dazu, wenn die alte
    Schreibweise gegriffen hat.
  • PASVADR behält seinen Namen. Es ist die annoncierte Adresse, kein
    Bind — die Vereinheitlichung der Schreibweise mit den beiden Bind-Keys
    würde diesen echten Unterschied verwischen.
  • PASVADR=ANY ist der neue Default (statt des literalen 127.0.0.1,
    das nur für einen lokalen Client je richtig sein konnte) und heißt: die
    Adresse, über die dieser Client die Kontrollverbindung erreicht hat. Damit
    ist der bisher stille getsockname()-Fallback der benannte Default statt
    einer dritten Fehlerbehandlung — sichtbar im CONFIG-Dump.
  • FTPD054I und FTPD051E nennen die Adresse, nicht nur den Port.
  • Der getsockname()-RC wird geprüft (zweiter Commit): der Aufruf liegt ab
    jetzt auf jedem PASV einer Standardkonfiguration, nicht mehr nur im
    Fehlerfall. Schlägt er fehl, antwortet PASV 425, statt den Client an den
    Inhalt eines uninitialisierten sockaddr zu schicken.

Test

test/tstadr.c, 29 Assertions, dual (host + MVS) — ftpd#adr.c ist frei
von ftpd.h und MVS-Services. Gegen die ersetzte Parserlogik fallen davon
10 um (plus ein Segfault bei NULL); mit dem neuen Parser sind
make test-host und der Cross-Build grün.

make test-mvs wurde nicht gefahren — das Modul ist gebaut, nicht auf MVS
ausgeführt. Getestet ist ftpd_adr_parse() selbst; das Routing in
parse_keyvalue() deckt kein Test ab.

Vor dem Deploy zu erledigen

Ein PARMLIB-Member, dessen SRVIP bisher ignoriert wurde, wird mit diesem
Stand wirksam. Auf dem Testsystem steht in SYS1.PARMLIB(FTPDPRM0)
SRVIP=123,0,0,1 — das parst jetzt sauber, FTPD bindet 123.0.0.1, die der
Stack nicht hat, meldet FTPD051E bind() failed on 123.0.0.1 port 2121 und
hört nicht zu. Das Member muss im selben Wartungsfenster mit umgestellt
werden (SRVBIND=ANY oder die tatsächliche Adresse).

https://claude.ai/code/session_01Acc7qm2TkDpFjsyhwunKJk

SRVIP, PASVADR and PASVBIND were parsed three different ways, with three
different ideas of what an unreadable value means.  SRVIP was the worst of
them: the value was copied verbatim, so the comma form the other two accept
made sscanf("%u.%u.%u.%u") report one conversion instead of four, the else
branch did nothing, and s_addr kept the 0 from the memset.  SRVIP=127,0,0,1
bound every address of the host without a word -- the exact opposite of what
the operator asked for, and the setup the sample recommends for running
behind a TLS proxy (#59).  A typo did the same: SRVIP=127.0.0 or
SRVIP=localhost were just as silent.

ftpd_adr_parse() now reads all three: ANY, dotted quad or comma form, four
octets, 0-255, nothing trailing.  It also closes holes the sscanf version
left open for PASVBIND -- 999.1.2.3 passed as four conversions, 1.2.3.4junk
too, and a value longer than the buffer was truncated into a valid but
different address (192.168.100.2000 -> 192.168.100.200).  Rejected values
are refused with a message naming the default that takes their place, and
the listener refuses to start rather than fall through to INADDR_ANY.

SRVBIND is the new name; SRVIP is still read and the CONFIG dump says when
it was the spelling that took effect.  PASVADR keeps its name -- it is the
advertised address, not a bind address, and that difference is worth
keeping visible.

Its silent getsockname() fallback becomes the named default instead: with
PASVADR=ANY (now the default, replacing a literal 127.0.0.1 that could only
ever be right for a local client) the client is sent to the address it
reached the control connection on.  The CONFIG dump spells that out, as
does FTPD054I for the address FTPD listens on.

test/tstadr.c pins the parser down on the host and on MVS: 29 assertions,
of which 10 fail against the parsing this replaces.

NOTE for existing installations: a PARMLIB member whose SRVIP was ignored
until now takes effect with this change.  SRVIP=123,0,0,1 no longer binds
every address, it binds 123.0.0.1 -- and if the stack does not have that
address, FTPD says so (FTPD051E) and does not listen.

Closes #76

Claude-Session: https://claude.ai/code/session_01Acc7qm2TkDpFjsyhwunKJk
PASVADR=ANY makes the control connection's local address the one FTPD puts
in the 227 reply, so this call is now on every PASV of a stock configuration
rather than only on a misconfigured one.  Unchecked, a failure left the
uninitialised sockaddr in place and sent the client to whatever it held.
Fail the command instead -- PASV answers 425, which is the honest answer.
@mgrossmann
mgrossmann merged commit da26c36 into main Aug 6, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Adressparameter uneinheitlich: SRVIP frisst keine Kommas (und bindet dann still ANY), Benennung SRVIP vs PASVADR

1 participant