. "$BST_LIB" # tcp-wrappers 7.6 -- host-based access control for network services. Two halves # ship and both are covered here: the five tools in /usr/sbin (tcpd, tcpdchk, # tcpdmatch, try-from, safe_finger) with /etc/hosts.allow and /etc/hosts.deny, # and libwrap.a + tcpd.h for daemons that link the access-control library. # # Neither half is what oe-core's recipe produces unaided. Debian's # 13_shlib_weaksym.patch repoints all five programs at a shared libwrap, which # cannot link on this static-only arm, so the port links them against libwrap.a # instead; and hosts_access.c did not compile at all until the port fixed a # 1997 struct-name typo that a C23-default clang turns into a hard error. So # what this suite has to keep honest is that the tools really work and the # archive really carries the compiled library. # # THE ACCESS-CONTROL LANGUAGE IS EXERCISED THROUGH tcpdchk -v, NOT tcpdmatch. # That is deliberate and is the one sharp edge on this image: tcpdmatch traps # (SIGILL, rc 132) the moment a rule MATCHES -- see the tcpdmatch cases at the # bottom. tcpdchk -v evaluates the same rules through the same libwrap matching # code and prints the same "access: granted/denied" verdicts without going # through hosts_access(), so it can assert the rule semantics that tcpdmatch # cannot reach here. # # Both tools need an inetd.conf to resolve a daemon name, and this image ships # xinetd rather than inetd, so there is none -- every case passes -i with its # own. The daemon named in it is "tcpdchk" because inet_chk() checks the server # argument really exists under /usr/sbin, and naming a real file keeps the # expected output free of "not found" noise. -d makes both tools read # ./hosts.allow and ./hosts.deny from the working directory, so nothing here # touches the image's own (deliberately empty) tables. require tcpdmatch tcp-wrappers/present tmp=${TMPDIR:-/tmp}/bst-tcpw.$$ mkdir -p "$tmp" || exit 0 IC=$tmp/inetd.conf printf 'tcpdchk stream tcp nowait root /usr/sbin/tcpd tcpdchk\n' > "$IC" # ---- packaging: the five tools, the tables, dpkg registration --------------- for t in tcpd tcpdchk tcpdmatch try-from safe_finger; do check "tcp-wrappers/bin-$t" test -x "/usr/sbin/$t" done check tcp-wrappers/hosts-allow-present test -f /etc/hosts.allow check tcp-wrappers/hosts-deny-present test -f /etc/hosts.deny check_out tcp-wrappers/pkg-installed "/etc/hosts.allow" \ cat /var/lib/dpkg/info/tcp-wrappers.list # ---- the access-control language, via tcpdchk -v --------------------------- # # One rule that must be granted and one that must be denied, in the two tables, # so a verdict that came out of the wrong table cannot pass both. cd "$tmp" || exit 0 printf 'tcpdchk: 10.0.0.1\n' > hosts.allow printf 'ALL: ALL\n' > hosts.deny check tcp-wrappers/tcpdchk-clean-rc tcpdchk -d -i "$IC" check_out tcp-wrappers/tcpdchk-rule-listed ">>> Rule hosts.allow line 1:" tcpdchk -v -d -i "$IC" check_out tcp-wrappers/tcpdchk-daemon-list "daemons: tcpdchk" tcpdchk -v -d -i "$IC" check_out tcp-wrappers/tcpdchk-client-list "clients: 10.0.0.1" tcpdchk -v -d -i "$IC" check_out tcp-wrappers/tcpdchk-verdict-allow "access: granted" tcpdchk -v -d -i "$IC" check_out tcp-wrappers/tcpdchk-verdict-deny "access: denied" tcpdchk -v -d -i "$IC" # -a reports a rule that ends without an explicit ALLOW/DENY option. check_out tcp-wrappers/tcpdchk-implicit-allow 'implicit "allow" at end of rule' \ tcpdchk -a -d -i "$IC" # A rule with no ":" between daemon and client list is the canonical syntax # error; tcpdchk reports it and still exits 0, so this is asserted on the text. printf 'tcpdchk 10.0.0.1\n' > hosts.allow check_out tcp-wrappers/tcpdchk-syntax-error 'missing ":" separator' tcpdchk -d -i "$IC" # ---- tcpdmatch: the paths that work on this image --------------------------- # # With both tables empty no rule matches, which is the library's documented # default-permit, and tcpdmatch completes normally -- so its argument parsing, # inetd.conf handling, request expansion and verdict printing are all real. : > hosts.allow : > hosts.deny check_out tcp-wrappers/tcpdmatch-client-expand "client: address 10.0.0.1" \ tcpdmatch -d -i "$IC" tcpdchk 10.0.0.1 check_out tcp-wrappers/tcpdmatch-daemon-expand "server: process tcpdchk" \ tcpdmatch -d -i "$IC" tcpdchk 10.0.0.1 check_out tcp-wrappers/tcpdmatch-default-permit "access: granted" \ tcpdmatch -d -i "$IC" tcpdchk 10.0.0.1 # A rule present but not matching this (daemon, client) must also come out # granted, and must not be confused with the empty-table case above. printf 'tcpdchk: 10.9.9.9\n' > hosts.allow check_out tcp-wrappers/tcpdmatch-no-match "access: granted" \ tcpdmatch -d -i "$IC" tcpdchk 10.0.0.1 # ---- tcpdmatch on a MATCHING rule: blocked on the target ------------------- # # Every rule that matches kills tcpdmatch with a wasm trap ("memory access out # of bounds" -> SIGILL, rc 132) before it can print its verdict. The trigger is # tcpdmatch's own hosts_access_verbose = 2, the only thing that makes # table_match() report a match through syslog() -> vsyslog() -> percent_m(), # and percent_m is where the trap lands. tcpdchk never calls hosts_access() and # is unaffected, which is why the rule semantics above are asserted through it. # # Not a defect this port can fix: it is not the static link (tcpdchk resolves # percent_m from the same libwrap.a and is fine), not the log buffers, not the # va_list handoff, and not the setjmp frame -- each was built and booted and # ruled out, and standalone reproducers of the va_list and setjmp shapes both # pass on this image. # # So this case RUNS the failing command and reports what it finds: skip while # the trap is still there, ok the moment it stops -- so the suite tells the # truth today and turns green by itself when the platform fix lands, without # anyone having to remember to come back and re-enable it. printf 'tcpdchk: 10.0.0.1\n' > hosts.allow out=$(tcpdmatch -d -i "$IC" tcpdchk 10.0.0.1 2>&1); rc=$? case "$out" in *"access: granted"*) hit=1 ;; *) hit=0 ;; esac if [ "$rc" -eq 0 ] && [ "$hit" -eq 1 ]; then bst_ok tcp-wrappers/tcpdmatch-matching-rule elif [ "$rc" -eq 132 ]; then bst_skip tcp-wrappers/tcpdmatch-matching-rule \ "blocked: tcpdmatch traps (rc 132) on any matching rule; verbose match report via percent_m" else bst_notok tcp-wrappers/tcpdmatch-matching-rule "rc=$rc $(_bst_1line "$out")" fi # ---- the other two tools --------------------------------------------------- # # try-from and tcpd are both meant to be exec'd by inetd with an accepted socket # on their standard descriptors. Run from a shell there is no socket, and both # say so and exit 0 rather than misbehaving -- which is the whole of what can be # asserted about them on a machine that runs no inetd. check_out tcp-wrappers/try-from-runs "server process (%d): try-from" try-from check_out tcp-wrappers/try-from-no-socket "can't get client address" try-from check_rc tcp-wrappers/tcpd-bare-rc 0 tcpd # ---- the library half: -dev + -staticdev payload --------------------------- # # Static-only image and no on-target compiler, so -- as for libyaml, duktape and # the other archive-only ports -- the assertion is that the shipped payload is # real: the header with the API a daemon needs, and the archive carrying the # compiled objects and their exported symbols. inc=/usr/include/tcpd.h lib=/usr/lib/libwrap.a check libwrap/header-present test -f "$inc" check libwrap/header-nonempty test -s "$inc" # The C API a daemon needs to use libwrap at all: build a request, fill it from # the accepted socket, ask for the verdict, and the severity globals it sets. missing=$(for f in hosts_access hosts_ctl request_init request_set fromhost \ allow_severity deny_severity RQ_DAEMON RQ_CLIENT_NAME; do grep -q "$f" "$inc" || printf '%s ' "$f" done) check_eq libwrap/header-api "" "$missing" check libwrap/archive-present test -f "$lib" check_out libwrap/archive-magic "!" head -c 8 "$lib" # Flatten to printable text once: GNU grep suppresses matches inside binary data # and busybox tr does not handle [:print:] under -c, so the octal range is the # formulation both greps and both trs agree on (same as the libyaml/duktape # suites). flat=$tmp/libwrap.flat tr -c '\40-\176\n' ' ' < "$lib" > "$flat" 2>/dev/null # The archive must carry the real object set, not just exist. hosts_access.o is # the one the port had to fix to compile at all. missing=$(for m in hosts_access.o options.o shell_cmd.o eval.o hosts_ctl.o \ refuse.o percent_x.o clean_exit.o weak_symbols.o fromhost.o \ fix_options.o socket.o workarounds.o update.o misc.o diag.o \ percent_m.o myvsyslog.o; do grep -q "$m" "$flat" || printf '%s ' "$m" done) check_eq libwrap/archive-members "" "$missing" # Exported symbols spanning the same surface asserted on the header, so the # index is evidence the wasm32 compile really produced the library. missing=$(for s in hosts_access hosts_ctl request_init request_set fromhost \ hosts_allow_table hosts_deny_table hosts_access_verbose; do grep -q "$s" "$flat" || printf '%s ' "$s" done) check_eq libwrap/archive-symbols "" "$missing" # Static-only is distro policy AND the reason patch 0002 exists: oe-core's # recipe links every tool against a shared libwrap. A libwrap.so here would mean # that patch was lost and the tools are no longer statically linked. check libwrap/no-shared sh -c 'set -- /usr/lib/libwrap.so* /lib/libwrap.so*; [ ! -e "$1" ]' # The archive must not carry the build directory (INHIBIT_PACKAGE_STRIP is set # on this distro, so nothing would remove it). check libwrap/no-buildpath sh -c "! grep -q '/tmp-glibc/work/' '$flat'" cd / rm -rf "$tmp"