NextBSD · Porting plan · Execution
Exactly what Apple source to vendor into nextbsd-userland/src, where every file installs on the filesystem, what to drop/replace in the FreeBSD-compat layer (PAM, NSS, membership), and a phased roadmap for directoryservicesd — the net-new daemon that stands in for the closed opendirectoryd.
directoryservicesd is a net-new C daemon built on NextBSD's proven wland/configd pattern (bootstrap_check_in + a raw mach_msg MIG loop + a launchd job) that embeds the reusable Apple C++ plugin bodies from DirectoryService-621.12 in-process, behind the C DirServices.h API. It is not a port of Apple's ~50k-LOC C++ DirectoryService daemon core, and not the never-released opendirectoryd.
This resolves the old scoping doc's "Path A (port the C++ daemon) vs Path B (write a minimal daemon)" fork: we take Path B's daemon shell wrapping Path A's plugin logic — the smallest honest thing that runs local login, exactly how wland is a net-new shell around stock wpa_supplicant. Legally it also lands clean: the daemon is our code (functional name, wland precedent), the reused pieces keep their Apple names (compat identifiers), and nothing is branded opendirectoryd.
Client → daemon → backend, and what lives above vs below the Mach line
┌──────────────── GERSHWIN (above the Mach line — libobjc2 + Foundation exist here) ─────────────┐
│ ObjC OpenDirectory.framework ODNode / ODSession / ODQuery / ODRecord (OpenDirectory-146) │
│ Accounts pref pane · Directory-Utility-equivalent │
└───────────────┬────────────────────────────────────────────────────────────────────────────────┘
│ ObjC → CF-C
┌───────────────┼──────────────── nextbsd-userland (below the Mach line — NO ObjC) ───────────────┐
│ libCFOpenDirectory.so (OpenDirectory-146 Core/, CoreFoundation-C twin — ODNodeRef/ODSessionRef)│
│ dscl · dsmbrutil · dscacheutil · pwpolicy (DSTools-162 C/C++ CLIs) │
│ │ MIG subsystem `directoryservices` (flat scalars, NO out-of-line — see R4) │
│ ╔════════▼══════════════════════════════════════════════════════════════════════════════╗ │
│ ║ directoryservicesd NET-NEW C shell · /usr/sbin · launchd org.nextbsd.directoryservicesd║
│ ║ ├─ MIG server thread (clone of WLAN/mach_service.c) ║
│ ║ ├─ dispatcher: node routing + AuthenticationAuthority ║
│ ║ └─ in-process plugins behind DirServices.h (DirectoryService-621.12, C++) ║
│ ║ • Local (dslocal plist) [v0.1] • BSD-flat (/etc/master.passwd fallback)[v0.1] ║
│ ║ • Cache / Search / Configure [v0.1] • LDAPv3 (bind/search/replica) [v0.2] ║
│ ╚═════╤══════════════════════╤═══════════════════════╤═══════════════════════════════════════╝
│ │ ;ShadowHash; (local) │ ;ApplePasswordServer; │ ;Kerberosv5; │
│ ▼ ▼ ▼ │
│ in-proc hash verify passwordserviced libkrb5 (link, not a daemon) │
│ (DSPasswordServerPlugin (NET-NEW C daemon + MIT-krb5 shipped; KDC external │
│ hash-slot logic) [v0.1] Cyrus SASL) [v0.3] [v0.3] │
└──────▲───────────────────────────────────────────────────▲──────────────────────────────────────┘
│ nss_directoryservices.so (NSS → getpwnam/getgrnam) │ pam_directoryservices.so (PAM auth)Real opendirectoryd (10.7+) runs out-of-process XPC modules. NextBSD has launchd + libxpc + libdispatch, so that path is technically open — but two facts make it the wrong first move: dispatch_mach is unfinished in this port (configd already falls back to a raw mach_msg loop), and MIG out-of-line data is broken in this kernel. An XPC-module fabric leans on exactly those. The DirectoryService-621.12 plugins were written to run in-process behind DirServices.h anyway, and NextBSD's libCoreFoundation already ships CFPlugIn.c/CFBundle*.c — so the native in-process loader is reusable as-is. Choosing XPC modules would mean writing the module host and reverse-engineering the never-released odxpc schema; choosing in-process means compiling code that already exists the way it exists.
Initialize/ProcessRequest/Shutdown) so one could later be relocated behind an XPC transport without touching the dispatcher. That seam is exactly where a closed AD plugin would have to attach — which is the only reason to keep it clean, not a commitment to build AD (it stays permanently out, §8).Verified by scanning the tree: the only .m files in nextbsd-userland are libdispatch internals and test fixtures — no Foundation, no NSObject, no libobjc2. So:
ODNode.m/ODSession.m…) cannot build in userland → it goes to Gershwin, above the Mach line — the identical split the WLAN plan used for CoreWLAN.Core/CFOD*.c) can live below the line as libCFOpenDirectory.so (libCoreFoundation is present). This is what actually marshals MIG requests to the daemon.odxpc.c was never released → we author a thin MIG client (not port one). Because we write both ends, we own the Mach service name (§1.3).directoryservicesd would be the first bsd.prog.mk daemon in the tree to link C++ (-lc++ -lcxxrt). That's the single biggest build unknown (R1) — smoke-test a one-file C++ bsd.prog.mk stub against the vendored CF/Mach headers under -fblocks before vendoring the whole Server/ tree. Keep the daemon shell in C; confine C++ to the plugin bodies behind a C ABI.org.nextbsd.*The two net-new daemons take functional NextBSD names (the wland→org.nextbsd.wlan rule): org.nextbsd.directoryservices and org.nextbsd.passwordservice; launchd job labels org.nextbsd.directoryservicesd / …passwordserviced.
com.apple.system.DirectoryService.* Mach labels (because Apple's libinfo looks them up); another argued for org.nextbsd.*. Resolution: org.nextbsd.* wins — because (a) NextBSD abandons Apple's libinfo/lookupd path for FreeBSD nsswitch (§6), so those libinfo_v1 labels are moot, and (b) we author the OD transport glue ourselves, so nothing Apple-derived hard-codes a label we must match. Fabricating com.apple.opendirectoryd for a NextBSD daemon would be the "fake Apple label" the mach_service.c comments explicitly reject. Mitigation (R9): grep the reused DSTools binaries at port time for a baked-in com.apple.private.opendirectoryd.rpc lookup; if present, patch it or register a bootstrap alias. (Contrast configd/notifyd, which keep com.apple.* precisely because they model a released Apple daemon that Apple-derived code binds to by that name.)src/Three userland components + one Gershwin framework — not a monolith
The daemon (C++/Mach), the password engine (separate Apple repo, separate trust boundary + BSD-licensed SASL), and the CLIs have different toolchain/link needs; and the ObjC OD API physically can't compile in userland. So the port splits:
# ── nextbsd-userland/src/ (below the Mach line) ──
src/DirectoryService/ # directoryservicesd daemon + libDirectoryService (C API)
directoryservicesd.cpp WRITE-NEW main(): liblaunch bootstrap + dispatch + MIG loop
CServerPlugin.cpp CHandlers.cpp Mbrd_*.cpp CLog/CPlugInList/CRefTable.cpp
VENDOR DirectoryService-621.12 Server/ (in-proc CFPlugIn host)
directoryservices.defs mach_service.cpp ds_mig.cpp
WRITE-NEW MIG surface, modeled on WLAN/wlan.defs + mach_service.c
plugins/{BSD,Cache,Local,Search,Configure,Common}/*.cpp
VENDOR DirectoryService-621.12 PlugIns/ (6 built-ins, static-linked)
plugins/LDAPv3/*.cpp VENDOR-DEFERRED needs OpenLDAP libldap; gate off until v0.2
api/DirServices.cpp include/DirectoryService/DirServices.h
VENDOR APIFramework/ — C++ impl behind the C header
src/libCFOpenDirectory/ # CoreFoundation-C twin — NO ObjC
CFOpenDirectory.c CFOD{Node,Record,Query,Session}.c + headers
VENDOR OpenDirectory-146 Core/
src/PasswordService/ # net-new password-auth daemon (v0.3)
passwordserviced.cpp pws.defs pws_mig.cpp
WRITE-NEW daemon host (never released by Apple)
AuthFile.c CAuthFileBase.cpp DES.c SMBAuth.c KerberosInterface.c
VENDOR DSPasswordServerPlugin-281.1 (hash-slot / policy)
PolicyBase.m → .c CPSPlugIn.mm → .cpp
REWRITE strip ObjC/ObjC++ → C/C++
sasl/ VENDOR|PORTS passwordserver_sasl-215 (Cyrus SASL, BSD) — or base cyrus-sasl2 [U]
src/DSTools/ # CLIs
dscl.m → dscl.c REWRITE ObjC → C over DirServices.h (WLAN CLI pattern)
dscacheutil.c dsmbrutil.c dsconfigldap.c
VENDOR DSTools-162 (already C)
pwpolicy.cpp VENDOR DSTools-162 (C++)
# ── Gershwin (above the Mach line) ──
Gershwin …/OpenDirectory.framework/ ODSession.m ODNode.m ODRecord.m ODQuery.m
VENDOR-INTO-GERSHWIN OpenDirectory-146 Framework/ (ObjC)
dseditgroup/dsimport/dsconfigad are missing from DSTools-162 → write-new or omit (dsconfigad needs the absent AD plugin → omit).directoryservicesd MakefileModeled line-for-line on src/WLAN/Makefile — with the C++ and CFPlugIn deltas called out
PROG= directoryservicesd
MAN= directoryservicesd.8
BINDIR= /usr/sbin
SRCS= directoryservicesd.cpp mach_service.cpp ds_mig.cpp
SRCS+= CServerPlugin.cpp CHandlers.cpp CPlugInList.cpp CRefTable.cpp CLog.cpp
SRCS+= Mbrd_MembershipResolver.cpp Mbrd_HashTable.cpp Mbrd_Cache.cpp
.PATH: ${.CURDIR}/plugins/BSD ${.CURDIR}/plugins/Cache ${.CURDIR}/plugins/Local \
${.CURDIR}/plugins/Search ${.CURDIR}/plugins/Configure ${.CURDIR}/plugins/Common
SRCS+= CBSDPlugin.cpp CCachePlugin.cpp CDSLocalPlugin.cpp \
CSearchPlugin.cpp CConfigurePlugin.cpp CBasePlugin.cpp
# MIG server stub: directoryservices.defs → directoryservicesServer.c (host mig, MIGOUT=)
MIGOUT?=
.if !empty(MIGOUT)
.PATH: ${MIGOUT}
SRCS+= directoryservicesServer.c
CFLAGS+= -I${MIGOUT} -I${.CURDIR}
CFLAGS.directoryservicesServer.c+= -w
CFLAGS+= -Wno-macro-redefined # mach/notify.h vs mach/message.h MACH_NOTIFY_*
.endif
WARNS= 0 # plugins pull the CF public header tree
CFLAGS+= -O2 -pipe -fblocks
CXXFLAGS+= -O2 -pipe -fblocks -std=c++17 -fno-exceptions -fno-rtti
CFLAGS+= -I${.CURDIR}/../launchd/liblaunch -I${.CURDIR}/../launchd/freebsd-shims
CXXFLAGS+= -I${.CURDIR}/../launchd/liblaunch -I${.CURDIR}/../launchd/freebsd-shims
SYSROOT?=
.if !empty(SYSROOT)
CFLAGS+= -I${SYSROOT}/usr/include
CXXFLAGS+= -I${SYSROOT}/usr/include
LDFLAGS+= -L${SYSROOT}/usr/lib/system -Wl,-rpath,/usr/lib/system -Wl,--allow-shlib-undefined
.endif
# Link line — the WLAN-fixed system_* names, PLUS CFPlugIn + the C++ runtime.
LDADD+= -llaunch -lsystem_kernel # bootstrap_check_in + mach_msg
LDADD+= -lCoreFoundation # CFBundle/CFPlugIn (in-proc plugin load)
LDADD+= -lsystem_dispatch -l:libsystem_blocks.so
LDADD+= -lc++ -lcxxrt # [R1] first C++ daemon in tree — UNVERIFIED link
# NOT -ldispatch / NOT -lBlocksRuntime (#378 rtld collision)
LIBADD+= pthread
.include <bsd.prog.mk>
The C API library libDirectoryService.so (api/DirServices.cpp + the public C header) is a sibling bsd.lib.mk target the daemon does not link but every CLI and libCFOpenDirectory does — same relationship as configd ↔ libSystemConfiguration.
Four-domain rules: bare /Library/X → /Local/Library/X; /System/Library & /var stay; libs → /usr/lib/system; daemons → /usr/sbin
/System/Library/Frameworks on NextBSD — every Apple .framework ships as a versioned .so in /usr/lib/system with headers under /usr/include/<Name>/ (the libSystemConfiguration pattern). And there is no plugin loader — the DS plugins are statically linked into the daemon, so there are no .dsplug files on disk.| Artifact | Apple original | NextBSD path |
|---|---|---|
| Directory daemon | /usr/libexec/opendirectoryd (closed) | /usr/sbin/directoryservicesd |
| Password daemon v0.3 | /usr/sbin/PasswordService (never released) | /usr/sbin/passwordserviced (name = open decision) |
| OpenDirectory CF-C twin | /System/Library/Frameworks/OpenDirectory.framework | /usr/lib/system/libCFOpenDirectory.so + /usr/include/CFOpenDirectory/ |
| OpenDirectory ObjC layer | same framework | Gershwin /System/Library/Frameworks/OpenDirectory.framework |
| DirectoryService C API | /System/Library/Frameworks/DirectoryService.framework | /usr/lib/system/libDirectoryService.so + /usr/include/DirectoryService/ |
| All DS plugins | …/Resources/Plugins/*.dsplug | statically linked into the daemon — no on-disk file |
| launchd plists | (closed) | overlay/System/Library/LaunchDaemons/org.nextbsd.directoryservicesd.plist (+ …passworderviced…) |
| Mutable OD config | /Library/Preferences/OpenDirectory/ | /Local/Library/Preferences/OpenDirectory/ (runtime-created) |
| Local record store | /var/db/dslocal/nodes/Default/ | /var/db/dslocal/nodes/Default/ (unchanged — already /var-qualified) |
| LDAP mapping templates | /System/Library/OpenDirectory/Templates/ | /System/Library/OpenDirectory/Templates/ (System domain, read-only overlay) |
| CLIs | /usr/bin/dscl … | /usr/bin/{dscl,dscacheutil,dsmbrutil,pwpolicy,dserr} |
| Mach service names | com.apple.system.opendirectoryd.* | org.nextbsd.directoryservices (§1.3) |
overlay/System/Library/LaunchDaemons/org.nextbsd.directoryservicesd.plist new
overlay/System/Library/LaunchDaemons/org.nextbsd.passwordserviced.plist new (v0.3)
overlay/System/Library/OpenDirectory/Templates/LDAPv3/ new, read-only
overlay/private/var/db/dslocal/nodes/Default/users/{root,daemon,nobody}.plist seed [see R below]
overlay/private/var/db/dslocal/nodes/Default/groups/{wheel,staff,…}.plist seed
overlay/private/etc/openldap/schema/apple.schema only if OD-server (v0.4+)
The plist mirrors org.nextbsd.wland.plist (RunAtLoad, KeepAlive, a MachServices dict the daemon claims via bootstrap_check_in). Since there's no launchd ordering and no first-boot provisioner, the dslocal tree must be seeded at image-assembly time via the overlay, not by the daemon on first run — [U] confirm assemble-image.sh recurses into overlay/private/var].
Where the auth glue actually lands — and it's not in nextbsd-freebsd-compat
nextbsd-freebsd-compat is fbsdglue — scaffold-only, ships no PAM/pam.d/OpenPAM. All directory-auth work lands in nextbsd-userland, which already ships Apple OpenPAM-35 as libpam.so.6 (replacing FreeBSD-pam in place) and Apple OpenBSM libbsm. OpenPAM on both sides ⇒ Apple's PAM modules port with zero PAM-API shim.pam_directoryservices.soApple's pam_opendirectory.c is open (apple-oss-distributions/pam_modules) and is an OpenPAM module — but it calls the OpenDirectory framework, a thin XPC client to the closed opendirectoryd. Since our daemon is the net-new directoryservicesd, there's no closed schema to match. Verdict: keep pam_opendirectory.c as the structural template (the auth/account/password/session function-class layout, authtok conversation), but replace every od_record_* call with a ds_* client call into directoryservicesd. Critically, the module knows nothing about ShadowHash vs PasswordServer vs Kerberos — it calls one verb, ds_record_verify_password(), and the daemon dispatches on AuthenticationAuthority (§7). Installs to /usr/lib/pam/pam_directoryservices.so.6 ([U] confirm OPENPAM_MODULES_DIR).
# overlay/private/etc/pam.d/login — pam_unix kept as flat-file fallback
auth optional pam_directoryservices.so # try directory first
auth required pam_unix.so # /etc/master.passwd fallback
account required pam_nologin.so
account required pam_directoryservices.so
password required pam_directoryservices.so
session required pam_uwtmp.so
session optional pam_directoryservices.so
macOS routes getpwnam through Libinfo→lookupd→the ds module. NextBSD has deliberately not ported that path (the libinfo.h shim's si_search_module_set_flags is a no-op). So visibility comes from FreeBSD nsswitch: add nss_directoryservices.so (template = the in-tree nss_mdns.c) implementing getpwnam_r/getpwuid_r/getgrnam_r/getgrgid_r/getgrouplist as a thin MIG client to the daemon, plus an /etc/nsswitch.conf overlay: passwd: files directoryservices (files first = single-user safety).
| Component | Action | Detail |
|---|---|---|
pam_directoryservices.so | ADD | Cloned from open pam_opendirectory.c; od_record_*→ds_*. → /usr/lib/pam/ |
pam.d {login,su,sshd,passwd,other} | ADD | Overlay policy; pam_unix retained as fallback |
nss_directoryservices.so | ADD | nsswitch module, template nss_mdns.c → /usr/lib/nss_directoryservices.so.1 |
/etc/nsswitch.conf | ADD | passwd/group: files directoryservices |
membership.h mbr_* stubs | REPLACE | Currently ENOENT; make them daemon clients (uid↔UUID↔group) — needed for ACL/nested groups & Apple id |
libinfo.h shim | KEEP (no-op) | Apple libinfo path stays abandoned |
FreeBSD /etc/pam.d/* | DROP | Reference un-vendored FreeBSD-only modules; superseded by overlay set |
flat-file master.passwd/pw | KEEP, demote | Stay as the files source + bootstrap authority; lose primacy, not presence |
MIT libkrb5.so.122 | KEEP | curl/git ABI; fine for local ShadowHash (no KDC) & plain network krb5 (§7) |
NextBSD ships MIT krb5 1.22.1; Apple's directory-Kerberos stack is Heimdal-coupled (LKDC, hdb, the Password Server replica protocol). Impact: non-issue for ;ShadowHash; local login (no KDC involved — the whole v0.1 case); fine for plain ;Kerberosv5; against any RFC-4120 KDC (link MIT libkrb5, call krb5_get_init_creds_password); a hard blocker only for Apple LKDC / Password Server SSO (Heimdal-only) — which is deferred anyway. If ever needed, add FreeBSD's Heimdal port beside MIT for the daemon only (keep MIT's SONAME for curl/git).
The principle is split by store: system/service accounts stay in /etc/master.passwd (the flat-file fallback), while directory/human accounts live in the directory (plists), managed by dscli/directoryservicesd. So each base account tool is keep / remove / route depending on which store it writes. The actual removals are narrow — only the human-account convenience layer (adduser/rmuser). The low-level flat-file tools stay, because system/service accounts still live in master.passwd and the base + ports depend on them.
| Tool | Disposition | Why |
|---|---|---|
pw | KEEP | Load-bearing — pkg/ports install scripts create service accounts with pw useradd -n _foo. Remove it and package installs break. Writes /etc/master.passwd (system/service accounts = correct target). Just not the tool for human users. |
adduser / rmuser | REMOVE | Interactive wrappers around pw that create human accounts in the flat file — the wrong store in this model, and a confusing second path. Replaced by dscli user add / dscli user delete. |
/etc/adduser.conf, /etc/adduser.msg | REMOVE | Config for adduser; goes with it. |
chpass / chfn / chsh | KEEP (system) / route (directory) | Edit master.passwd gecos/shell for system accounts. A directory user changes those via dscli edit, not these. |
passwd(1) | KEEP (system) / route (directory) | For system accounts, stock passwd → master.passwd. For a directory user it must hit the daemon — either dscli passwd, or the pam.d/passwd stack routes them through pam_directoryservices. |
vipw, pwd_mkdb | KEEP | Low-level master.passwd edit / db rebuild — system accounts only. |
pw — it is the single most load-bearing account tool for a FreeBSD-derived base; every service account a package creates goes through it.These tools come from the FreeBSD base closure via nextbsd-freebsd-compat; the exact in-closure set and the ports USES= that depend on pw should be verified against the live srclist before the removals land. dscli vs the Apple dscl naming is an open unification question.
If a Mac binds to the (future, deferred) LDAP front-end, it authenticates fine, but its home must be presented in macOS's namespace, not NextBSD's /Local convention. A Mac is not a NextBSD client: it has no /Local domain, doesn't run directoryservicesd, and doesn't NFS-mount /Local as /Network. Hand a Mac NFSHomeDirectory: /Local/Users/jmaloney and it logs in with a broken/missing home — the path is meaningless there. So the plist→LDAP bridge must rewrite the home per client; the raw /Local path never reaches a Mac.
NFSHomeDirectory is a path, not a protocol Despite the name, NFSHomeDirectory (native homeDirectory) is just the local POSIX home path (what $HOME/pw_dir becomes) — the "NFS" is vestigial. The mount protocol comes from the separate HomeDirectory attribute (native apple-user-homeurl), whose URL scheme selects the transport: afp:// / nfs:// / smb://. No URL at all → local home, nothing mounts. So a home can sit at an NFSHomeDirectory path yet mount over AFP, or not mount at all.| Client | NFSHomeDirectory (path) | HomeDirectory URL (protocol) | Result |
|---|---|---|---|
NextBSD (directoryservicesd) | /Local/Users/jmaloney (→ /Network/Users/… on a client) | — | Gershwin four-domain convention |
| Modern Mac (recommended) | /Users/jmaloney | none | Local home — centralized login, no network home (the realistic modern pattern) |
| Vintage Mac (network home) | /Network/Servers/nextbsdserver/Users/jmaloney | afp://nextbsdserver/Users (or nfs://) | autofs mounts AFP/NFS home there |
uid must match: a network home over NFS/AFP uses AUTH_SYS ownership — if jmaloney is uid 5001 on NextBSD, the Mac must act as uid 5001 (it will, from the directory) or it gets permission-denied on its own home. This section describes the deferred Mac-facing LDAP front-end; it does not affect the NextBSD/Linux plist path.
The daemon reads each record's authority attribute and routes the credential — the PAM/NSS clients never see this
| Authority tag | Route | Component | Phase |
|---|---|---|---|
;ShadowHash;<slots> | in-process hash verify vs ShadowHashData | DSPasswordServerPlugin hash-slot logic (SALTED-SHA512 / PBKDF2) | v0.1 |
;ApplePasswordServer;… | IPC to passwordserviced | net-new daemon + Cyrus SASL (CRAM/DIGEST-MD5, NTLM, MS-CHAPv2) + policy engine | v0.3 |
;Kerberosv5;… | link libkrb5, get_init_creds | MIT krb5 (library call; KDC external) | v0.3 |
;ActiveDirectory; | — | closed plugin | never |
v0.1 needs only the ;ShadowHash; path — reusing the hash logic in-process, with no passwordserviced at all. That keeps v0.1 to "daemon shell + Local plugin + local hash verify."
Each phase mapped to the deferred scoping doc's end-states (§5.1 OD client / §5.2 OD+AD / §5.3 OD server)
Goal: boot and log in with directory-backed local accounts — getpwnam() and PAM both served by directoryservicesd, not /etc/master.passwd (except system accounts).
directoryservicesd C shell — clone mach_service.c; bootstrap_check_in("org.nextbsd.directoryservices"); raw mach_msg loop.directoryservices.defs MIG subsystem (base id ~31000, clear of configd 20000 / wland 30000) — flat-scalar, no OOL (R4): ds_open_node, ds_get_record, ds_get_attr_value (one value per round-trip), ds_verify_password, ds_query_begin/next.DirServices.h; seed /var/db/dslocal from /etc/master.passwd.;ShadowHash; verify.nss_directoryservices → getpw*/getgr* over MIG (non-blocking with flat-file fallback, R7).pam_directoryservices.so swapped into the login / LoginWindow policy.dscl CLI over MIG — the ground-truth admin tool.Exit test: dscl . -create /Users/alice → getent passwd alice resolves → LoginWindow authenticates alice, with no line in /etc/master.passwd.
Embed LDAPv3 plugin; dsconfigldap binds a node; search-policy ordering (Local then LDAP); ObjC OpenDirectory.framework ships in Gershwin on the CF-C twin (Accounts / Directory-Utility UI). Exit: a network LDAP user logs into the box — exactly the scoping doc's §5.1 end-state, executed.
passwordserviced net-new daemon (Cyrus SASL mechanisms + auth-file store + password policy: lockout/expiry/history/complexity — the biggest gap the scoping doc named); ;ApplePasswordServer; + ;Kerberosv5; dispatch wired; nested groups via a memberd-equivalent folded into the daemon.
slapd + a KDC + transport security → v0.4+ / on-demand.| # | Risk / question | Assessment |
|---|---|---|
| R1 | C++ in this tree — directoryservicesd is the first bsd.prog.mk C++ daemon | Highest build risk. Smoke-test a one-file C++ stub (-lc++ -lcxxrt, -fno-exceptions/-rtti) against vendored CF/Mach headers under -fblocks before vendoring Server/. Keep the shell in C; C++ confined to plugin bodies behind a C ABI. |
| R4 | MIG OOL broken + records are big — a full directory record easily exceeds an inline message | Central IPC design problem. Never return a whole record — one attribute value per round-trip + paging tokens; big blobs (hash/cert) via chunked offset+len RPCs or a 0600 file-handoff (the wland run-dir pattern). Softener: the daemon has its own store, not bound by configd's 8 KiB — the real ceiling is the mach_msg buffer (configd proves 32 KiB works). [U] measure it. |
| R7 | No launchd ordering — NSS/PAM callers (even launchd itself) may run before the daemon | NSS module must be non-blocking with a flat-file fallback, or a boot-time getpwnam deadlocks. v0.1 gating design item. |
| — | gershwin-DS role conflict — NextBSD may already ship a nss_gershwin user/group backend (plaintext, TCP/4721) | Unresolved authority overlap. The dslocal path is free, but two directory backends answering getpwnam will race. Pick one authority (this plan assumes directoryservicesd supersedes gershwin-DS) — decide before v0.1 seeds root/wheel. |
| R5 | No keychain — LDAP bind creds / machine secret have nowhere encrypted to live | v0.1 needs none (ShadowHashData is already a hash, 0600 in dslocal — matches macOS). v0.2 bind cred → plaintext 0600, documented (wland precedent). Real secret-store is a separate future project; don't block. |
| R6/R9 | Open naming decision — the password daemon; and reused DSTools baked-in Mach labels | Password daemon working name passwordserviced (parallel to directoryservicesd) — your call, v0.3 so not urgent. Grep DSTools at port time for com.apple.private.opendirectoryd.rpc; patch or alias if found. |
The naming boundary is drawn to line up with the authorship and open-source boundaries, which keeps trademark exposure on the parts that are entirely ours:
directoryservicesd, passwordserviced, org.nextbsd.directoryservices — the wland-not-wifid precedent. These are the pieces Apple never released, so they're ours to name.DirectoryService.framework, OpenDirectory.framework, dscl, the dsAttrTypeStandard/AuthenticationAuthority attribute vocabulary — the GNUstep/Gershwin posture (functional API names reused for compatibility, project branded independently).opendirectoryd — Apple's strong "Open Directory" mark; and it's the one binary we're not reusing (closed), so there's no compatibility reason to adopt it.passwordserver_sasl is Cyrus/BSD (cleanest to redistribute); retain Apple notices, mark modified files. See the research doc for the full source-availability audit.