. "$BST_LIB" # BST-TIMEOUT: 1800 # # openldap 2.6.10 (meta-oe recipes-support/openldap) -- the OpenLDAP directory # server, its offline database tools and its client tools. # # WHAT THE TICKET ASKS FOR. BRINTOS-1235 names one pass criterion and it is a # directory round trip: a bundled LDIF loaded into a database, served over # ldapi://, searched back byte-exact against a reference, an entry # added/modified/deleted, and a bind with a wrong password refused. All of it is # below. TWO ALREADY-TRACKED PLATFORM GAPS decide which half of it is green, and # both were measured on this image before a line of this file was written -- # neither is openldap's: # # (1) THE mdb BACKEND CANNOT OPEN A DATABASE. LMDB maps its data file # MAP_SHARED with PROT_WRITE, and this kernel refuses a file-backed # MAP_SHARED mapping whenever PROT_WRITE is asked for -- brintos/linux#30, # the same gap 77-tokyocabinet and 77-sqlite3 already carry. slapadd stops at # # mdb_db_open: database "dc=brintos,dc=test" cannot be opened: # No such device (19). Restore from backup! # # MEASURED with the database on /tmp, which is a tmpfs here (`tmpfs /tmp # tmpfs rw,relatime` in /proc/mounts) -- so this is not the virtiofs root and # no choice of directory moves it. The mdb case below is the ticket's named # backend and is kept as an attributed RED rather than skipped, because it # flips green by itself the day #30 lands, which is the regression signal the # suite is bought for. # # (2) slapd CANNOT SERVE, BECAUSE THIS ARM REFUSES EVERY THREAD. slapd creates # its ldapi socket and then starts a listener thread; pthread_create returns # EAGAIN and it shuts down cleanly. From slapd's own log, on target: # # slapd starting # listener ldap_pvt_thread_create failed (11) # slapd stopped. # # The guest kernel says why, in its own words, on the same console: # # [k] wasm32: REFUSING thread-mode clone (CLONE_VM, pid=77, # flags=0x3d0f00) with EAGAIN -- the coro substrate's runtime # protocol state is single-lane per process # # That is brintos/linux-in-the-browser#85 / brintos/hardwarejs#12 -- the # guest kernel refuses a thread-mode clone with EAGAIN -- and 79-libqb, # 78-nspr and 62-ndpi carry it too. What it does NOT stop is worth pinning # separately, and this file does: the AF_UNIX socket is bound and listening # before the thread is asked for, so the socket layer is not the wall. openldap # also clears the two walls that stop bind next door -- its epoll probe is a # RUN-TIME test, so cross-compilation answers no and it falls through to # poll(2) (`checking for epoll system call... no`, from this image's own # do_configure log), and it reads no rlimits at startup. # # SO THE ROUND TRIP IS DRIVEN OFFLINE, AND THAT IS REAL COVERAGE RATHER THAN A # CONSOLATION. openldap ships the offline counterpart of every client tool the # criterion names -- slapadd for ldapadd, slapmodify for ldapmodify and for # ldapdelete (changetype: delete), slapcat for ldapsearch -- and they go through # the SAME backend, schema, LDIF parser and entry encoder that the network path # would. What the offline arm cannot exercise is the wire: the LDAP protocol # encoders, the connection layer and simple bind. Those are the client cases at # the end, they are red, and they are red for one cause. # # THE BACKEND FOR THE WORKING ARM IS back-ldif, NOT mdb, AND IT IS COMPILED IN. # back-ldif stores one LDIF file per entry and mmaps nothing, so #30 does not # reach it. It is a stock openldap backend that this build already carries -- # with --disable-modules everything is linked into slapd, and `slapd -T test` # accepts `database ldif` -- so nothing was added to the port to make this arm # possible. # # EXEC COUNT IS THE OTHER CONSTRAINT. Everything openldap ships statically links # glibc and openssl: slapd is 57203599 bytes and each client 40-43 MB, measured # in the .deb. The loader stages an exec image as a segment list capped at # WASM32_IMG_MAX_SEGS=1024 and the block size shrinks as the guest heap # fragments, so late execs of images this size are refused -ENOMEM # (brintos/linux#44); check/check_out/check_rc route that to bst_unrun on their # own. Hence the ordering: the zero-exec cases first, then the ticket's named # backend, then the working round trip, and the client cases -- whose outcome is # already known and identical -- last, where a refusal costs the least. # # WAITING IS DONE BY POLLING AN ARTIFACT WITH A YIELD IN THE LOOP. sleep(1) does # not sleep on this image (~0.29 s, linux-in-the-browser#120), so it is not a # bound; but a poll loop with NO sleep is not a wait at all -- 300 iterations of # `[ -S file ]` complete in microseconds and report "never appeared" before the # 57 MB server has been staged. That mistake cost a diagnostic round on this # ticket. slapd is never signalled or waited on either: a task parked in # accept(2) never crosses a dispatch boundary, so it cannot be reaped # (brintos/linux#21). require slapd openldap/present D=/tmp/bst-openldap URI=ldapi://%2Ftmp%2Fbst-openldap%2Fldapi BASE=dc=brintos,dc=test ADMIN=cn=admin,dc=brintos,dc=test ADMINPW=brintos-admin ATTRS="objectClass dc o ou uid cn sn mail telephoneNumber" rm -rf "$D"; mkdir -p "$D/mdb" "$D/ldifdb" || { bst_notok openldap/workdir "cannot create $D"; exit 0; } # The bundled slapd.conf is the mdb one. The ldif-backend conf is derived from it # so the two arms cannot drift apart in suffix, rootdn or rootpw: `index' and # `maxsize' are mdb-only directives and back-ldif rejects the config outright if # they are left in. cp "$BST_DATA/openldap-slapd.conf" "$D/mdb.conf" sed -e 's#^database.*#database ldif#' -e '/^maxsize/d' -e '/^index/d' \ -e "s#^directory.*#directory $D/ldifdb#" \ "$BST_DATA/openldap-slapd.conf" > "$D/ldif.conf" # Canonical form of an LDIF stream: keep only the attributes this suite asked # for, drop blank lines, sort in byte order. The allow-list is what makes a # slapcat comparison possible at all -- slapcat also emits the operational # attributes the backend generated (entryUUID, createTimestamp, entryCSN, # structuralObjectClass, creatorsName), which are per-run values by construction, # and userPassword, which it base64-encodes. Sorting is not cosmetic either: LDAP # defines no order for the attributes within an entry and the order of entries is # the backend's business, so comparing raw output would be asserting an # implementation detail rather than the directory's contents. The reference files # were generated by applying this same reduction to the entries this suite loads. canon() { grep -E '^(dn|objectClass|dc|o|ou|uid|cn|sn|mail|telephoneNumber): ' | LC_ALL=C sort } # --- free cases: shell builtins, zero execs ----------------------------------- # The port's file list at the paths the recipe installs them. slapd and the # offline slap* tools are the openldap-slapd package, the ldap* clients are # openldap-bin, so a gap here says which of the two failed to land. slapadd and # the rest are symlinks onto slapd, which is why -x rather than -f. inv="" for f in /usr/sbin/slapd /usr/sbin/slapadd /usr/sbin/slapcat /usr/sbin/slapmodify \ /usr/sbin/slappasswd /usr/sbin/slapindex /usr/sbin/slaptest \ /usr/bin/ldapsearch /usr/bin/ldapadd /usr/bin/ldapmodify \ /usr/bin/ldapdelete /usr/bin/ldapwhoami /usr/bin/ldappasswd \ /usr/bin/ldapcompare /usr/bin/ldapmodrdn /usr/bin/ldapurl; do [ -x "$f" ] || inv="$inv $f" done check_eq openldap/inventory "" "$inv" # The schemas this suite's config includes. Their absence would mean do_install's # schema copy silently did nothing, and every later case would go red for a # reason that has nothing to do with the directory. missing="" for f in /etc/openldap/schema/core.schema /etc/openldap/schema/cosine.schema \ /etc/openldap/schema/inetorgperson.schema /etc/openldap/ldap.conf; do [ -f "$f" ] || missing="$missing $f" done check_eq openldap/schemas "" "$missing" # The backends are linked INTO slapd on this arm rather than dlopen'd, so there # must be no back_*.so anywhere -- one would mean the bbappend's # --disable-modules stopped taking effect and slapd would be looking for a loader # this image has no substrate for. The glob is unexpanded when it matches # nothing, which is also the answer when the directory does not exist at all. mods=$(echo /usr/libexec/openldap/back_*.so) check_eq openldap/no-dlopen-backends "/usr/libexec/openldap/back_*.so" "$mods" # --- exec 1: the ticket's named backend, mdb ---------------------------------- # Expected RED under brintos/linux#30 until the kernel allows a file-backed # MAP_SHARED with PROT_WRITE. Asserted on the SIGNATURE rather than on "it # failed": a different failure here would be a different finding, and reporting # it as #30 would bury it. mdb_out=$(slapadd -f "$D/mdb.conf" -l "$BST_DATA/openldap-base.ldif" 2>&1); mdb_rc=$? if [ "$mdb_rc" -eq 0 ]; then bst_ok openldap/mdb-slapadd elif bst_execfail "$mdb_out"; then bst_unrun openldap/mdb-slapadd "$mdb_out" else case "$mdb_out" in *"No such device"*) bst_notok openldap/mdb-slapadd \ "brintos/linux#30 (file-backed MAP_SHARED with PROT_WRITE is ENODEV): LMDB cannot map its data file, on tmpfs as well as virtiofs -- $(_bst_1line "$mdb_out")" ;; *) bst_notok openldap/mdb-slapadd \ "rc=$mdb_rc and NOT the #30 ENODEV signature -- $(_bst_1line "$mdb_out")" ;; esac fi # --- exec 2: slapadd, the ticket's first requirement, on back-ldif ------------ check openldap/slapadd \ slapadd -f "$D/ldif.conf" -l "$BST_DATA/openldap-base.ldif" # --- exec 3: slapcat, byte-exact against the reference ------------------------ cat_out=$(slapcat -f "$D/ldif.conf" -o ldif-wrap=no 2>&1); cat_rc=$? if [ "$cat_rc" -ne 0 ] && bst_execfail "$cat_out"; then bst_unrun openldap/slapcat "$cat_out" else check_eq openldap/slapcat \ "$(canon < "$BST_DATA/openldap-search.expected")" \ "$(printf '%s\n' "$cat_out" | canon)" fi # --- execs 4-5: the entry is added and then modified, offline ----------------- # slapmodify is the offline counterpart of ldapmodify and drives the same LDIF # change parser; openldap-add.ldif carries `changetype: add' so the one file # serves both it and the ldapadd case further down. check openldap/slapmodify-add \ slapmodify -f "$D/ldif.conf" -l "$BST_DATA/openldap-add.ldif" check openldap/slapmodify-modify \ slapmodify -f "$D/ldif.conf" -l "$BST_DATA/openldap-mod.ldif" # --- exec 6: both changes read back in ONE dump ------------------------------- # The reference differs from the first one by exactly carol: present, with the # REPLACED mail and the ADDED telephoneNumber. So this single comparison decides # both changes, and a modify that silently did nothing fails it. aft_out=$(slapcat -f "$D/ldif.conf" -o ldif-wrap=no 2>&1); aft_rc=$? if [ "$aft_rc" -ne 0 ] && bst_execfail "$aft_out"; then bst_unrun openldap/slapcat-after-change "$aft_out" else check_eq openldap/slapcat-after-change \ "$(canon < "$BST_DATA/openldap-search-after.expected")" \ "$(printf '%s\n' "$aft_out" | canon)" fi # --- execs 7-8: the entry is deleted, and the directory is back to the base --- check openldap/slapmodify-delete \ slapmodify -f "$D/ldif.conf" -l "$BST_DATA/openldap-del.ldif" # Compared against the FIRST reference rather than merely asserting carol is # absent: a delete that also took something else with it fails this and would # pass a bare absence check. del_out=$(slapcat -f "$D/ldif.conf" -o ldif-wrap=no 2>&1); del_rc=$? if [ "$del_rc" -ne 0 ] && bst_execfail "$del_out"; then bst_unrun openldap/slapcat-after-delete "$del_out" else check_eq openldap/slapcat-after-delete \ "$(canon < "$BST_DATA/openldap-search.expected")" \ "$(printf '%s\n' "$del_out" | canon)" fi # --- exec 9: the server ------------------------------------------------------- # Two cases out of one invocation, because the two halves fail in different # places and only one of them is a wall. slapd binds and listens on the AF_UNIX # socket FIRST and asks for its listener thread SECOND, so the socket node # appearing says the socket layer works, and the process then dying says threads # do not. Never signalled, never waited on -- see the header. slapd -f "$D/ldif.conf" -h "$URI" -d 256 > "$D/slapd.out" 2>&1 & i=0 while [ $i -lt 90 ]; do [ -S "$D/ldapi" ] && break sleep 1 i=$((i+1)) done # The socket appearing is not the end of slapd's startup -- the listener thread # is asked for AFTER the bind, so reading the log the instant the node shows up # can catch slapd before it has had the chance to fail. Poll a second time for a # TERMINAL marker, and let a healthy slapd spend the whole (short) bound without # producing one: waiting a few seconds for a server that is working costs # nothing, and not waiting misreports one that is not. j=0 while [ $j -lt 20 ]; do case "$(cat "$D/slapd.out" 2>/dev/null)" in *"slapd stopped."*|*"thread_create failed"*) break ;; esac sleep 1 j=$((j+1)) done slapd_log=$(cat "$D/slapd.out" 2>/dev/null) if [ -S "$D/ldapi" ]; then bst_ok openldap/slapd-binds-socket elif bst_execfail "$slapd_log"; then bst_unrun openldap/slapd-binds-socket "$slapd_log" else bst_notok openldap/slapd-binds-socket \ "no ldapi socket at $D/ldapi after $i polls -- $(_bst_1line "$slapd_log")" fi # The server has to still be RUNNING to serve anything, and this case is asserted # on slapd's own log rather than on the liveness of its pid. `kill -0' is the # obvious test and it is wrong here: this suite deliberately never waits on slapd # (see the header), so once slapd exits its pid stays a zombie, `kill -0' # succeeds on a zombie, and the case reads GREEN beside five client cases that # have just reported there is no server. It did exactly that on the first run of # this file -- a guard that passes is not evidence, and this one was passing for # a reason unrelated to what it claimed to measure. slapd's log says what # happened and cannot be fooled by an unreaped pid. case "$slapd_log" in *"thread_create failed"*) bst_notok openldap/slapd-listener-thread \ "brintos/linux-in-the-browser#85 + brintos/hardwarejs#12 (thread-mode clone refused EAGAIN -- the coro substrate is single-lane per process): slapd bound its socket, then could not create the listener thread -- $(_bst_1line "$slapd_log")" ;; *"slapd stopped."*) bst_notok openldap/slapd-listener-thread \ "slapd started and then exited, and NOT with the #85 thread_create signature -- $(_bst_1line "$slapd_log")" ;; *"slapd starting"*) bst_ok openldap/slapd-listener-thread ;; *) bst_notok openldap/slapd-listener-thread \ "slapd printed no startup banner at all -- $(_bst_1line "$slapd_log")" ;; esac # --- execs 10-14: the client tools over the wire ------------------------------ # All five are red for the one cause above -- there is no server to answer them, # because slapd could not create a thread. They are kept, and kept as separate # cases, because they are the ticket's criterion in its own terms and because # they flip green together the day #85 lands. They are LAST because their result # is already known and a #44 exec refusal costs least here. # # Each is asserted on `Can't contact LDAP server' specifically. A bare non-zero # would also be produced by a broken client, a malformed URI and a missing # binary, and those are different findings. ldapfail() { # case "$3" in *"Can't contact LDAP server"*) bst_notok "$1" "brintos/linux-in-the-browser#85 + brintos/hardwarejs#12: no server to answer -- slapd cannot create its listener thread (see openldap/slapd-listener-thread) -- $(_bst_1line "$3")" ;; *) bst_notok "$1" "rc=$2 and NOT the no-server signature -- $(_bst_1line "$3")" ;; esac } run_client() { # _cid=$1; shift _cout=$("$@" 2>&1); _crc=$? if [ "$_crc" -eq 0 ]; then bst_ok "$_cid" elif bst_execfail "$_cout"; then bst_unrun "$_cid" "$_cout" else ldapfail "$_cid" "$_crc" "$_cout"; fi } run_client openldap/ldapsearch \ ldapsearch -LLL -o ldif-wrap=no -x -H "$URI" -b "$BASE" -s sub '(objectClass=*)' $ATTRS run_client openldap/ldapadd \ ldapadd -x -H "$URI" -D "$ADMIN" -w "$ADMINPW" -f "$BST_DATA/openldap-add.ldif" run_client openldap/ldapmodify \ ldapmodify -x -H "$URI" -D "$ADMIN" -w "$ADMINPW" -f "$BST_DATA/openldap-mod.ldif" run_client openldap/ldapdelete \ ldapdelete -x -H "$URI" -D "$ADMIN" -w "$ADMINPW" "uid=carol,ou=people,dc=brintos,dc=test" # The wrong-password bind. It cannot be asserted as "exits non-zero" here and be # worth anything: with no server, EVERY bind exits non-zero, so the case would # read green for the wrong reason and would keep reading green after #85 lands # even if authentication were broken. It is therefore asserted on the server # saying `Invalid credentials', which is red today for the same one cause and # becomes a real assertion the moment there is a server to answer. bad_out=$(ldapwhoami -x -H "$URI" -D "uid=alice,ou=people,dc=brintos,dc=test" \ -w wrong-password 2>&1); bad_rc=$? if bst_execfail "$bad_out"; then bst_unrun openldap/bind-wrong-password "$bad_out" elif [ "$bad_rc" -eq 0 ]; then bst_notok openldap/bind-wrong-password \ "bind with a wrong password SUCCEEDED (rc=0) -- $(_bst_1line "$bad_out")" else case "$bad_out" in *"Invalid credentials"*) bst_ok openldap/bind-wrong-password ;; *) ldapfail openldap/bind-wrong-password "$bad_rc" "$bad_out" ;; esac fi