183 KiB
mars-nwe NCP dispatch redesign notes
Architecture index
This file is the durable architecture/design record, not the active TODO list
and not patch history. Chronological patch state belongs in AI.md; active
open implementation work belongs in TODO.md. This index is only a map of
major design areas.
| Area | Status | Notes |
|---|---|---|
| Quota architecture | 🟩 Done | Linuxquota and NWQUOTA authority split is implemented, live-smoked, and covered by DOS board-tool smoke. |
| All-smoke log collection | 🟩 Done | Quota smokes collect per-volume logs and an nw.log slice into an uploadable bundle. |
| DOS namespace adaptation | 🟨 Active next | Adapt NSS DOS namespace semantics before broad 4.x work. |
| LONG/OS2 namespace adaptation | ⬜ Later | Start after DOS namespace is stable. |
| Salvage metadata/export model | 🟨 Planned | Keep .recycle compatibility, add NSS-shaped deleted metadata/xattrs for backup tools through shared libnwcore/libnwfs helpers. |
| NCP dispatch context cleanup | ⬜ Later | Replace magic forwarding results with named dispatch results and a typed request context. |
| Isolated live-test environment | ⬜ Later | Use the CMake/test build with generated SYS/QUOTA images once transport can run without host IPX. |
This file collects design notes for the internal NCP handoff path, filesystem
metadata direction, dependency boundaries and later service splits. It is
intentionally separate from TODO.md: the TODO file tracks concrete active
work, while this file describes architecture that can be implemented gradually.
The goal is not to rewrite MARS-NWE at once. The goal is to make the current handoff behavior explicit, reduce ambiguity around magic return values, and make future endpoint work easier to audit against the Novell/Micro Focus SDK, WebSDK, and NDK Core Protocols PDF.
The active-work dashboard lives in TODO.md and is intentionally allowed to list
unfinished long-running tracks even when their design is described here.
Salvage metadata and selective NSS low-level helper imports are still open until
implementation and tests close them.
Documentation boundary before namespace work
Logging compatibility boundary
The existing XDPRINTF mechanism is a numeric threshold model configured by
INI entries 100 through 106, not a clean severity taxonomy. The old configs
use 1 as the default for errors and notes and describe 99 as maximum debug.
For new namespace/libnwfs code, keep the compatibility surface narrow: accept the
legacy thresholds, but write normal call sites through semantic facade levels:
1=error, 2=warn, 3=info, 4=debug and 5=trace. Legacy levels 6..99
collapse into a single nwlog_detail() maintainer-diagnostic path that is
compiled out or returns 0 unless MAINTAINER_BUILD is active. There must be
no INI switch that enables maintainer-detail logging in production builds. Do not add zlog as a backend target: it is Apache-2.0 and not compatible with
the repository's GPL-2.0-only core policy. Keep backend choices behind
nwlog, and keep direct XDPRINTF or backend calls out of new libraries. Detailed observations live in
include/nwlog.h and doc/LOG_LEVEL_AUDIT.md.
Before the namespace implementation line starts, keep the repository documents separated by purpose:
TODO.mdis the active backlog and may keep the top implementation dashboard. It should list unfinished long-running work such as salvage metadata and selective NSS helper imports until implementation and tests really close them.REDESIGN.mdis this architecture record. It may describe namespace, salvage, transport and dependency models even while the matching TODOs remain open.AI.mdis only the assistant handoff/rules file. It should stay concise and should not accumulate duplicate historical patch dumps.ENDPOINTS.mdowns NCP selector audit tables with decimal and wire/code hex notation.- Focused topic files under
doc/own detailed audits and roadmaps, especially namespace, NSS public-core and salvage/compression/tool planning.
This boundary is important for the next namespace work: implementation patches
should not bury active tasks in REDESIGN.md, and design patches should not
turn TODO.md into a changelog.
Salvage metadata/export model
The salvage backend should remain compatible with the existing Samba-style
recycle repository: live deletes move payloads into .recycle. The next
redesign step is not to replace this with the NSS purge tree and not to keep
private .salvage JSON as a permanent metadata authority. Instead, adapt the
useful NSS metadata model so deleted objects carry authoritative
NetWare-visible deleted metadata in netware.metadata on the recycled payload.
The NSS pieces worth adapting are conceptual and structural:
zGET_DELETED_INFO/zMOD_DELETED_INFO: deleted time and deleter ID are a first-class part of a deleted object's information view.zNTYPE_DELETED_FILEandDeletedPersistentParentEntry_s: deleted names carry time and deleting user identity next to the parent/name identity.- volume salvage counters (
purgeableBytes,nonPurgeableBytes,numDeletedFiles,oldestDeletedTime, watermarks and keep seconds) are the right future vocabulary for reporting, even if MARS computes them by scanning.recyclepayloads withnetware.metadatarather than an NSS purge tree. - the NSS
netware.metadataxattr shape is the right export format for live and salvaged file metadata: attributes, timestamps, owner/archiver/modifier IDs, IRM and trustees.
The shared library direction is:
- Define a small
nwfs_deleted_snapshot/nwcore_salvage_snapshotstructure that represents the portable facts collected at delete time: source path, recycle path, original parent entry ID, original name, deleted time, deleted-by ID/name, DOS attributes, timestamps, trustees, IRM, AFP hints and size. - Move the common builders into libnwcore/libnwfs-style helpers:
- path normalization and safe relative repository path creation;
- NSS-shaped
netware.metadatainitialization/fill/validate; - deleted-info write/read helpers backed by xattrs;
- trustee/IRM copy-in and restore-out helpers;
- volume salvage counter calculation from
.recycleplusnetware.metadata.
- Keep NCP reply formatting in
namspace.c/nwconn.c, but make it consume the shared metadata view instead of re-parsing JSON independently. - Add tests for both views: NCP salvage scan/recover/purge still works, and a host-side backup-style xattr dump sees NSS-shaped metadata for salvaged content.
- Treat
.salvageJSON as legacy migration/debug data only, then remove the yyjson dependency when no required consumer remains.
Compressed primary streams need one extra rule: if a compressed file is deleted
into .recycle, the recycled payload should be materialized as a normal
uncompressed Linux file. Samba and host-side recycle tools can then inspect or
restore the payload without knowing about NWFS compression internals. The
previous compression descriptor belongs in netware.metadata so NCP recover can
restore/recompress according to volume policy.
This keeps compatibility with existing host-side recycle workflows while moving the metadata model toward NSS/OES semantics.
Current problem
The current NCP path grew around several cooperating processes and handlers:
nwconn.cowns the connection/session side and receives most packets first.nwbind.chandles bindery, queue, some server-management, and some final reply construction.- Other modules such as semaphore, message, namespace, AFP, file, salvage, and queue code implement individual protocol families or backend actions.
- Some calls are handled completely in
nwconn.c. - Some calls are forwarded to
nwbind.cby returning-1from thenwconn.cdispatcher. - Some calls are forwarded with saved request state by returning
-2, so thatnwconn.ccan do post-processing afternwbind.chas replied. - Some forwarded paths mutate request payloads before handoff.
- Some code paths build responses locally, while other paths rely on the target process to build the final completion code and payload.
This works, but it is hard to reason about while auditing endpoint layouts. The
same looking value can mean different things depending on which file it appears
in. For example, return(-1) in the relevant nwconn.c dispatcher path means
"forward this request to nwbind". A disabled return(-1) inside a #if 0
block in nwbind.c does not have that forwarding meaning and should not be
copied into active code.
The visible symptoms are:
- endpoint documentation must follow a handoff across files before it can say the request or reply layout is known;
- missing endpoints are difficult to distinguish from forwarded endpoints;
- request parsing, backend behavior, reply encoding, and process routing are often mixed in one switch block;
- byte order differences are easy to miss because parsing and reply writing are open-coded in different places;
- disabled future stubs can look like active dispatch behavior;
TODO.mdcan become a dumping ground for architectural observations that are not immediate endpoint bugs.
Desired shape
A cleaner long-term structure would have one small internal NCP dispatch layer:
wire packet
-> NCP envelope parser
-> NcpContext
-> endpoint lookup
-> endpoint handler / provider
-> reply encoder
-> central reply sender
This does not need to be a general-purpose message bus. A full message bus would probably be too large and too abstract for this code base. A typed internal NCP context plus explicit dispatch results would be enough.
The important separation is:
- decode the packet envelope;
- identify the endpoint;
- decode the endpoint request body;
- execute the backend operation;
- encode the endpoint reply body;
- send the response from one well-defined place.
Proposed NCP context
Introduce, in a later functional cleanup, a small context object that represents one NCP request while it moves through the server. The exact field names should fit the existing code style, but the conceptual shape would be:
typedef struct {
int connection;
uint16_t request_type; /* 0x2222, 0x3333, 0x5555, ... */
uint8_t function; /* top-level NCP function */
/*
* Some NCP families are only one level deep, but others are nested.
* The selector path records the bytes/words that identify the logical
* operation after the top-level function, without pretending that every
* family has exactly one byte-sized subfunction.
*/
int selector_count;
uint32_t selector[4]; /* e.g. subfunction, level, verb, info type */
const uint8_t *request;
int request_len;
uint8_t *reply;
int reply_cap;
int reply_len;
uint8_t completion;
uint8_t connection_status;
uint32_t flags;
} NcpContext;
The context should not replace all old globals in one patch. It can start as a thin wrapper around the existing request and response buffers, then gradually become the preferred handler interface.
The useful property is that endpoint documentation can point to a stable model:
functionidentifies the first NCP selector byte;selector[]identifies any nested selector path after that byte;requestandrequest_lenare the bytes after the already-decoded envelope;replyandreply_lenare the bytes before the common NCP response envelope;completionis set once by the handler or by central error handling.
Do not assume that the logical endpoint key always stops at
request_type/function/subfunction. The Novell documentation has several
families where an endpoint has another selector inside the subfunction payload.
Examples include NDS fragmented requests (0x2222/104/02) where the request
contains a 32-bit NDS verb, statistical calls such as 0x2222/123/34 where an
InfoLevelNumber selects the returned structure, NCP extension calls where the
extension number is dynamic, and reply formats that vary by information type.
The audit notation for such cases should make the nesting explicit, for example
0x2222/104/02 verb=... or 0x2222/123/34 level=2, instead of flattening it
into an invented one-byte zz case.
Replace magic return values with named results
The current 0, -1, and -2 convention should be made explicit before any
larger refactor. The first step can be documentation-only or macro-only:
#define NCP_LOCAL_DONE 0
#define NCP_FORWARD_NWBIND -1
#define NCP_FORWARD_NWBIND_POST -2
A later cleanup can replace those with an enum:
typedef enum {
NCP_DISPATCH_DONE,
NCP_DISPATCH_FORWARD_BIND,
NCP_DISPATCH_FORWARD_BIND_POST,
NCP_DISPATCH_NOT_IMPLEMENTED,
NCP_DISPATCH_BAD_REQUEST,
NCP_DISPATCH_INTERNAL_ERROR
} NcpDispatchResult;
The important rule is that the meaning must be scoped. A named result returned
from a nwconn.c dispatcher may request process handoff. A return statement in
nwbind.c should not silently inherit that meaning unless the function is
explicitly part of the same dispatch interface.
Endpoint table as audit index first
Before replacing switch statements, add an endpoint inventory table as a non-invasive audit aid. It can be compiled only for debug builds or kept as a source-level documentation table.
Conceptual form:
typedef struct {
uint16_t request_type;
uint8_t function;
int selector_count;
uint32_t selector[4];
const char *selector_note;
const char *name;
const char *provider;
uint32_t flags;
} NcpEndpointDoc;
Example entries:
{ 0x2222, 23, 1, { 109 }, "subfunction", "Change Queue Job Entry old", "nwbind/queue", NCPDOC_FORWARDED },
{ 0x2222, 32, 1, { 0 }, "subfunction", "Open Semaphore old", "sema", NCPDOC_LOCAL },
{ 0x2222, 33, 0, { 0 }, NULL, "Negotiate Buffer Size", "nwconn", NCPDOC_LOCAL },
/* Later NetWare 4.x examples that need more than one logical selector. */
{ 0x2222, 104, 2, { 2, 0 }, "subfunction + NDS verb", "Send NDS Fragmented Request/Reply", "nwnds", NCPDOC_FUTURE },
{ 0x2222, 123, 2, { 34, 2 }, "subfunction + info level", "Get Volume Information by Level", "servermgmt", NCPDOC_FUTURE },
This table would help with the ongoing endpoint audit:
- SDK/PDF/WebSDK listed and implemented;
- SDK/PDF/WebSDK listed and forwarded;
- SDK/PDF/WebSDK listed but disabled as a future stub;
- SDK/PDF/WebSDK listed but absent from the current compatibility target;
- planned NetWare 4.x endpoint, not part of the default NetWare 3.x compatibility target, or later 5.x/OES/MOAB/newer endpoint recorded as prose-only/out-of-scope with no disabled source stub.
The first version should not drive runtime dispatch. It should only make review and missing-endpoint checks less error-prone.
The initial shared vocabulary for this audit now lives in
include/ncp_endpoint.h. That header is intentionally declarative: it names
logical providers, endpoint flags, current compatibility dispatch results, and
future handoff reply kinds without changing runtime dispatch. Keep it as a
review aid until the active nwconn.c magic handoff sites are annotated.
The table should be able to represent a selector path rather than only a single
subfunction. This matters for later NetWare 4.x families and for extension
mechanisms. The first selector element is usually the documented subfunction
byte, but later elements may be 16-bit or 32-bit fields from the request body,
not dispatch bytes in the classic switch sense. Treat them as layout selectors,
not as automatic nested switch cases unless the code actually dispatches on
them.
Handler structure
For newly touched endpoint families, prefer the following logical split even if it remains in one C function at first:
request decode
-> validation
-> backend operation
-> reply encode
For complex endpoints this could become explicit helper functions:
static int decode_foo(NcpContext *ctx, FooRequest *out);
static int exec_foo(NcpContext *ctx, const FooRequest *req, FooReply *reply);
static void encode_foo(NcpContext *ctx, const FooReply *reply);
This is especially useful for endpoint families where the audit has already found old/new layout differences:
- 16-bit old queue job numbers versus newer 32-bit job numbers;
- big-endian versus little-endian SDK notation;
- old short replies versus newer long replies;
- connection-side prehandling that inserts or rewrites fields;
- bindery or queue paths that build final replies in a different process.
Small endpoints do not need three separate helper functions if that would make the code noisier. The rule is that request bytes and reply bytes should be easy to identify and compare with the SDK documents.
Make handoff explicit
Forwarded calls should say exactly what is handed off. A good comment should answer:
- which bytes are forwarded;
- whether the subfunction byte is preserved or stripped;
- whether
nwconn.cmutates the request before forwarding; - whether
nwbind.cor another provider builds the final reply; - whether
nwconn.cexpects post-processing after the provider reply.
Examples of handoff cases that need this clarity:
- Queue calls where
nwconn.cexpands paths or inserts job file handles beforenwbind.csees the request. - Quota/bindery prehandling where the destination handler receives an already transformed request.
- Semaphore and message groups that are grouped in the SDK but routed through local helper modules.
- Direct lifecycle calls such as End Of Job and Logout where local cleanup and final success reply are split across files.
The preferred future style is not "nwbind must do the rest" but something like:
Forward to nwbind with the original subfunction byte and payload unchanged.
No nwconn post-processing is expected; nwbind builds the completion-only reply.
or:
Forward to nwbind after saving the original request. nwbind validates bindery
state and returns the bindery result; nwconn then performs the file-handle
post-processing in handle_after_bind().
Response building rule
Every endpoint audit should identify the reply builder, not only the request parser. A handler is not fully documented until the response path is known.
For each endpoint family, record:
- completion-only reply;
- fixed-size payload reply;
- variable-length payload reply;
- provider-built reply;
nwconn.cpost-processed reply;- intentionally unsupported reply status.
Long-term, response sending should become centralized enough that endpoint code only encodes payload bytes and a completion code. This reduces off-by-one reply length bugs and makes the logs easier to normalize.
Normalized inter-process handoff replies
The process handoff path should be normalized before adding more provider
processes. The current nwconn to nwbind forwarding path relies on magic
return values and implicit shared-buffer conventions. That is workable for the
historic two-process case, but it will not scale cleanly to future providers such
as nwqueue, nwnds, or a directory service.
The long-term rule should be:
Every provider process returns exactly one formal internal handoff reply
object for every internal handoff request it accepts. There is no implicit
"no return" success path.
That internal reply is not the same thing as a client-visible NCP reply. A provider may explicitly say that no client-visible reply should be sent, but it must still send a formal internal handoff reply object back to the caller. This avoids silent success/failure paths and makes timeout/error handling deterministic.
Conceptual reply kinds:
typedef enum {
NW_HR_REPLY, /* provider produced a client NCP reply payload */
NW_HR_NO_CLIENT_REPLY, /* handled; nwconn must not send a client reply */
NW_HR_DEFERRED, /* accepted; final reply/event will be produced later */
NW_HR_FORWARD, /* provider requests forwarding to another provider */
NW_HR_ERROR /* internal provider or handoff failure */
} NwHandoffReplyKind;
The normal successful completion-only case is still a reply:
kind = NW_HR_REPLY
completion = 0x00
reply_len = 0
The true "do not answer the client" case is explicit:
kind = NW_HR_NO_CLIENT_REPLY
reply_len = 0
Do not encode this as text in a payload such as "no reply". It should be a
machine-readable reply kind so that nwconn can make one central decision.
A conceptual internal handoff reply header could look like this:
typedef struct {
uint16_t version;
uint16_t kind;
uint32_t request_id;
uint32_t connection_id;
uint32_t sequence;
uint32_t task_id;
uint8_t completion;
uint8_t connection_status;
uint32_t flags;
uint32_t reply_len;
} NwHandoffReply;
The matching request should carry the same correlation fields plus the NCP selector path and payload length. The exact structure can follow existing mars-nwe style, but the contract should be stable:
nwconn -> provider:
request_id, connection_id, sequence, task, selector path, request payload
provider -> nwconn:
same request_id, reply kind, completion/status, reply payload length, payload
This gives future provider processes a uniform contract:
nwconn -> nwbind
nwconn -> nwqueue
nwconn -> nwnds
nwconn -> nwdirectory
all use the same shape. Provider-specific behavior belongs in the payload and provider API, not in special process-specific return conventions.
Reply ownership
The preferred long-term ownership rule is:
Provider builds the logical reply payload and completion/status.
nwconn owns the final client NCP response envelope and sends it to the client.
This means providers should not directly send client packets. They return an
internal result that says what should happen. nwconn then applies the original
sequence, connection number, task, transport, and NCP envelope rules in one
place.
This avoids several classes of bugs:
- duplicate replies;
- wrong sequence or task in replies;
- inconsistent completion-only reply lengths;
- provider-specific send/error paths;
- unclear post-processing after
nwbindreplies; - future provider processes needing to know transport details.
Legacy paths may continue to have provider-built replies during migration, but that should be marked as a legacy compatibility mode, not the design target.
Post-processing without return(-2)
The current return(-2) convention means roughly "forward to bindery, save the
original request, then let nwconn do more work after the provider reply". In a
normalized handoff this should become explicit state, not a magic result value.
Possible flags:
#define NW_HF_SAVE_ORIGINAL_REQUEST 0x00000001
#define NW_HF_POSTPROCESS_REPLY 0x00000002
#define NW_HF_LEGACY_PROVIDER_REPLY 0x00000004
The request context can also name the post-processing hook, or store a small post-processing enum, so the provider does not need to know why the caller will continue after the reply. This keeps the handoff transport generic.
Error mapping and dead-provider behavior
The handoff layer should define what happens when a provider fails before a protocol handler can return a normal NetWare completion code. Otherwise every new provider will invent a slightly different failure path.
The normalized rules should cover:
- provider process not running;
- provider closes the IPC channel;
- malformed internal reply;
- mismatched
request_id; - provider timeout;
- reply payload longer than negotiated capacity;
- provider returned
NW_HR_ERRORwith an internal error code; - provider returned
NW_HR_FORWARDto an unsupported target.
For client-visible requests, nwconn should map those failures through one
central function to either an NCP completion code, a connection-level failure, or
an intentional disconnect. Endpoint handlers should not open-code provider IPC
failures as arbitrary completion bytes.
Correlation and replay safety
Every internal handoff request should carry a monotonically useful correlation
identifier, even if the first implementation is only local to one nwconn
process. The tuple should include enough information to catch stale or crossed
replies:
request_id + connection_id + sequence + task_id
This matters once there are multiple provider processes or deferred replies. It also makes logging and debugging much easier, because an endpoint audit can show the complete path of one request through several processes.
Payload ownership and size limits
The handoff protocol should define who owns buffers and what size limits apply. At minimum, document these rules before functional refactoring:
- request payload is immutable after handoff unless a mutating legacy wrapper is explicitly documented;
- provider reply payload length must be checked against caller capacity;
- variable-length replies must report exact encoded length;
- zero-length payload is valid for completion-only replies;
NO_CLIENT_REPLYis a reply kind, not a zero-length success reply;- byte order inside payloads remains the NCP endpoint's documented wire order, not native host order.
This is especially important for nested selector families and old/new endpoint variants, where a provider may need to choose different reply structures based on a level, verb, or information type.
Logging and audit benefit
A normalized handoff reply gives logging one consistent shape:
REQ id=42 conn=7 seq=19 ncp=0x2222/23/113 provider=queue len=...
RPLY id=42 conn=7 seq=19 kind=REPLY completion=0x00 len=...
RPLY id=43 conn=7 seq=20 kind=NO_CLIENT_REPLY reason=...
ERR id=44 conn=7 seq=21 provider=bindery error=timeout mapped=0xfb
This would also make the endpoint documentation pass easier: each audited endpoint can identify the provider, the request layout, the logical reply kind, the reply payload layout, and any caller-side post-processing.
NSS message-layer references
The imported NSS sources contain several useful references for handoff design, but they should not be treated as a drop-in MARS IPC runtime. The relevant patterns are:
include/nwfs/nss/sdk/include/msg.hand relatedmsg*.hfiles define an object/key/method/message vocabulary withMSG_Call,MSG_Send,MSG_SendKey,mpkMSG_CallandzMSG_Call. This is useful precedent for method-based provider calls, but the NSS Door/Object runtime is too large and too NSS-specific for the MARS provider boundary.src/nwfs/nss/common/fsmsg.candfsmsg.hshow NSS filesystem method tables. These are useful for future libnwfs object methods, not for replacing the NCP endpoint dispatcher.include/nwfs/nss/sdk/include/nsskr.hand theNSSKR_REQUESThandling insrc/nwfs/nss/lsaSuper.cshow a simple signature/version/opcode request header with typed payloads. That is a good reference for a future native MARS handoff header.
doc/HANDOFF_AUDIT.md records the current MARS magic-return sites and the NSS
references in one focused place.
Migration order for handoff normalization
The safe order is:
- document current
nwconn/nwbindhandoff behavior; - add names for current magic values without changing behavior;
- add a small wrapper such as
ncp_handoff_to_provider()that still calls the old path internally; - introduce a formal internal reply object in the wrapper;
- make the wrapper always return a formal reply, including
NO_CLIENT_REPLY; - centralize final client reply sending in
nwconnfor converted paths; - only then attach future providers such as
nwqueueornwnds.
The rule is: do not create a new provider process until the caller can receive a formal reply from it and can handle provider failure centrally.
nwserv as control plane, not data-plane router
Future provider processes need a way to find and trust each other, but normal
request payloads should not all be routed through nwserv. nwserv should stay
the supervisor and registry for the mars-nwe process tree. It should not become
a central payload broker for every decoded NCP request.
The preferred split is:
nwserv:
control plane
process supervision
provider registry
endpoint/socket ownership
restart and shutdown coordination
nwconn <-> provider:
data plane
direct request/reply IPC
normalized handoff messages
So a future request path should look like this:
client -> nwconn -> direct provider IPC -> provider -> nwconn -> client
not like this:
client -> nwconn -> nwserv -> provider -> nwserv -> nwconn -> client
nwserv may still create, own, or advertise IPC endpoints. For example, it can
start nwbind, nwqueue, nwdirectory, or nwnds, create a protected runtime
directory such as /run/mars-nwe, assign socket paths or inherited file
descriptors, and record which provider is currently alive. A nwconn process
can then discover provider endpoints from configuration, inherited descriptors,
or a small nwserv registry query. After discovery, normal handoff traffic
should go directly to the provider.
This keeps nwserv small and avoids several failure modes:
- no extra copy and latency for every NCP handoff;
- no single data-plane bottleneck;
- no need for
nwservto understand every provider payload; - fewer decoded password/auth/directory payloads visible to the supervisor;
- easier provider-specific timeouts and back-pressure;
- clearer ownership:
nwconnowns the client connection, providers own their service logic, andnwservowns lifecycle.
The kinds of messages that should go through nwserv are control messages:
- provider started, registered, unhealthy, or exited;
- provider restart requested or refused;
- global shutdown or graceful drain;
- configuration reload notification;
- socket/FD registration and permission setup;
- health and version/capability queries.
The kinds of messages that should not normally go through nwserv are data-plane
messages:
- decoded NCP request payloads;
- Bindery object/property operations;
- Queue job lifecycle operations;
- Directory/NDS authentication or schema operations;
- file/volume provider payloads;
- provider replies carrying completion/status and reply payloads.
There can be narrow exceptions during migration, especially for existing legacy
nwconn/nwbind plumbing, but those exceptions should be documented as legacy
wrappers. New provider processes should be designed around direct normalized IPC
from the caller to the provider.
This also fits the secure IPC policy: local direct IPC can use protected
Unix-domain sockets, pipes, or inherited descriptors. If a future provider is
connected over TCP instead, that specific provider IPC channel must use the
separate MatrixSSL/mTLS policy described below. nwserv discovery must not be used
as an excuse to downgrade provider data-plane traffic to plaintext TCP.
Secure internal provider IPC and client transport compatibility
Provider handoff security and client transport compatibility are separate
concerns. Future TCP/IP support for client-facing NCP/NDS traffic must not
imply that every historic NetWare 4.x-compatible client is required to speak
TLS. Classic compatibility should remain protocol-accurate first; TLS for
client-facing protocols should only be enabled where the real protocol/frontend
supports it, such as LDAP/LDAPS/StartTLS on nwdirectory or a later explicitly
secure client protocol.
Internal provider IPC is different. Once a request has been decoded or partially
decoded by nwconn, the internal handoff payload can contain sensitive material:
password data, login/authentication material, Bindery object properties, Queue
state, future NDS credentials, or directory attributes. A plaintext TCP-based
provider handoff would expose more than the original network packet boundary did.
The design rule should be:
local provider IPC:
Unix-domain sockets or pipes are acceptable when filesystem permissions,
process ownership, and socket directories are strict.
TCP-based provider IPC:
never plaintext; MatrixSSL-backed TLS with mutual authentication is required.
So the local default may remain simple and compatible:
nwconn -> nwbind local pipe/Unix-domain IPC, strict permissions
nwconn -> nwqueue local pipe/Unix-domain IPC, strict permissions
nwconn -> nwnds local pipe/Unix-domain IPC, strict permissions
But any future provider split over TCP, even if it is only localhost TCP or a
container boundary, must be treated as a secure channel:
nwconn -> provider over TCP:
MatrixSSL required
mutual authentication required
no anonymous TLS
no opportunistic downgrade to plaintext
clear failure if certificates/keys are missing or invalid
This is intentionally stricter than client-facing compatibility. Historical NetWare 4.x/NDS clients should not be forced to use TLS just because internal provider IPC can use TLS. The two policy surfaces are independent:
Client NCP/NDS transport:
compatibility first; no blanket TLS requirement for classic clients.
LDAP/LDAPS/StartTLS:
handled by nwdirectory; MatrixSSL is appropriate at that network edge.
Internal provider IPC over TCP:
always MatrixSSL/mTLS because decoded server-internal data may cross it.
Local IPC still needs security rules even without TLS:
- IPC sockets/directories should not be world-readable or world-writable;
- provider processes should run as the expected user/group;
- raw handoff payloads must not be logged by default;
- debug dumps should redact or omit authentication payloads;
- sensitive request/reply buffers should be wiped when practical after use;
- core dumps for processes handling credentials should be considered unsafe by default;
- formal handoff logging should record selector paths, provider names, reply kinds, and completion codes, not raw secret bytes.
This also means there are two different MatrixSSL-backed TLS uses in the long-term design:
1. LDAP TLS:
external LDAP/LDAPS/StartTLS clients -> nwdirectory
2. Provider IPC TLS:
mars-nwe process -> mars-nwe provider process, only when the provider IPC
transport is TCP-based
The two uses should have separate configuration and credentials. Reusing an external LDAP certificate as an internal provider trust root should not be the default assumption. A later setup tool can create or install a local mars-nwe provider-IPC CA/key set if TCP-based provider IPC ever becomes supported.
Provider boundaries
A clean design would treat the existing modules as providers instead of hidden fallback paths:
nwconn connection/session, packet IO, top-level envelope
ncpdispatch endpoint lookup, handoff policy, common errors
nwbind bindery database and bindery-backed services
queue queue metadata, jobs, print queues, and direct-spool adapter
sema semaphore state
message station/message/broadcast state
namespace path, directory handle, name-space operations
file file handle and read/write/open/close operations
salvage deleted-file scan/recover/purge backend
AFP AFP metadata and AFP namespace adapter
extension future NCP Extension registry/executor for 36/37, if enabled
This is a design target, not a demand to move files immediately. The important
part is that future code should avoid making nwbind a catch-all sink for
unrelated NCPs just because it already has an IPC path.
Source and header subtree layout
Large-file cleanup should preserve the same logical ownership names used by the provider design. Split large modules into matching source and header subtrees instead of creating unrelated flat files:
src/
nwconn/
nwconn.c
broadcast.c
sync.c
files.c
serverinfo.c
timesync.c
rpc.c
extension.c
nwbind/
nwbind.c
objects.c
properties.c
sets.c
auth.c
monitor.c
nwqueue/
nwqueue.c
jobs.c
print.c
nwnds/
nwnds.c
objects.c
schema.c
nwdirectory/
nwdirectory.c
path.c
namespace.c
volume.c
trustees.c
include/
nwconn/
nwconn.h
broadcast.h
sync.h
files.h
serverinfo.h
timesync.h
rpc.h
extension.h
internal.h
nwbind/
nwbind.h
objects.h
properties.h
sets.h
auth.h
monitor.h
internal.h
nwqueue/
nwqueue.h
jobs.h
print.h
internal.h
nwnds/
nwnds.h
objects.h
schema.h
internal.h
nwdirectory/
nwdirectory.h
path.h
namespace.h
volume.h
trustees.h
internal.h
The old flat module headers remain as compatibility and convenience umbrella
headers. For example, include/nwbind.h should include the public headers
under include/nwbind/*.h; callers that need all public bindery declarations
can keep including nwbind.h, while split implementation files may include
narrower headers such as nwbind/monitor.h or nwbind/properties.h. Use the
same umbrella pattern for nwconn.h, nwqueue.h, nwnds.h, and
nwdirectory.h as those modules move.
include/<module>/internal.h is a private module header. It may only be
included by files in the matching src/<module>/ subtree. If another module
needs a declaration, that declaration belongs in an explicit public header under
include/<module>/ and can be re-exported by the flat umbrella header.
This layout is compatible with the later provider split, but it is not the same
thing as a process split. A mechanical move from src/nwconn.c to
src/nwconn/sync.c, or from src/nwbind.c to src/nwbind/monitor.c, must not
change runtime behavior. Build-list changes and include-path changes belong in
the same mechanical move patch; endpoint semantics, provider IPC, and switch
cleanup belong in later semantic patches.
Provider boundary versus process boundary
A provider boundary is not the same thing as a Unix process boundary. This is an important distinction because splitting every NCP family into a separate process would make the server harder to debug and could introduce new ordering, locking, and reply-ownership bugs.
The preferred rule is:
first define logical providers;
only later promote the few large stateful providers to separate processes.
A logical provider can start as an ordinary C module called from the existing process path. It becomes valuable as soon as the dispatch table can say "this endpoint belongs to the queue provider" or "this endpoint belongs to the connection-local provider", even if no new process exists yet. A process split should be treated as an implementation detail that is only justified when the provider has enough independent state and lifecycle to benefit from isolation.
This keeps the redesign incremental:
now:
nwconn switch -> existing local code or nwbind handoff
first cleanup:
nwconn switch -> provider-named helper/module
later, only where useful:
nwconn/dispatcher -> IPC -> provider process
Good process candidates
Bindery
Bindery is already a natural service boundary. It owns long-lived server state:
objects, properties, sets, security, password/login/key handling, and object
lookup. Keeping bindery behind a clear provider boundary is appropriate, and the
existing nwbind process can remain that boundary while the dispatch layer is
cleaned up.
The main cleanup is not to remove nwbind, but to stop treating it as a generic
catch-all for unrelated forwarded requests. A future endpoint table should mark
true bindery calls as bindery, and queue or management calls should not be
classified as bindery merely because their current implementation lives in
nwbind.c.
Queue / possible nwqueue
Queue management is the strongest candidate for a future separate process after bindery. Queue handling has its own domain state:
- queue objects and queue metadata;
- queue job lifecycle;
- queue server attach/detach state;
- service, finish, and abort state;
- job position and priority;
- client-rights transitions during job servicing;
- queue directories and spool/job files.
That is large enough to deserve a logical queue provider even before any
runtime split. A future nwqueue process can be considered once request/reply
ownership and bindery access are explicit.
The first step should only be a provider split:
0x2222/23 queue subfunctions -> queue provider
queue provider -> bindery provider/library for object/security/property checks
queue provider -> file/path helpers for queue job files
A real nwqueue process should not be created by simply moving the current queue
cases out of nwbind.c. It needs an explicit contract for:
- which process owns the final NCP reply;
- how queue calls read bindery objects and properties;
- how queue job files are opened and handed back to the connection process;
- how connection cleanup affects attached queue servers and in-service jobs;
- how old 16-bit job-number calls and newer 32-bit job-number calls are kept compatible.
Until those contracts are clear, nwqueue should remain a design target, not an
immediate functional change.
NCP Extension provider
The 0x2222/36 NCP Extension information family and the direct
0x2222/37 Execute NCP Extension call are a planned NetWare-4.x-era
compatibility surface, not a reason to route arbitrary extension payloads
through nwserv.
If implemented later, they should use a small extension registry/provider that
can report registered extension numbers, names, versions, custom data, maximum
data sizes, and dispatch 37 execution payloads to explicitly registered
handlers. nwserv may supervise provider processes and publish control-plane
registration information, but it must not become the normal data-plane broker
for extension request/reply payloads.
Until such a registry exists, the source should keep 36/37 behind disabled
MARS_NWE_4 stubs that return unsupported rather than pretending that arbitrary
NLM extension execution is available.
Direct print/spool NCPs versus existing queue printing
Printing needs a small extra distinction because mars-nwe already has
queue-backed printing paths even though the old direct 0x2222/17 Print/Spool
NCP family does not have an active top-level dispatcher today. The redesign
should not describe "printing" as entirely absent. Instead, keep these two
compatibility surfaces separate:
queue printing
existing queue/job based printing support, tied to print queues and backend
execution such as Unix print commands
direct print/spool NCPs
old `17/xx` client compatibility calls such as create/write/close spool file,
printer status, and printer queue lookup
A future implementation of the direct 17/xx calls should bridge into the
existing queue printing mechanics where possible instead of inventing a second
print system. Conceptually, direct spool-file calls can become compatibility
front-ends that create or update queue jobs, while printer-status and
printer-queue calls should consult the same queue/backend state used by the
queue provider.
This means the logical owner is still the queue/print-spool provider area,
not nwnds and not the directory provider. If nwqueue later becomes a real
process, it should be the natural home for both queue-management NCPs and the
old direct Print/Spool compatibility bridge. Until then, the direct 17/xx
stubs should remain documentation-only and must not obscure the fact that
queue-based printing already exists.
Possible but risky process candidates
File and volume subsystem
The file/volume/name-space area is large and stateful, so it can look like a candidate for a separate process. It owns or touches directory handles, file handles, locks, trustee evaluation, volume information, name spaces, salvage and purge operations, and Unix filesystem mapping.
However, this area is also tightly coupled to connection state and existing file descriptor ownership. Moving it behind IPC too early could create more problems than it solves. The safer path is:
first: file/volume/name-space provider modules inside the current process model
later: consider a process split only after handle ownership is explicit
A file provider boundary is useful for documentation and dispatch cleanup. A separate file process is optional and should be considered high-risk.
Accounting
Accounting is a maybe. It has a separate protocol domain, but in many setups it
may be small enough to stay as an in-process provider. A process boundary only
makes sense if accounting grows into a real persistent service with charges,
holds, notes, audit records, and recovery behavior that should be isolated from
connection handlers. The legacy Accounting NCPs (23/150 through 23/153) can
also be used by accounting/print servers to hold, charge, and note service work,
so future queue-printing integration should call into the accounting provider
rather than duplicating balance/hold/audit behavior inside the queue provider.
Poor process candidates
Semaphore
Semaphore calls should have a clean provider boundary, but a dedicated process is
probably overkill. The old semaphore group is small: open, examine, wait,
signal, and close. It needs shared state, but not necessarily a standalone
process. A sema provider module with clear request/reply ownership should be
enough unless later testing shows that cross-connection semaphore state cannot be
managed safely in the existing process model.
Connection lifecycle and session-local calls
Connection lifecycle operations should stay with nwconn or a connection-local
provider. Calls such as Logout, End Of Job, watchdog handling, buffer
negotiation, and connection-state cleanup are fundamentally tied to the session
that received the packet. Moving them into another process would make cleanup
ordering and error handling harder.
Server-management provider and possible nwservermgmt process
Server-management and information calls should first become a clear logical
provider, not an immediate payload route through nwserv. The servermgmt
provider covers login-status queries, server description strings, server time,
NetWare-4.x TimeSync compatibility stubs, console-privilege checks, small
broadcast/control helpers, the NetWare-4.x 0x2222/123 server-information and
statistics family, and guarded NetWare-4.x RPC/server-control calls.
Small calls may remain in-process while they only adapt local state. For
example, 0x2222/123 should adapt existing mars-nwe, host, transport,
filesystem, and queue state into NetWare-compatible information replies rather
than grow a second management database. TimeSync should remain a host-time
adapter. Console/status queries should not require a new daemon merely to
return static or already-known information.
However, once multiple NetWare-4.x management families are implemented, a
dedicated nwservermgmt process becomes a reasonable future process boundary.
That process would own the server-management provider data plane for 123/xx,
114/xx, selected 23/200+ console/server-management calls, and guarded
131/xx RPC/server-control requests. It may query nwserv for supervised
process status, provider capabilities, restart state, or registered endpoints,
but it must not replace nwserv as supervisor.
The intended split is:
nwserv
control plane / supervisor / provider registry
starts, monitors, restarts, and publishes provider endpoints
does not broker decoded NCP management payloads
nwservermgmt
optional future server-management provider process
handles server-info, TimeSync adapter, console/status, and guarded RPC surfaces
asks nwserv only for control-plane state when needed
nwconn
sends normalized handoff requests directly to nwservermgmt if it exists
remains final owner of the client NCP reply envelope
NetWare-4.x RPC server-control calls such as load/unload NLM, mount/dismount
volume, SET command changes, and NCF execution must remain disabled until there
is a real privilege model and backend behavior. A future nwservermgmt process
may be the right owner for those checks, but it must still use normalized
handoff replies and must not fake success for operations that change server
state.
TimeSync should adapt host time discipline, not implement NTP
The NetWare-4.x 0x2222/114 Time Synchronization compatibility provider should
not grow into a full NTP implementation. That would duplicate operating-system
infrastructure and create a new time-discipline service inside mars-nwe.
The preferred design is an adapter:
NCP 114 TimeSync request
-> servermgmt/time provider
-> host system clock and configured time service
(ntpd, chrony, systemd-timesyncd, or local administrator policy)
-> NetWare-compatible TimeSync reply
For early compatibility, 114/01 Get Time can be backed by the local system
clock. Future 114/02 Exchange Time and server-list queries may report the
configured local policy or host time-service status, but mars-nwe should not try
to manage or reconfigure ntpd, chrony, or systemd-timesyncd at runtime. In
particular, 114/06 Set Server List should not be wired to fake success or to
blindly rewrite host NTP configuration until there is an explicit, safe admin
policy for that behavior.
This keeps TimeSync in the servermgmt provider boundary and avoids adding a
new process. It also keeps the security model simple: the host remains
responsible for disciplining time; mars-nwe only translates that state into the
NetWare-compatible NCP surface.
Suggested provider map
The endpoint audit table should be able to use provider names like these:
local packet/session-local handling in nwconn
bindery object/property/security/login backend
queue queue objects, jobs, queue servers, print queues, direct-spool bridge
filesystem file, directory, volume, namespace, trustee, salvage helpers
semaphore semaphore state and old 0x2222/32 calls
message station messaging and broadcast helpers
servermgmt server-management, time-sync, information, and guarded RPC/control calls
accounting account status, charges, holds, notes
AFP AFP namespace and metadata helpers
unknown documented but not yet mapped
Only some providers should ever become processes:
already process-like: bindery / nwbind
likely future process: queue / possible nwqueue, including queue printing/direct-spool bridge
reasonable future process: servermgmt / possible nwservermgmt once 123/114/131/23-management are real
maybe, high risk: filesystem
usually in-process: semaphore, message, accounting, AFP helpers
The practical design rule is:
Use provider names everywhere in documentation and endpoint tables.
Use new processes only where shared state, isolation, and lifecycle justify the
extra IPC complexity.
Future NetWare 4.x directory, LDAP, and storage direction
NetWare 4.x support should not be added by letting nwbind grow into a second
large catch-all service. The long-term directory design should keep the legacy
Bindery, the future NDS compatibility layer, and the LDAP protocol frontend as
separate logical layers above one shared directory store.
The intended naming model is:
libflaim
persistent embedded database engine
libnwds
future shared internal NetWare Directory Services API used by nwbind,
future nwnds, nwsetup/provisioning tools, and nwdirectory integration code
owns mars-nwe/NDS-facing object, schema, rights and authentication policy
calls libnwdirectory instead of speaking LDAP or FLAIM directly
libnwdirectory
the tinyldap-derived directory library built today as libnwdirectory
currently owns LDAP helper code plus flatfile/mmap/journal storage helpers
remains the place where the storage backend is selected
later uses libnwflaim when the FLAIM backend replaces the flatfile backend
libnwflaim
later persistent database backend used by libnwdirectory
nwdirectory
mars-nwe service/binary name for the integrated tinyldap-derived LDAP server
owns LDAP/LDAPS/StartTLS protocol handling
uses MatrixSSL/libnwssl only at the LDAP network/TLS edge
uses libnwdirectory and, later, libnwds-facing policy glue
nwnds
future NetWare 4.x/NDS compatibility layer
owns NDS/NCP directory semantics, contexts, tree-oriented operations,
NetWare-specific rights/auth behavior, and later compatibility glue
calls the directory core/store directly
nwbind
legacy NetWare 2.x/3.x Bindery compatibility layer
maps Bindery objects, properties, sets, security, and login-visible behavior
onto the shared directory core/store where possible
In this model, nwdirectory is not a separate design from tinyldap. It is the
mars-nwe integration name for the tinyldap-derived LDAP directory service, so
that the installed binary/module follows the existing nw* naming scheme. The
upstream tinyldap code can provide the LDAP protocol implementation, but the
project-facing component should be named nwdirectory.
libnwds is the future NetWare Directory Services policy/API boundary, but it
should sit above libnwdirectory rather than replacing the tinyldap-derived
library name. libnwdirectory already exists as the library output for the
nwdirectory server code. It should remain the LDAP/helper/storage library
whose backend can later switch from flatfile/mmap/journal to FLAIM. libnwds
can then provide the common NetWare/NDS-facing operations that legacy Bindery,
future NDS compatibility, setup code, and the LDAP frontend need:
dir_open_store();
dir_close_store();
dir_txn_begin();
dir_txn_commit();
dir_txn_abort();
dir_object_create();
dir_object_delete();
dir_object_lookup_by_id();
dir_object_lookup_by_name();
dir_object_search();
dir_attr_get();
dir_attr_set();
dir_attr_delete();
dir_acl_check();
dir_auth_verify();
dir_schema_get();
The exact function names are placeholders, but the ownership rule is important:
NetWare protocol handlers should call a directory API, not encode LDAP requests
to reach local server state. If nwdirectory later runs as a separate process,
libnwds can either remain the shared embedded store library or define the
internal IPC contract. In both cases the protocol layers still depend on the
directory API, not on LDAP text/protocol behavior.
nwnds should remain a separate layer because LDAP is only one protocol view of
the directory. NDS has NetWare-specific semantics that should not be forced into
the LDAP frontend. Conversely, LDAP clients should not be required to pass
through the NDS/NCP compatibility handler just to reach the directory database.
The preferred relationship is sibling frontends above a shared directory API,
with the storage backend hidden below libnwdirectory:
+----------------------+
| libnwds |
| NetWare DS semantics |
+----------+-----------+
|
+----------v-----------+
| libnwdirectory |
| flatfile now, FLAIM |
| selected later |
+----------+-----------+
^
+---------------+---------------+
| |
nwdirectory nwnds
tinyldap-based LDAP/LDAPS NetWare 4.x/NDS semantics
frontend, MatrixSSL TLS edge NCP/NDS compatibility layer
^ ^
| |
LDAP clients NetWare/NDS clients
The legacy Bindery service should also move toward this shared API over time:
NetWare 3.x client -> Bindery NCP -> nwbind -> libnwds -> libnwdirectory -> storage
LDAP client -> LDAP/LDAPS -> nwdirectory -> libnwdirectory -> storage
NetWare 4.x client -> NDS/NCP -> nwnds -> libnwds -> libnwdirectory -> storage
That means nwbind should become a compatibility mapping over directory objects
and attributes instead of maintaining a completely separate long-term identity
truth. This is especially important once NetWare 4.x/NDS support exists, because
Bindery compatibility can then be implemented as a legacy view of the same
underlying users, groups, properties, and rights data.
The internal path should not be:
nwbind -> LDAP protocol -> nwdirectory -> directory store
nwnds -> LDAP protocol -> nwdirectory -> directory store
Using LDAP as the mandatory internal storage API would mix protocol concerns into
server internals, make old Bindery behavior harder to preserve, and add needless
encoding/search semantics between tightly coupled modules. LDAP should remain an
external protocol frontend. nwbind, future nwnds, setup/import tooling, and
provider code should use libnwds, or a clearly defined IPC protocol modeled
after that API, to reach directory state. The nwdirectory LDAP server may use
libnwdirectory directly for its LDAP storage path and use libnwds only where
NetWare/NDS policy glue is needed.
FLAIM should therefore be treated as the later persistent storage engine below
libnwdirectory, not as an LDAP-only database and not as something to wire into
LDAPv2 work immediately. libnwds owns the NetWare-facing schema, object model,
rights/authentication policy and provisioning contract. libnwdirectory owns
the selected storage backend. nwdirectory exposes directory data through LDAP;
future nwnds exposes it through NDS semantics; nwbind can later expose it
through legacy Bindery calls.
A separate setup/provisioning tool should own initial population of this store.
The proposed project-facing name is nwsetup, matching the nw* naming scheme.
Its job is not to be another protocol server. It should create or migrate the
initial directory database through libnwds directly. During the flatfile phase
that path may still end in the current libnwdirectory storage files; after the
backend swap it will end in FLAIM:
flatfile phase: nwsetup -> libnwds -> libnwdirectory -> flatfile/mmap/journal
FLAIM phase: nwsetup -> libnwds -> libnwdirectory -> libnwflaim -> FLAIM
Examples of setup-owned work:
- create an empty directory store;
- initialize the base tree, root/container objects, and default schema;
- create initial admin/server/service objects;
- create Bindery compatibility objects and properties needed by NetWare 2.x/3.x clients;
- import or migrate an existing mars-nwe Bindery database when that becomes practical;
- set initial passwords/secrets using the same authentication primitives that
nwbind,nwnds, andnwdirectorywill use at runtime; - validate or repair indexes before the server starts.
nwsetup should not fill the database by acting as an LDAP client to
nwdirectory. LDAP import/export can be useful for interoperability later, but
the local bootstrap path should avoid requiring a running LDAP server and should
not make LDAP the canonical internal representation.
Directory implementation order: flatfile first, FLAIM later
Do not combine the LDAP compatibility work and the FLAIM storage conversion in
one patch series. The tinyldap-derived nwdirectory server should first be
made testable and compatible while it still uses its current flatfile/mmap/journal
storage. LDAPv2 support comes first, using OpenLDAP 1.0.3 as the historical
LDAPv2 server reference. LDAPv3 behaviour that already works, NetWare/NDS schema
import or .sch to LDIF conversion, and the first libnwds plus console-only
nwsetup provisioning path should all be proven on the flatfile backend before
the storage engine changes. The interactive nwtui setup frontend comes later,
after the flatfile path and the FLAIM storage backend are both stable.
This keeps changes upstreamable to the tinyldap-derived code line: LDAPv2 work is generally useful, while the FLAIM storage backend is mars-nwe-specific. The flatfile backend must remain buildable and tested when the FLAIM backend is added.
The later storage switch should be mechanical. Keep the storage source files parallel and select the implementation in CMake instead of changing every call site at once:
flatfile backend, current default:
mstorage_add.c
mstorage_add_bin.c
mstorage_init.c
mstorage_init_persistent.c
mstorage_unmap.c
mduptab_*.c
future FLAIM backend, selected by CMake:
flaimstorage_add.c
flaimstorage_add_bin.c
flaimstorage_init.c
flaimstorage_init_persistent.c
flaimstorage_unmap.c
flaimduptab_*.c
The FLAIM-backed files may keep the same exported function names as the flatfile
files, for example flaimstorage_add_bin.c can provide mstorage_add_bin()
when that backend is selected. Only one backend source set may be compiled into
libnwdirectory at a time. That lets LDAP parser/server code keep calling the
existing storage API while CMake chooses the backend.
The target dependency chain for the later storage phase is:
libnwds
-> libnwdirectory
-> libnwflaim
-> FLAIM
nwdirectory server/binary
-> libnwdirectory
-> LDAP/LDAPS/StartTLS network service
The nwdirectory binary is the LDAP server. libnwdirectory is the library
that the server uses. libnwds is the later NetWare Directory Services API
layer above it.
LDAP CTests should exercise the server over TCP against the flatfile backend
before FLAIM is introduced. Prefer existing tinyldap test clients and helpers
where they are suitable. A test should allocate a temporary workdir, start
nwdirectory on 127.0.0.1:<free-port>, load or prepare test LDIF/schema data,
run LDAPv2 or LDAPv3 bind/search/unbind checks, stop the server, and remove the
workdir. The first nwsetup tests should be console/CLI tests that call the
new libnwds API directly; they should not require a terminal UI and should not
act as LDAP clients to bootstrap local directory state.
NetWare 4.11 LDAP compatibility baseline
The primary mars-nwe directory compatibility target is the stock NetWare 4.11 LDAP server behavior. That baseline is LDAPv2. LDAPv3 support belongs to the later NDS 8 / eDirectory upgrade path and must be treated as an optional profile, not as the default assumption.
Stock NetWare 4.11 LDAP requirements:
- support LDAPv2 simple bind, search, and unbind;
- use LDAPv2 as the compatibility baseline for tests and protocol decisions;
- keep read/write access to NDS-like objects possible through the LDAPv2 view;
- avoid assuming referrals, SASL, controls, extended operations, or LDAPv3-only UTF-8 DN behavior.
The stock baseline is anchored in the LDAPv2-era specifications such as RFC 1777 for the core protocol and RFC 1778 for standard attribute syntax string representations. This matters because many real NetWare 4.11 installations did not receive the NDS 8 / eDirectory LDAPv3 upgrade.
The optional later NDS 8 / eDirectory profile may add LDAPv3 behavior, including
RFC 2251 core protocol behavior, RFC 2252 schema exposure, RFC 2253 UTF-8 DN
string representation, RFC 2255 LDAP URLs, and SASL via RFC 2222. Those features
must be additive. They should not force the internal directory store, schema
model, or nwsetup bootstrap flow to require LDAPv3.
Design consequences:
- TinyLDAP/nwdirectory must keep LDAPv2 paths first-class and tested.
- LDAPv3 can stay enabled where it already works, but feature gates or explicit compatibility profiles should keep it separate from the NetWare 4.11 default.
- The internal directory API and FLAIM-backed store must not depend on LDAPv3
schema-publishing semantics. Native
.schimport and internal schema enums are the first target. nwndsandnwsetupuse the directory core directly; they do not bootstrap by acting as LDAPv3 clients.
LDAPv2 implementation references
The LDAPv2 compatibility work should use historical references without turning mars-nwe into an OpenLDAP clone.
Primary references for protocol behavior:
RFC 1777 / RFC 1778
TinyLDAP's current compact BER/LDAP implementation
UMich LDAP / early OpenLDAP 1.x, especially openldap-1.0.3
The uploaded openldap-1.0.3.tgz archive is useful because it is close to the
UMich LDAP code line and still contains LDAPv2-era server code and documentation:
doc/rfc/rfc1777.txt
doc/rfc/rfc1778.txt
include/ldap.h
include/lber.h
libraries/liblber/
libraries/libldap/
servers/slapd/bind.c
servers/slapd/search.c
servers/slapd/add.c
servers/slapd/modify.c
servers/slapd/delete.c
servers/slapd/modrdn.c
servers/slapd/operation.c
servers/slapd/result.c
servers/slapd/str2filter.c
servers/slapd/filter.c
servers/slapd/filterentry.c
servers/slapd/back-ldbm/
Use that code to check LDAPv2 operation dispatch, result codes, BER/LBER
encoding, filter parsing, and simple backend/index ideas. Do not import the old
slapd architecture wholesale. The mars-nwe direction remains small,
TinyLDAP-sized patches with CTests around behavior.
Samba 2.2.12 and Samba 3.0.37 were also checked for a reduced embedded LDAPv2
server. They do not provide one. Their LDAP-related code is mostly client or
backend integration code such as pdb_ldap, smbldap, idmap_ldap, ADS LDAP,
and CLDAP client support. Those sources may be useful later for account/schema
mapping ideas, but they are not the LDAPv2 server reference for NetWare 4.11
compatibility.
NetWare/NSS compatible xattr and trustee metadata
The initial mars-nwe xattr compatibility target should be the Novell/OES NSS
netware.* Linux xattr interface, not a new mars-specific namespace. The
OES/NSS source drops are GPL-2.0 material and mars-nwe is GPL-2.0-only, so code
and structure definitions may be adapted directly when the original copyright
and GPL-2.0 license notices are preserved. Do not treat these sources merely as
vague inspiration when exact compatibility structures are needed.
Primary source files inspected. The full nss.tar(2).bz2 and
nss-common.tar(2).bz2 kernel-module drops supplied by the user are the
authoritative local NSS reference, not only the reduced copies already imported
under src/nwfs/nss/:
nss/shared/sdk/public/zXattr.h
nss/shared/sdk/public/zParams.h
nss/shared/sdk/internal/macNSpace.h
nss/shared/sdk/internal/dirQuotas.h
nss/shared/sdk/include/comnBeasts.h
nss/public_core/lsa/lsaXattr.c
nss/public_core/lsa/lsaSuper.c
nss/public_core/comn/authsys/zasAuthModel.c
nss/public_core/comn/authsys/zasAuthSpace.c
nss/public_core/comn/namespace/dosNSpace.c
nss/public_core/comn/namespace/dosNSWild.c
nss/public_core/comn/namespace/longNSpace.c
nss/public_core/comn/namespace/macNSpace.c
nss/public_core/comn/namespace/dataStreamNSpace.c
nss/public_core/comn/common/comnMacintosh.c
nss/public_core/comn/common/dirQuotas.c
nss/public_core/zlss/salvageLog.c
nss/public_core/sharedsrc/manage.c.h
The visible compatibility xattr names come from zXattr.h and the LSA xattr
registration table in lsaXattr.c:
netware.ncpstat
netware.quota
netware.volumeinfo
netware.metadata
netware.userquota
The first implementation should still concentrate on ncpstat, metadata,
quota, and userquota. netware.volumeinfo is active in NSS and should be
kept as the later volume-management/tooling xattr for nwvolinfo, nwsetup,
and future NSS-like volume reporting.
netware.trustee has real helper functions in lsaXattr.c (xattr_get_trustee
and xattr_set_trustee), but its registration entry is disabled/commented out.
For the mars-nwe baseline, trustees should therefore be carried in
netware.metadata, matching zNW_metadata_s, instead of inventing a separate
active netware.trustee xattr. Keep the disabled helper path as a compatibility
note only.
The important binary structures to adapt are:
zNW_ncpstat_s file id, parent id, volume id, logical/physical size,
file attributes and create/modify/access times
zNW_metadata_s file attributes, all important timestamps,
owner/archiver/modifier/metadata-modifier GUIDs,
directory quota, inherited rights mask and trustees
zNW_trustee_s trustee GUID plus rights
zNW_quota_s used/limit/ancestor-directory quota information
zNW_user_quota_s paged list of user quota restrictions
zMacInfo_s / PackedMacInfo_s
NSS Mac metadata root variable data: FinderInfo,
ProDOSInfo, dirRightsMask and layout marker
NSS Mac/AFP metadata is not stored as mars-nwe-private xattrs. The full NSS source shows this model:
zMacInfo_s finderInfo[32], proDOSInfo[6], filler[2], dirRightsMask
PackedMacInfo_s rvdID, rvdLayout, zMacInfo_s
RVD_MAC_META_DATA root-variable-data ID for Mac metadata
MAC_METADATA_LAYOUT packed layout value 1
MAC_RF data-stream name for the Mac resource fork
Therefore the post-pl27 mars-nwe AFP target is to replace
org.mars-nwe.afp.* xattrs with NSS-style metadata/data-stream storage. Since
this code has not been released to users since the pl27 line, there is no need
for fallback, migration, or mirror support for those private xattrs. If Mac
metadata packing requires changes to netware.metadata writer logic, trustee
variable-length handling, or root-variable-data packing, improve those pieces; do
not create a parallel netware.macmetadata convenience store unless later full
NSS evidence proves that is the actual Linux xattr boundary.
Salvage must preserve and restore the same NSS-style Mac metadata and later the
MAC_RF resource fork stream. The current AFP NCP handlers may remain the
protocol entry points, but their backing state should move into libnwfs metadata
and data-stream providers.
The NSS trustee rights constants are defined in zParams.h and should be the
baseline rights model:
R zAUTHORIZE_READ_CONTENTS 0x0001
W zAUTHORIZE_WRITE_CONTENTS 0x0002
C zAUTHORIZE_CREATE_ENTRY 0x0008
E zAUTHORIZE_DELETE_ENTRY 0x0010
A zAUTHORIZE_ACCESS_CONTROL 0x0020
F zAUTHORIZE_SEE_FILES 0x0040
M zAUTHORIZE_MODIFY_METADATA 0x0080
S zAUTHORIZE_SUPERVISOR 0x0100
zAUTHORIZE_SALVAGE 0x0200 NSS extension
zAUTHORIZE_SECURE 0x8000 special, not normally inherited
The compatibility baseline is positive NetWare/NSS trustee rights plus the
inherited rights mask, not Linux trustees-3.0 deny/clear semantics. The OES/NSS
inheritance path applies inherited rights only when an ACL entry has
zAUTHORIZE_INHERIT_DOWN, then filters the inherited rights through the current
object's inheritedRightsMask, and finally strips special bits with
zVALID_TRUSTEE_RIGHTS. The inherited rights mask is also supervisor-preserving:
when the mask is set, NSS ORs zAUTHORIZE_SUPERVISOR back in. When effective
rights contain supervisor, NSS expands that to all valid trustee rights.
In compact form:
inherited_child_rights = parent_rights
& child.inheritedRightsMask
& zVALID_TRUSTEE_RIGHTS
if effective_rights has zAUTHORIZE_SUPERVISOR:
effective_rights |= zVALID_TRUSTEE_RIGHTS
lsaXattr.c also shows the xattr update model: netware.metadata carries a
modify mask, validates byte order/version, maps modified fields into zInfoC_s,
and passes trustees as an array of zTrusteeInfo_s. lsaSuper.c intentionally
hides netware.metadata from normal listxattr() output unless the NSS setting
ListXattrNWmetadata is enabled and the caller has CAP_SYS_ADMIN. mars-nwe
should document and test the same default-hidden behavior, even if the first
implementation stores data in sidecar metadata before moving it into a stronger
backend.
NSS also maps the same internal NetWare/NSS metadata into normal Linux VFS
attributes. The netware.* xattrs are therefore not a separate side channel in
OES; they are backup/migration/admin views of the same state that Linux sees
through stat(2), chmod(2), chown(2), timestamp updates and truncate paths.
The first mars-nwe code step should start with xattr payload compatibility, but
it must keep the existing mars-nwe file attribute and trustee code on a path where
that data can become the single source of truth for all three consumers:
1. Linux/VFS-like state: mode bits, uid/gid mapping, size and timestamps
2. NetWare/OES xattrs: netware.ncpstat, netware.metadata, quota/userquota
3. NCP handlers: obtain/modify info, trustees, effective rights, quotas
Important OES/NSS mapping rules to preserve when adapting the code:
chmod / ATTR_MODE
updates NetWare file attributes, including read-only/hidden/execute-style
mappings where NSS exposes NetWare attributes through Linux mode changes.
chown / ATTR_UID
maps Linux UID to the NSS/eDirectory owner GUID, which is the same owner
identity exported through netware.metadata.
utime / ATTR_ATIME / ATTR_MTIME / metadata changes
update the NetWare accessed, modified and metadata-modified timestamps that
are also visible through netware.ncpstat and netware.metadata.
truncate / ATTR_SIZE
changes the logical file size seen by both Linux stat and NCP obtain-info
paths.
So the implementation rule is: do not create a second, disconnected xattr-only
metadata store. Adapt the existing mars-nwe xattr support and trustee storage so
netware.metadata becomes the compatibility serialization of the same metadata
that will later drive Linux attribute views and NCP file/trustee endpoints.
Additional NSS source areas are useful once the first xattr/trustee layer is in place:
nss/public_core/lsa/lsaComn.c
lsa_getInheritedRights(), lsa_metadata(), lsa_quotas() and related helpers
show how NSS assembles inherited rights, metadata and quota views.
nss/public_core/comn/namespace/
dosNSpace.c, longNSpace.c, macNSpace.c, unixNSpace.c, extAttrNSpace.c,
dataStreamNSpace.c and nameSpace.c document NSS namespace split points.
Use these later for DOS/LONG/UNIX/MAC names, Extended Attributes and data
stream behaviour; do not mix that into the first xattr ABI patch.
nss/public_core/comn/namespace/macNSpace.c and shared/sdk/internal/macNSpace.h
Mac metadata packing/unpacking and namespace rules. Use the NSS
zMacInfo_s/PackedMacInfo_s/RVD_MAC_META_DATA model; replace private
org.mars-nwe.afp.* xattrs instead of migrating them.
nss/public_core/comn/common/comnMacintosh.c
Mac resource fork data-stream lookup/creation and the `MAC_RF` stream name.
nss/public_core/comn/compression/
Compression sources such as nwAlgo.c, cdcomp.c, cduncomp.c, cmCompFile.c
and cmBgCompress.c are later references for NetWare/NSS compressed-file
attributes and background compression policy.
nss/public_core/library/eDir/ and public_core/library/guid,id/
getDSGuid.c, parseDSObjectName.c, guid.c and id.c are later references for
owner/trustee/modifier GUID handling and DN/GUID/UID mapping.
Historical reference and source split:
- OES/NSS is the primary source for
netware.*xattr names, binary layout, trustee rights constants, inherited-rights behavior, namespace split points, compression policy references and Linux attribute mapping. Selected source material may be imported as direct GPL-2.0 source material for adaptation. Low-level helpers that are already adapted into normalsrc/core/andinclude/core/files must not keep duplicatesrc/core/nss/staging copies. nwfs1201/FENRISis imported undersrc/nwfs/nwfs1201/. Use it for older NetWare filesystem record semantics, classic file attribute bits, directory records, trustee masks, quotas, salvage/repair/backup/restore ideas and tool behaviour cross-checks.trustees-3.0is imported undersrc/nwfs/trustees3/. Use its userspace tooling ideas, especiallysettrustees.cand examples, as a starting point for a futurenwtrusteestool that writesnetware.metadatathroughlibnwfs. Its kernel/LSM deny/clear/one-level enforcement model is not the NetWare/NSS trustee rule baseline.
First implementation direction:
libnwfs.so
NetWare/OES-compatible filesystem metadata library.
include/nwfs/zXattr.h adapted from include/nwfs/nss/zXattr.h
src/nwfs/lsaXattr.c adapted from src/nwfs/nss/lsaXattr.c
src/nwfs/lsaComn.c adapted from src/nwfs/nss/lsaComn.c
src/nwfs/lsaPrivate.h adapted from src/nwfs/nss/lsaPrivate.h
This is not a wrapper around untouched NSS sources. The first code patch should
move/copy the needed imported files into the normal include/nwfs/ and
src/nwfs/ build area, keep the original Novell/GPL-2.0 headers, preserve NSS
function/structure names where they still fit, and remove the NSS kernel/VFS/
runtime pieces mars-nwe does not need. The resulting libnwfs.so should own the
netware.* metadata ABI and be linkable by tests, mars-nwe tools and later
nwserv code.
Imported source cleanup policy
Imported GPL-2.0-compatible source is allowed in this GPL-2.0-only repository when provenance and license notices are preserved. Linux kernel reference code may be imported only under the GPL-2.0-only selection used by mars-nwe. NSS/OES source imports follow the same rule: use the code directly when it is the right compatibility source, but do not keep parallel reference trees after an import is adapted into normal MARS paths.
Current cleanup line:
src/core/nss/ removed after the low-level Unicode, UTC, GUID/ID,
bitmap/hash/crc/string/allocation helpers were
imported or stubbed under src/core/ and include/core/.
src/nwfs/nss/ keep while namespace/common/compression/authsys/LSA
include/nwfs/nss/ adaptation is still active; remove individual files
only after the matching libnwfs path exists or the
audit marks them obsolete.
Do not delete src/nwfs/nss/ wholesale before DOS/LONG namespace, metadata,
streams, EA, salvage and compression adaptation have consumed the open reference
points.
Use fixed-width integer types where mars-nwe needs local ABI clarity, preserve the on-wire/on-disk field sizes and byte order constants, and keep any directly adapted Novell code clearly attributed. Add tests before wiring this into live NCP operations:
netware.ncpstat roundtrip and version/byteorder rejection
netware.metadata roundtrip with one trustee
inheritedRightsMask filtering
supervisor expands to all valid trustee rights
netware.metadata hidden from default listxattr path
netware.quota and netware.userquota size/index validation
netware.volumeinfo recognized as an active but later volume-info xattr
Quota must be split into metadata compatibility and enforcement. NSS/OES stores
and exposes quota state through netware.quota, netware.userquota, and the
directory quota field in netware.metadata, but enforcement depends on the host
volume type. The planned per-volume setting is:
NWFS_QUOTA_BACKEND=LINUXQUOTA default for normal Linux filesystems
NWFS_QUOTA_BACKEND=METADATAONLY metadata only, no host enforcement
NWFS_QUOTA_BACKEND=NSS real NSS/OES volume enforcement
LINUXQUOTA keeps the existing mars-nwe expectation that normal Linux volumes
can use Linux quotactl() for user quota enforcement, while libnwfs becomes the
NSS-compatible metadata model. METADATAONLY is for tests, migration and debug
volumes where roundtripping the NSS quota xattrs is enough. NSS means NSS
itself enforces quota and mars-nwe should not try to duplicate that enforcement
through Linux quota calls.
Post-0357 mars-nwe userquota enforcement status:
The current mars-nwe implementation already has a working split between host
Linux quota enforcement and metadata-backed NWQUOTA enforcement. Linuxquota
volumes continue to use the host quota path. Volumes without a quota device can
fall back to the mars-nwe metadata/NWQUOTA backend, where netware.userquota on
the volume root stores both restriction and current used 4K blocks. The xattr is
accessed under temporary effective uid 0 so normal client I/O running as a
mapped Unix user can still update volume-level quota accounting safely.
The enforcement rule intentionally matches the observed NetWare behavior: deny a
write/create-growth request before data when projected usage would reach or
exceed the user restriction, and return NCP completion 0xff. The namespace
create path performs an initial one-block precheck for new regular files so a
create/open sequence cannot bypass the later growth check. After successful
growth, the metadata/NWQUOTA backend adjusts the stored used 4K count. AUTO
adjustment must prefer the metadata path when a valid netware.userquota entry
exists, while Linuxquota-backed volumes remain on the host quota path.
Removed experiments are intentionally not part of the design: file-handle
precharge flags, fchown/chown quota accounting, creator-xattr quota scans, and
quota-specific file-info stamping were tried and then removed once the
volume-root netware.userquota accounting path passed the dual backend smoke.
The dual userquota live smoke remained green after the 0357 directory-quota
retest on both QUOTA/Linuxquota and SYS/NWQUOTA.
Directory quota is different from Linux user quota. The classic NetWare 3.x
set/get/clear NCP path is audited as of 0357: decimal 22/35 = wire/code 0x23
gets directory disk-space restrictions, decimal 22/36 = wire/code 0x24 sets and
clears them, and clear leaves modify_mask=0x0000000000000000 with
dirQuotaLimit=9223372036854775807 inactive while NCP get returns entries=0.
Decimal 22/40 = wire/code 0x28 now parses Sequence Lo-Hi, but fuller scan-reply
validation remains a follow-up. On non-NSS volumes,
metadata.nwm_quota_limit and the netware.quota directory fields must still
be enforced by mars-nwe checks on create/write/extend paths, or left
metadata-only when the backend is METADATAONLY. Do not treat Linux
quotactl() as the whole NetWare/NSS quota model; it is only one enforcement
backend.
The inspected OES/NSS sources do not contain a ready-made ext4/xfs/btrfs
quotactl() adapter or generic non-NSS enforcement layer. The useful NSS code is
its own enforcement model: directory quotas, user-space restrictions, quota cache
and file/rename/write checks inside the NSS File_s/Volume/Beast object model.
Therefore mars-nwe should adapt NSS quota semantics and structures into
libnwfs, but keep the existing Linux quotactl() implementation as the
LINUXQUOTA enforcement backend for ordinary Linux filesystems.
Additional NSS/OES source areas to mine once the initial metadata/trustee layer builds:
public_core/comn/common/dirQuotas.c
directory quota set/get/min/check/fix/cache behaviour; adapt for mars-nwe
directory quota semantics and tests.
public_core/comn/common/comnFile.c, comnIO.c, comnRename.c
examples of where NSS enforces space/quota decisions in create, write/extend
and rename-like paths. Use them to place mars-nwe checks; do not import NSS
VFS/runtime assumptions wholesale.
public_core/comn/common/nameLookup.c, nameScan.c, nameCache.c, comnWild.c
namespace lookup, scan and wildcard behaviour to compare with `namspace.c`
after DOS/LONG/UNIX/MAC namespace adaptation starts.
public_core/comn/common/comnDataStream.c, extAttrBeast.c, fileBeast.c,
hardLinkBeast.c, objectIDStore.c, searchMap.c
data-stream, extended-attribute, object-id and search-index building blocks
for later salvage/archive/EA/data-stream work.
public_core/comn/common/repair.c and public_core/comn/main/comnCmdline.c
repair/fix and command behaviour, including directory quota commands such as
DirectoryQuotas, NoDirectoryQuotas, FixDirectoryQuotas and DirQuotaCache.
These files are direct GPL-2.0 source material like the already imported NSS
files. As of the expanded source import they are present in the mars-nwe tree
under src/nwfs/nss/common/, src/nwfs/nss/authsys/, src/nwfs/nss/main/ and
include/nwfs/nss/, but they are intentionally not part of the build. Adapt
only the pieces needed by a concrete libnwfs, tool or handler consumer, so the
first xattr/trustee library remains small and testable. Once a file is imported
into a normal MARS path or replaced by a deliberate stub, remove its staging copy
from the NSS reference tree unless the audit explicitly records a still-open
reference need.
libnwfs.so should be the unit-test boundary. Unit tests should compile and
link against the library directly so namespace rules, metadata roundtrips,
trustee inheritance, quota validation and xattr name visibility can be tested
without starting an NCP server or mounting a mars-nwe volume. After the quota
backend split, the next user-requested compatibility line is namespace first,
then the remaining useful NSS areas needed for a MARS-NWE 3.x compatibility
release:
1. dirQuotas.c -> finish the NetWare 3.x quota block first: adapt directory
quota model with libnwfs CTest, then wire decimal 22/35,
22/36, 22/40 (wire hex 0x23, 0x24, 0x28)
2. DOS namespace -> adapt NSS DOS legal-name, wildcard and 8.3 mangle rules
3. LONG/OS2 -> adapt NSS long-name rules and shrink nameos2.c where 3.x
clients and existing paths require it
4. namspace.c -> keep NCP path/handle/search dispatch, but remove local
matching/mangling code as libnwfs gains real providers
5. nwattrib.c -> read/write fileAttributes through libnwfs metadata
6. trustee.c -> store trustees and inherited-rights-mask in netware.metadata
7. nwatalk/AFP -> replace private org.mars-nwe.afp.* xattrs with NSS-style
zMacInfo_s/PackedMacInfo_s metadata and MAC_RF streams
8. nwarchive.c -> merge private mars-nwe archive/fileinfo xattrs into metadata
9. salvage -> snapshot/restore the same metadata, including AFP/archive state
10. MARS_NWE_4 -> keep NetWare 4.x namespace-aware variants, compression
status/control families and NDS/eDirectory work guarded
NSS namespace adaptation target
The namespace goal is to replace mars-nwe's local DOS/OS2 matching and alias
logic with code directly adapted from the already imported NSS sources. This is
not a wrapper around untouched NSS files. Keep the original Novell/GPL-2.0
provenance, preserve useful NSS names where they still fit, remove the NSS
Beast/VFS/runtime dependencies, and make the resulting code a normal libnwfs
component.
The current mars-nwe code has namespace behaviour split between namspace.c,
namedos.c, nameos2.c, connect.c and selected nwconn.c paths. namspace.c
also owns NCP request parsing, base handles, search sequence state,
create/open/delete/rename/trustee dispatch, salvage helpers and reply packing,
so it should be reduced in stages rather than replaced wholesale.
First sources to adapt:
src/nwfs/nss/namespace/dosNSpace.c
DOS legal-name checks, uppercase/casefold, reserved names, compare helpers,
the NSS mangleChars table and DOSNS_generateUniqueName().
src/nwfs/nss/namespace/dosNSWild.c
DOS wildcard match/replace semantics, including augmented/non-DOS wildcard
behaviour.
src/nwfs/nss/namespace/longNSpace.c
OS2/LONG legal-name, reserved-name, compare, wildcard and unique-name rules.
src/nwfs/nss/namespace/nameSpace.c, macNSpace.c, unixNSpace.c,
extAttrNSpace.c, dataStreamNSpace.c
later split points for MAC, UNIX/NFS, Extended Attribute and data-stream
namespace behaviour.
The first concrete implementation should expand/adapt the top-level build-area
namespace files, beginning with src/nwfs/dosNSpace.c, and expose namespace
entry points with distinguishable names. DOS-specific functions should not be
merged with LONG/OS2 or generic namespace helpers. Once tests pass, update the
existing callers of build_dos_83_alias() and DOS match helpers in
connect.c, namspace.c and nwconn.c, then remove the duplicate code from
namedos.c instead of keeping it as a permanent wrapper.
libnwcore.so remains the right home for generic helpers imported from the NSS
library tree, such as Unicode/casefolding, DOS/UTC time conversion, GUID/ID and
small NCP validation helpers. libnwfs.so should consume those helpers rather
than duplicating them.
Namespace records and host-side change reconciliation
netware.metadata is the authoritative per-object record for namespace state.
Do not add a private side database for names, file IDs, deleted state, streams or
external-change bookkeeping when the state can live in NSS-shaped metadata or in
a Linux quota/filesystem authority.
The long-term per-object namespace record should include at least:
file_id
parent_file_id
DOS name
LONG/OS2 name
MAC name
UNIX/backend name
casefold/hash fields used by namespace lookup
namespace flags and stream/EA linkage
The backend Linux name is an implementation detail for the UNIX namespace. It
may equal the LONG name for simple files, but it must be allowed to differ when a
name needs escaping or conflict avoidance on the host filesystem. MAC namespace
state is also a filesystem/namespace issue, not a transport issue. Classic Mac
clients may arrive through a future TCP/IP NCP transport, but the namespace layer
only receives namespace IDs and NCP request data. Resource forks, Finder info
and related Mac metadata should be represented later as streams and metadata
under libnwfs, not by adding a separate TCP-side path.
MARS-NWE must also handle files not created through MARS/NCP. Samba, rsync, a
local administrator, backup restore, or a direct host mv/cp can create or
rename visible files under a volume root. Those files still need stable
NetWare-facing identity before clients can rely on namespace, trustee, salvage,
EA or stream behaviour.
The intended libnwfs strategy is two-layered:
watcher/incremental path:
inotify/fanotify on the volume root
CREATE, MOVED_TO, DELETE, RENAME and ATTRIB handling
generate or update netware.metadata where safe
generate DOS/LONG/MAC/UNIX namespace records
invalidate namecache/search state
startup/full reconcile path:
scan the visible Linux tree
create missing netware.metadata for normal files and directories
report orphaned .nwfs_streams entries
verify .recycle payloads that claim NetWare salvage state
leave ambiguous repairs to explicit admin tools
Initial reconciliation of a normal host-created file should use conservative
defaults: UNIX/backend name equals the current Linux name, LONG/OS2 name equals
the current Linux name, DOS name is generated by the DOS namespace engine unless
existing metadata already provides one, and a stable file ID is allocated in
netware.metadata. The watcher is an accelerator and cache invalidation source;
it is not the only correctness mechanism, because events can be missed while the
server is stopped or while a volume is restored offline.
NSS/OES NCP structure references
After the netware.* xattr layout is represented locally, compare the existing
mars-nwe NCP file/trustee handlers against the NCP-adjacent structures from the
same OES/NSS source drops. These headers are GPL-2.0-compatible reference
material, but they are not a complete NCP server implementation and must not
replace mars-nwe's current ncpserv dispatcher wholesale.
Primary references:
nss/shared/support/lnxmbINC/encp.h
nss/shared/sdk/public/ipc2ncp.h
nss/shared/sdk/include/ncpIDAPI.h
encp.h is the most useful file. It documents extended NetWare 3.1-style
request/reply layouts and helper prototypes for file and namespace operations
that should eventually be backed by the same metadata store as
netware.ncpstat and netware.metadata:
ScanTrusteesRequest / ScanTrusteesReply
AddTrusteesRequest
DeleteTrusteesRequest
ObtainInfoRequest / ObtainInfoReply
ModifyInfoRequest
GetEffectiveRightsRequest / GetEffectiveRightsReply
GetDirSpaceRestrictionReq_t
ScanSalvageRequest / ScanSalvageReply
RecoverSalvageRequest
PurgeSalvageRequest
namespace-specific obtain/modify/salvage/effective-rights helpers
ipc2ncp.h only defines the NSS-to-NCP IPC request envelope. That is useful for
understanding the OES split between NSS and NCP layers, but it is not an endpoint
handler model. ncpIDAPI.h documents DN/GUID/UID/security-equivalent mapping
concepts that should feed the later Directory-backed owner, trustee, and quota
mapping work.
Implementation rule:
1. Build and test the local nw_xattr/nw_trustee layer first.
2. Then map mars-nwe NCP obtain/modify/trustee/effective-rights/salvage/quota
handlers onto that metadata layer.
3. Use encp.h as the structure/mask cross-check, while keeping mars-nwe's current
NCP dispatch and protocol flow as the implementation base.
NetWare 4.11 schema acquisition and import
The future NetWare 4.x directory schema should not be invented from memory or
from a small sample file. Real NetWare 4.11 installations appear to carry more
complete schema material than the standalone *.SCH examples. Some schema
fragments, such as NLS.SCH, are useful format samples: they are readable ASN.1-
style schema definition files containing ATTRIBUTE and OBJECT-CLASS blocks,
SyntaxID, Flags, SubClassOf, ContainedBy, NamedBy, MustContain,
MayContain, and ASN1ObjID fields. However, such files should be treated as
partial extension/schema examples, not as the canonical complete 4.11 schema.
The initial canonical schema source should come from a real NetWare 4.11
installation. If the complete schema is embedded in installer data such as
install.dat, the acquisition path should be documented and reproduced rather
than guessed:
NetWare 4.11 install media / installed server
-> extract or export complete NDS schema material
-> inspect native schema records and standalone .SCH fragments
-> convert/import through nwsetup
-> libnwds schema objects
-> libflaim-backed store
nwsetup should eventually support both a native NetWare 4.11 schema import path
and an LDIF import/export path:
nwsetup directory import-schema --format=netware411 <schema-source>
nwsetup directory import-schema --format=ldif <schema.ldif>
nwsetup directory export-schema --format=ldif > schema.ldif
LDIF remains valuable because it is readable, diffable, testable, and useful for
interoperability with the LDAP-facing nwdirectory service. It should not be
the only possible source of truth. Once the native NetWare 4.11 schema format is
understood, a native reader can avoid conversion loss and can preserve
NetWare-specific syntax IDs, flags, naming rules, containment rules, and class
relationships before they are mapped into libnwds.
The import implementation should live below nwsetup, not inside protocol
handlers:
nwsetup
-> schema import layer
-> NetWare 4.11/native schema reader
-> .SCH fragment reader
-> LDIF reader/writer
-> libnwds schema API
-> libflaim
The schema import layer must record provenance. Imported schema sets should be versioned with at least the source system, source file/archive, NetWare version, import tool version, and conversion warnings. This makes it possible to compare a later extracted 4.11 schema against the current mars-nwe schema without pretending that hand-written defaults are authoritative.
Open questions that should stay explicit until real 4.11 media has been inspected:
- exact native schema storage format inside
install.dator the installed tree; - whether all core classes/attributes are present as ASN.1-like
.SCHtext, binary records, or both; - mapping of NetWare/NDS syntax IDs to
libnwdsinternal syntaxes and LDAP syntaxes; - preservation of NDS flags such as single-valued, container/effective class, naming, containment, and mandatory/optional attribute sets;
- how Bindery-compatibility object classes and attributes are represented in the same schema store.
Samba schema/parser code as reference material
Samba is useful reference material for directory schema handling, but it should not become a code dependency for mars-nwe. The Samba tree contains mature AD schema conversion and prefix-map code, including files such as:
source4/dsdb/schema/schema_description.c
source4/dsdb/schema/schema_prefixmap.c
source4/dsdb/schema/schema_convert_to_ol.c
source4/dsdb/schema/schema_syntax.c
source4/dsdb/repl/replicated_objects.c
source4/setup/ad-schema/*
source4/setup/prefixMap.txt
The most relevant concepts to study are:
- schema element formatting into LDAP/OpenLDAP-style schema descriptions;
- class and attribute object modelling;
- OID and prefix-map handling;
- multi-pass schema loading when some classes or attributes depend on schema elements that have not yet been resolved;
- conversion between native directory schema representations and LDIF-like interchange formats;
- test data layout for imported schema sets.
The Samba implementation is GPL-family code and should be treated as a
reference, not copied blindly into mars-nwe. Any future NetWare 4.11 schema
importer should use its own tokenizer/parser implementation that is small,
license-compatible with mars-nwe's intended distribution, and tailored to NDS
*.SCH, install.dat, and libnwds needs. If a specific Samba algorithm
is intentionally reimplemented, the mars-nwe source should mention the conceptual
reference and keep the implementation independent.
Samba's prefix-map work is especially useful as a warning: OID handling should
not be reduced to string comparisons sprinkled across the code. libnwds
should have a small central OID/prefix-map module that can map between:
textual OID
<-> internal syntax/class/attribute identifiers
<-> LDAP/LDIF representations
<-> any native NetWare 4.11 schema identifiers discovered later
That module should be used by the .SCH reader, the native 4.11 importer, LDIF
import/export, nwdirectory, and future nwnds code.
Admin, Supervisor, and directory password bootstrap/recovery
The future typed INI must not carry reusable plaintext passwords for Admin, Supervisor, LDAP, NDS, Bindery, or provider IPC credentials. The old legacy pattern of placing a cleartext Supervisor password in the config and restarting the server so the password is reset should be treated as a compatibility behavior for old Bindery-only setups, not as the NetWare 4.x/directory design.
Initial password creation belongs to nwsetup:
first setup:
nwsetup asks interactively for the initial Admin/Supervisor password
nwsetup calls libnwds authentication primitives
libnwds stores only password verifiers/hashes in the libflaim-backed store
nw.ini records object names, tree/context, and paths, but not the password
Planned password changes should also be explicit setup/admin operations, for example conceptually:
nwsetup passwd supervisor
nwsetup directory set-password cn=admin,o=mars
Those commands can authenticate using the current password, local root/admin
rights, or a documented recovery mode. They should update the directory store
directly through libnwds, not by writing a plaintext key into the INI and
waiting for a daemon restart.
Emergency recovery must be possible, but it should be one-shot and local. The safe design is an explicit recovery command or recovery token/file, with strict permissions and exclusive store access:
nwsetup recovery reset-admin-password
requires local root or the configured mars-nwe admin user
requires the server to be stopped or the store to be exclusively locked
updates password verifier/hash through libnwds
logs the recovery event without logging the new secret
consumes or removes any one-shot recovery token/file
If a recovery file mechanism is added, it should live outside the main admin INI,
be root-owned, mode 0600, and be deleted or renamed after successful use. The
main INI may point to a recovery directory or policy, but it should not contain a
standing plaintext reset password.
Legacy compatibility can remain explicit and deprecated, for example as a
Bindery-only option whose comments warn that it is not used by directory/NDS
mode. Migration tools should warn when they find old cleartext reset settings
and should convert them into an interactive nwsetup password operation rather
than copying the secret into the new documented INI.
Kerberos should not be part of this initial design. Classic NetWare 4.x/NDS
compatibility should focus on native NDS-style authentication and directory
semantics. If a later eDirectory/NMAS compatibility effort ever needs Kerberos,
it should be considered a separate future authentication-provider topic, not a
requirement for the nwdirectory/nwnds/nwbind split.
The migration path should be conservative:
- add the design boundary and naming notes first;
- import or integrate tinyldap under the project-facing
nwdirectoryname; - keep client-facing MatrixSSL usage confined to the LDAP/LDAPS/StartTLS network edge initially; internal TCP-based provider IPC may also use MatrixSSL later, but only as a separate mTLS configuration;
- introduce
libnwdsbefore making Bindery depend on it; - add
nwsetupas the direct bootstrap/provisioning tool for the initial libflaim-backed directory store; - map selected
nwbindobjects/properties tolibnwdsonly after the legacy behavior is documented; - add
nwndslater as an NDS semantic layer, not as an LDAP wrapper; - only then consider replacing private Bindery persistence with libflaim-backed directory storage.
This keeps the future NetWare 4.x work aligned with the provider/process split:
nwdirectory, nwnds, and nwbind may be separate processes or modules, but
they should not be separate sources of truth for identity and directory data.
Third-party and forked component integration policy
The long-term redesign should distinguish between three different kinds of imported code:
1. fixed third-party building blocks kept under third_party/
2. optional third-party backends kept behind mars-nwe facades
3. forked mars-nwe components kept at the repository root
Do not treat all imported code the same way. A small, mostly unmodified library
that is built as part of mars-nwe belongs under third_party/. A component that
will be forked, renamed, given CMake support, wired into mars-nwe storage, and
changed as part of the server design should live at the repository root like the
existing project components (admin, dosutils, mail, smart).
The intended layout is:
third_party/yyjson
existing fixed JSON dependency used by salvage metadata
third_party/wolfssl
fixed TLS dependency used by mars-nwe TLS code
configured through CMake for the server's supported platforms and features
third_party/libowfat
planned fixed hard dependency, initially for tinyldap-derived nwdirectory work
imported from libowfat 0.34 unless a later dependency bump says otherwise
GPL-2.0-only; exposed through a CMake target such as OWFAT::owfat
may also be used by mars-nwe core code when a concrete helper use is justified
third_party/libflaim
planned fixed directory storage engine used through libnwds
mars-nwe-maintained import/fork of the FLAIM source
converted to a real standalone-and-subdirectory CMake build
third_party/matrixssl
planned mars-nwe-maintained fork/import of a GPL-2.0-compatible MatrixSSL tree
candidate crypto/TLS backend for the FLAIM CCS/NICI compatibility layer
converted to a native standalone-and-subdirectory CMake build
mars-tinyldap/
forked/integrated tinyldap-derived component in the repository root
upstream/standalone project identity remains tinyldap
mars-nwe service/binary name: nwdirectory
converted to a real standalone-and-subdirectory CMake build
later wired to libnwds/libflaim instead of tinyldap's original flat files
Current superbuild integration status
The current implementation work has moved several items from planning into an initial mars-nwe superbuild shape. Keep this status separate from the older endpoint-audit patch-number notes: these are functional/build integration facts, not a promise that every compatibility layer is complete.
Current source layout decisions:
third_party/yyjsonremains an external upstream snapshot pinned by release tag inupdate-submodules.sh.third_party/yyjsonis being compiled intolibnwcore; consumers should use thenwcoreinclude namespace and link the core target instead of exposing a standalone yyjson API as a mars-nwe public dependency.third_party/libsodium/libsodiumremains a nested external upstream snapshot pinned to1.0.20-FINALinside the mars-libsodium wrapper submodule.third_party/matrixsslis now the mars-maintained MatrixSSL fork producing the renamed backend librarylibnwmatrixssl. It should not contain the temporary OpenSSL-compat shim.libnwsslin the mars-nwe root owns the SSL/crypto facade plus FLAIM compatibility headers. Its compatibility header layout should stay under thenwsslinclude subtree, for exampleinclude/nwssl/openssl/*.handinclude/nwssl/private/nici/....third_party/flaimis the current FLAIM import path used by the working tree. It provides renamed mars-nwe libraries and tools; future prose may still refer tolibflaimas the logical storage engine, but the concrete submodule path isthird_party/flaimunless the user explicitly renames it.third_party/flaimis currently gated byENABLE_DIRECTORY. The normal build should not configure or build FLAIM when the directory service is disabled.
Current FLAIM CMake import decisions:
- Build
libnwflaimtk,libnwflaim,libnwflaimsql, andlibnwxflaimwith mars-nwe names so they do not collide with any system FLAIM installation. - Build and install the FLAIM/XFLAIM utilities with
nw-prefixed executable names such asnwflmcheckdbandnwxflmcheckdbwhen tools are enabled. - Use the ABI-facing version values from the public headers when they disagree
with
configure.ac:libnwflaimtk.so.1.2,libnwflaim.so.4.62,libnwflaimsql.so.6.00, andlibnwxflaim.so.5.12. - Install all FLAIM public headers under one namespace directory,
include/nwflaim/, includingxflaim.h. Do not install a separateinclude/nwxflaim/tree. - Keep CMake messages explicit about curses/ncurses detection so it is obvious whether curses-backed FLAIM tools will be built.
- Continue to prefer build glue and include-path fixes over invasive FLAIM source edits. Small modern-compiler fixes are acceptable when necessary to compile, but keep them as small, reviewable patches.
Current local dependency-test policy:
mars-nwe-masteris the root/superproject. Uploadedmars-*bundles are not sibling source trees for the root build; they materialize the matching submodule paths recorded in.gitmodules(dosutils,mail,smart,admin,directory, and thethird_party/*mars-maintained submodules).- Upstream point-release tarballs are copied only into the paths where the root
build already expects external snapshots:
yyjson-0.12.0intothird_party/yyjsonandlibsodium-1.0.20into the nestedthird_party/libsodium/libsodiumsource directory owned by the mars-libsodium wrapper submodule. gdbm-1.26is a local build dependency for verification. Build it into an isolated prefix and passGDBM_INCLUDE_DIRplusGDBM_LIBRARYto CMake; do not add GDBM as a mars-nwe submodule.ncursesand Linux-PAM are local header providers for compile checks. Extract their headers into the same isolated prefix when needed, but link final binaries against the host systemncurses/libpamlibraries. Do not vendor either library into mars-nwe.prepare-local-deps.shrecords this offline bootstrap layout for uploaded bundles/tarballs. It is a developer helper, not a packaging policy and not a replacement for proper distro build dependencies.- These local builds are test dependencies, not new third-party submodules.
Terminal UI, toolbox and curses replacement plan:
- Do not add new direct
curses.hdependencies for mars-nwe tools. Direct ncurses use in imported or test utilities should be isolated and replaced by the project-ownednwtuiAPI as those tools are touched. - The desired TUI look is a coloured DOS/NetWare-style interface, not a plain monochrome curses program: blue header/menu/status bars, cyan/bright borders, highlighted selections, warning/error/OK style roles and ASCII borders as the guaranteed fallback. UTF-8 or block graphics can be optional themes later.
- The layout should remain readable: header/version/system status, top menu or wizard-step line, main work panels, optional right/bottom running-action box, and a stable translated help/status line at the bottom.
- Tools must request semantic style roles from
nwtui; they must not hard-code terminal colours or backend escape sequences. - Add a small
nwi18nAPI for tool strings instead of introducing gettext as a required dependency. Catalog storage may be INI-like or generated C tables, but the INI parser/writer must not belong tonwi18n. - Add a shared
libnwcoreINI reader/writer API for server configuration, setup/toolbox editing and optionalnwi18ncatalog loading. The uploadedminInicode is only an API/feature reference because it is Apache-2.0; the core implementation should be small project-owned GPL-2.0-only code. Tools and daemons must callnw_ini_*, not any backend directly. - Future interactive server-local tools should be one multi-call binary.
nwtoolboxopens a main menu; symlink/argv[0]names such asnwsetup,nwfiler,nwsalvage,nwmetadataandnwbackupenter the matching applet directly. This shares TUI initialisation, translations, logging, config loading and privilege checks while preserving familiar tool names. - Remote administrator clients are a separate client/server problem. A future
nwadmin/ConsoleOne-like frontend must talk to the server through NCP or later directory protocols via a portable admin client library; it must not link directly to local server libraries such aslibnwdirectory,libnwflaim, or server-privatelibnwdsinternals to manage a running server. - The first remote admin-client path is NetWare 3.x bindery/server
administration. Directory/NDS management can be added later when LDAPv2/LDAPv3,
schema import,
libnwds, consolenwsetup, and the FLAIM-backed directory storage work are ready. - A future GUI may use Lazarus LCL/FreePascal or another cross-platform toolkit, but the GUI must stay above the portable admin client API. On Linux the first backend should investigate ncpfs. On Windows, the practical first backend should wrap the installed Novell Client because that client stack is normally required there anyway; do not revive the old Pascal VLM/NWTP runtime layer for new code. For Classic/PPC macOS, the historical Novell NetWare Client for Macintosh is only a compatibility/reference path; modern macOS needs either an ncpfs client-library port or a small project-owned NCP transport.
- Keep the first admin client simple. Rich Pascal GUI/web stacks such as custom-drawn Lazarus libraries or browser-oriented Pascal frameworks can be evaluated later, but they must not become required dependencies for the common admin client library or server daemons.
- A server-local web admin frontend may eventually replace the old SMArT role,
but it must be split into a small
nwwebuifrontend process and annwadminbackend. The user-facing web name should benwConsole; the remote/desktop client family should benwAdmin.nwwebuiowns HTTP/HTTPS, TLS offload, sessions, static assets and embedded nwConsole status/restart pages, whilenwadminowns web/config plugin dispatch and calls the portable admin API over localhost NCP or explicit control IPC. Thenwwebui-to-nwadminboundary should be an explicit local IPC socket/protocol, replacing the old SMArT-style subprocess stdout/console scraping model. A host-level systemd/init restart of the MARS-NWE service may restart the whole service family, includingnwwebui. A restart requested through nwConsole must keepnwwebuirunning unless the operator explicitly restarts the web frontend; targeted restarts may restart backend services such asnwadmin,nwconn,ncpserv,nwbind, directory, FTP or later service daemons individually and show embeddedRestarting .../ backend-unavailable pages meanwhile. Authentication is bindery/server auth first for 3.x, later directory/libnwdsauth first for 4.x, and PAM only fallback. Socket and HTTP helper work should prefer bundledlibowfat, and HTTPS/TLS should use the MatrixSSL/libnwsslproject TLS path instead of another local implementation. nwConsole and nwAdmin frontends should share an open-source or project-owned icon catalog and iManager/ConsoleOne-inspired navigation language without copying proprietary Novell assets. - Production daemons must not depend on the TUI stack or on administrator GUI toolkits.
- See
doc/TUI_TOOLBOX_PLAN.mdfor the detailed UI/toolbox plan,doc/NWCORE_INI_PLAN.mdfor the shared INI reader/writer plan, anddoc/NWADMIN_CLIENT_PLAN.mdfor the remote admin-client roadmap.
Current namespace / collision-avoidance policy:
- The
nwprefix is used for vendored upstream components and imported helper tools that could collide with system packages, not as a blanket rename of every historical mars-nwe executable. - Historical mars-nwe programs keep their established names:
nwserv,ncpserv,nwclient,dbmtool,ftrustee, and related original server/client commands are not renamed just for symmetry. - Vendored libraries that replace or embed upstream projects use mars-nwe names:
libnwowfat,libnwsodium,libnwmatrixssl,libnwflaimtk,libnwflaim,libnwflaimsql,libnwxflaim,libnwcore,libnwssl, andlibnwds. The name records that mars-nwe is intentionally using its pinned/imported version rather than any systemlibowfat,libsodium,matrixssl,flaim, or similar package. - Vendored headers are likewise installed below
nw...include namespaces:nwlibowfat,nwsodium,nwmatrixssl,nwflaim,nwssl,nwcore, andnwds. - Imported tools from TinyLDAP and FLAIM/XFLAIM must use
nw-prefixed installed names because their upstream names are generic or likely to collide. Examples includenwt2,nwparse,nwldapclient,nwasn1dump,nwx,nwflmcheckdb,nwflmview,nwxflmcheckdb, andnwxflmdbshell. - Compatibility headers owned by
libnwsslstay inside thenwsslnamespace, for exampleinclude/nwssl/openssl/*.handinclude/nwssl/private/nici/.... Do not install a top-levelinclude/openssl/directory from mars-nwe.
libowfat dependency rule
libowfat should be a hard bundled dependency, initially for the
tinyldap-derived mars-tinyldap/nwdirectory work, not merely a reference
archive. It belongs under third_party/libowfat and should be handled like a
fixed third-party building block, similar to yyjson for salvage metadata.
The reviewed source is libowfat-0.34.tar.xz. Its README describes libowfat as
a library of general-purpose APIs extracted from Dan Bernstein's software,
reimplemented under GNU GPL Version 2 with no later-version grant. The initial
import should therefore document libowfat 0.34 and GPL-2.0-only explicitly in
third_party/libowfat/README.mars-nwe.md.
The CMake integration should not simply shell out to libowfat's original
Makefile. It should expose a normal target usable by both standalone
mars-tinyldap and the mars-nwe superbuild, for example:
standalone:
cmake -S third_party/libowfat -B build-libowfat
inside mars-nwe:
add_subdirectory(third_party/libowfat)
target_link_libraries(nwdirectory PRIVATE OWFAT::owfat)
The expected target name is:
OWFAT::owfat
The tinyldap-derived code may depend on libowfat directly where that matches the upstream programming model. mars-nwe core code may also use libowfat when a concrete helper is useful, for example for portable byte operations, formatting, scanning, dynamic buffers, socket helpers, or event-loop support. That use should be explicit and local to a real consumer patch, not a blanket include in unrelated NCP dispatch code.
If multiple mars-nwe modules need the same libowfat helper pattern, prefer a small mars-nwe facade so call sites stay readable and ownership remains clear. Do not invent a facade only to hide that libowfat is a hard dependency; direct use is acceptable when the dependency is already intentional and the consumer is well scoped.
Areas of libowfat likely relevant to tinyldap include byte, buffer, fmt,
scan, str, stralloc, uint, open, socket, and possibly io or cdb.
Keep the first import focused on the parts required to build the tinyldap-derived
LDAP service and any immediately justified mars-nwe consumer; optional
tools/tests/manpage installation can remain disabled until there is a concrete
maintenance need.
The uploaded mars-libowfat bundle was also checked for the future TCP listener
work. The relevant public API is in socket.h and io.h; use the actual
libowfat names, not shorthand names. TCP listener setup should be based on
socket_tcp4()/socket_tcp6(), socket_bind4_reuse()/socket_bind6_reuse(),
socket_listen(), and the socket_accept4*_makenonblocking*() /
socket_accept6*_makenonblocking*() family. Per-interface IPv6 binds can use
socket_getifidx() where a configured listener needs a scope ID. Event-loop
integration should use io_fd() or io_fd_canwrite() to register descriptors,
io_wantread()/io_wantwrite() to express interest, io_wait() or
io_waituntil2() to poll, and io_canread()/io_canwrite() to drain ready
work. Keep any later src/nwtcp.c patch honest to these names so the design
matches the bundled API.
Do not introduce GPLv3 code into this dependency path. If a later libowfat bump or replacement is considered, review the license and make that a separate third-party update patch.
TLS/crypto dependency rule
The current GPL-2.0-only direction is to avoid OpenSSL and evaluate a GPL-2.0-compatible MatrixSSL fork as the preferred crypto/TLS candidate. Earlier wolfSSL-oriented notes should be treated as superseded for new dependency work unless a later patch deliberately reopens that choice with a pinned GPL-2.0-compatible wolfSSL revision or a commercial license.
TLS call sites should still go through a small internal facade instead of using a third-party TLS API directly:
include/nwtls.h
src/nwtls.c
src/nwtls_matrixssl.c
The facade is not meant to promise equal OpenSSL/LibreSSL/wolfSSL support. Its purpose is to keep certificate loading, policy checks, mTLS requirements, error logging, secure defaults, and backend-specific setup in one place. The planned GPLv2-friendly backend candidate is MatrixSSL.
Protocol handlers and provider code should therefore use nwtls concepts, not
raw MatrixSSL types. Example ownership:
nwdirectory LDAP listener -> nwtls -> MatrixSSL
provider TCP IPC -> nwtls -> MatrixSSL/mTLS
nwlog/security logging -> logs TLS events, never private key material
TLS configuration belongs in the documented INI, but private key material does
not. The config may point at certificate/key files, while nwsetup should be
able to generate or validate local defaults. Private keys and provider mTLS
credentials must live in protected directories with strict permissions.
mars-nwe shared library layering
The MatrixSSL/FLAIM/directory plan should produce mars-nwe-named libraries instead of exposing raw upstream library identities to the rest of the project. This keeps linking deterministic, avoids collisions with distro packages, and makes it clear which layer owns which compatibility boundary.
Planned library names:
libnwmatrixssl
mars-nwe-maintained MatrixSSL fork/import
patched only for CMake, portability, symbol/library naming, and build hygiene
provides the low-level GPL-2.0-compatible crypto/TLS implementation
libnwssl
mars-nwe SSL/crypto facade
provides TLS abstraction for apps/services
provides the CCS/NICI compatibility API used by FLAIM encryption paths
provides a narrow OpenSSL-compat shim only for legacy FLAIM/FTK network code
is the only normal project layer that talks directly to libnwmatrixssl
libnwflaimtk
renamed FLAIM FTK support library
built from the imported FLAIM source but renamed to avoid system-library
conflicts
libnwflaim
renamed classic FLAIM database library
depends on libnwflaimtk and uses libnwssl only through the CCS/NICI interface
when encrypted storage support is enabled
libnwds
future mars-nwe NetWare Directory Services API library
sits above the tinyldap-derived libnwdirectory library
provides the C API used by future nwnds, nwsetup/import tools, Bindery/NDS
integration and nwdirectory policy glue
owns NetWare/NDS-facing schema, security and object policy
libnwdirectory
existing tinyldap-derived library output used by the nwdirectory server
owns LDAP helper code and the storage API currently backed by flatfile files
later selects a FLAIM backend and links libnwflaim
The intended dependency direction is:
LDAP/LDAPS nwdirectory service -> libnwdirectory -> libnwflaim -> libnwflaimtk
future nwnds/NDS provider -> libnwds -> libnwdirectory -> libnwflaim -> libnwflaimtk
nwsetup/import tooling -> libnwds -> libnwdirectory -> libnwflaim -> libnwflaimtk
libnwflaim encrypted storage -> libnwssl CCS/NICI compat -> libnwmatrixssl
service/app TLS -> libnwssl TLS facade -> libnwmatrixssl
Consumers must not skip layers for convenience. Future nwnds, nwsetup,
nwbind, and provider code should call libnwds for NetWare directory state;
libnwds then uses libnwdirectory. The nwdirectory LDAP server may use
libnwdirectory directly for its LDAP storage path and may use libnwds only
where NetWare/NDS policy glue is needed. None of these layers should include
FLAIM, MatrixSSL, OpenSSL-style, or CCS/NICI implementation headers directly.
TLS/crypto policy goes through libnwssl.
This also means the imported FLAIM targets should not be named exactly like a
system installation. Use mars-nwe names such as NWFlaim::ftk /
NWFlaim::flaim or equivalent targets that produce libnwflaimtk and
libnwflaim, while keeping source-origin notes in third_party/libflaim.
MatrixSSL fork/CMake rule
MatrixSSL should be evaluated as the preferred GPL-2.0-compatible crypto/TLS
candidate for the FLAIM CCS/NICI compatibility layer and any later TLS work that
needs a GPLv2-friendly backend. It should not be consumed as a random system
library. If selected, mars-nwe should carry a maintained fork/import under:
third_party/matrixssl
The import must record the exact upstream or fork URL, revision, license terms, local patches, and why the pinned source is compatible with mars-nwe's GPL-2.0-only policy. Do not silently track a moving branch.
The MatrixSSL fork must gain a native CMake build. Do not keep the original Makefiles as the long-term build path and do not shell out to them from the main mars-nwe build. The expected shape is:
standalone:
cmake -S third_party/matrixssl -B build-matrixssl
inside mars-nwe:
add_subdirectory(third_party/matrixssl)
Expose normal targets. The exact names may be adjusted during import, but the intended split is:
NWMatrixSSL::crypto # produces/represents libnwmatrixssl crypto primitives
NWMatrixSSL::tls # optional TLS surface from the MatrixSSL fork, if built
FLAIM should still not include MatrixSSL headers directly. The intended layering is:
libflaim -> CCS/NICI compatibility layer -> MatrixSSL crypto backend
This keeps the FLAIM port compatible with its original NICI/CCS expectations and
lets mars-nwe replace or audit the backend without editing FLAIM call sites. The
same rule applies to future nwdirectory/libnwds code: it should call the
mars-nwe directory/crypto facade, not MatrixSSL APIs directly.
Old FLAIM/FTK OpenSSL-facing network code should remain disabled by default. If
it must be kept for source compatibility, isolate it inside the FLAIM import and
keep OpenSSL out of the public mars-nwe dependency policy. libnwssl may own a
narrow OpenSSL-compat shim for that FLAIM/FTK code path, but the shim is not the
project TLS API and must not be used by mars-nwe LDAP, provider IPC, or service
code. MatrixSSL import and CMake conversion must be separate from the later
FLAIM CCS/NICI implementation, so each patch stays mechanical and reviewable.
Logging backend dependency rule
zlog is not a mars-nwe backend target. It was considered for advanced
category/rule/format routing, but it is Apache-2.0 and must not be vendored into
this GPL-2.0-only core. The mars-nwe public logging API remains
include/nwlog.h; endpoint/provider code must not call zlog or any other
backend directly.
The preferred direction is a small project-owned backend set behind nwlog:
endpoint/provider code -> nwlog -> stderr/file backend
-> journald-friendly stderr backend
-> optional syslog backend
-> optional GELF backend
-> optional JSON-lines file backend for Filebeat/Elastic Agent
GELF and JSON-lines output should be implemented by mars-nwe code when needed,
with redaction and correlation fields applied before formatting. Do not add a
third-party logging router merely to regain features that nwlog can provide in
a small controlled backend.
libflaim storage engine rule
libflaim should live under third_party/libflaim, not at the repository root.
It is a bundled storage engine dependency for the future directory store, not a
mars-nwe service component. The initial import may come from the SourceForge
FLAIM code base or from a distro-vetted source package such as the openSUSE
libflaim-4.9.1046 source package, but the exact source must be recorded.
The imported tree is expected to need a mars-nwe-maintained fork because the original build system is too hard to integrate directly. Do not merely wrap the old Makefiles from CMake. Replace the build integration with a real CMake build that can work both ways:
standalone:
cmake -S third_party/libflaim -B build-flaim
inside mars-nwe:
add_subdirectory(third_party/libflaim)
target_link_libraries(directory PRIVATE FLAIM::flaim)
The CMake conversion should separate the library from optional tools/tests,
expose a normal target such as NWFlaim::flaim, and support static/shared choices
where useful. The original Makefiles may remain as reference material during the
import, but they should not be the long-term build path.
The inspected FLAIM trunk snapshot (flaim-code-r1112-trunk) already contains
Autotools files (configure.ac, Makefile.am, libtool usage, config.h
generation) and several subprojects (ftk, flaim, sql, xflaim). Even so,
mars-nwe should still replace that build integration with native CMake. The
Autotools setup is useful as source inventory and feature-check documentation,
but it does not match the mars-nwe build style and should not be invoked from the
main CMake build.
The first CMake import should be intentionally small and storage-focused:
required first targets:
NWFlaim::ftk # produces libnwflaimtk, the renamed FTK support library
NWFlaim::flaim # produces libnwflaim, the renamed classic FLAIM DB library
optional later targets:
FLAIM::sql # SQL FLAIM, not needed for initial directory store
FLAIM::xflaim # XML FLAIM, not needed for initial directory store
flaim tools # checkdb/rebuild/view/dbshell/etc., off by default
flaim tests # off by default
flaim docs # off by default
NWFlaim::flaim depends on the renamed FTK toolkit target, so the CMake
conversion should model
that dependency explicitly instead of relying on Autotools' installed-library
search. The initial mars-nwe directory-store work only needs the classic FLAIM
library path; SQL FLAIM, XFLAIM, Java/C# bindings, Doxygen, OBS/debian package
metadata, NetWare NLM files, and Windows project files should remain imported as
reference material but disabled in the mars-nwe build until a real need exists.
Autotools feature checks should be translated into CMake checks only when they
matter for the required targets. Examples from the inspected tree include
pthread support, large-file support, selected POSIX headers/functions, optional
ncurses/rt checks for utilities, debug defines such as FLM_DEBUG, and platform
defines such as OSX. OpenSSL support in FTK should not become a new TLS
direction for mars-nwe; TLS policy remains MatrixSSL through the nwtls facade.
If any old FLAIM crypto/TLS-related option is required for building, it must be
reviewed separately and kept isolated from mars-nwe provider IPC and LDAP TLS
policy.
If the FLAIM source cannot be built cleanly without old OpenSSL-facing code, the
preferred fallback is still not to introduce a general OpenSSL/LibreSSL backend
matrix. First disable the old FTK TLS-facing code for the required
NWFlaim::ftk/NWFlaim::flaim targets. If a legacy path is unavoidable, keep the
patch inside third_party/libflaim and route it through a narrow libnwssl
OpenSSL-compat shim backed by libnwmatrixssl; do not expose OpenSSL-compatible
types outside the FLAIM/FTK build and do not make LDAP/provider IPC use OpenSSL
APIs.
The inspected r1112 FTK network code uses OpenSSL-style APIs for TLS/BIO/X.509
client helpers, not for FLAIM database encryption. The observed surface is
small enough to keep private: initialization helpers (SSL_load_error_strings,
SSL_library_init, ERR_load_BIO_strings), SSL_CTX setup/free, BIO_*
connect/read/write/memory helpers, SSL_* verify/certificate access, X509_*
subject/public-key helpers, and EVP_PKEY_free. Older code also tests the
public-key type directly (EVP_PKEY_RSA style), so the import should prefer a
small source adjustment or wrapper at that call site over broad OpenSSL-struct
emulation.
The preferred FLAIM integration should keep FLAIM source call sites close to the
imported third-party code. Do not patch FLAIM just to replace legacy NICI/CCS or
OpenSSL include names with mars-nwe-specific include names. Instead, libnwssl
may provide private compatibility headers and the NWFlaim::ftk /
NWFlaim::flaim targets may add that directory to their private include path:
include/nwssl/private/nici.h
include/nwssl/private/openssl/*.h
That lets old includes such as <openssl/ssl.h> resolve through mars-nwe's
private libnwssl compatibility layer without turning FLAIM into mars-nwe-owned
source. These headers are private to the FLAIM build and must not be installed
or advertised as the public libnwssl TLS/Crypto API. Normal mars-nwe modules,
LDAP, provider IPC, nwconn, nwbind, nwdirectory, and future nwnds code use
the normal public libnwssl and libnwds headers.
If crypto primitives are required by Flaim storage encryption, implement them via
the CCS/NICI compatibility layer backed by MatrixSSL crypto primitives, not by
including MatrixSSL/OpenSSL-style headers in libnwds, nwbind,
nwdirectory, or future nwnds code.
FLAIM is C++ code. That is acceptable, but its C++ API must not leak into the
old mars-nwe C code. nwbind, future nwnds, nwdirectory, and nwsetup
should use a C ABI through include/nwds.h or an equivalent internal
libnwds API. Only the libnwds implementation should know that the
store underneath is libflaim.
The final import must include third_party/libflaim/README.mars-nwe.md or an
equivalent note listing:
- import source and version/revision;
- distro package and distro patches, if used;
- library source license, observed as LGPL-2.1 in the inspected source;
- separate helper/tool licenses such as BSD-3-Clause for files like
svn2cl.xsl; - local CMake and portability changes;
- how mars-nwe links it through
libnwds.
Forked tinyldap / nwdirectory rule
tinyldap is different from yyjson, MatrixSSL, and libflaim. It is not a
small library dependency and it is not merely a storage engine. It becomes the
LDAP service component used by mars-nwe, so it follows the existing root-level
component pattern used by admin, dosutils, mail, and smart.
The repository name can be mars-tinyldap/, but the upstream/standalone project
identity should remain tinyldap. In other words: when built standalone it is
still tinyldap; when built as part of mars-nwe, the project-facing service/binary
name is nwdirectory.
The planned integration requires a fork because the original code needs mars-nwe-specific work:
- a real CMake build, not only the original Makefile;
- standalone tinyldap build support under the tinyldap name;
- mars-nwe subdirectory build support producing the
nwdirectoryservice; - LDAP/LDAPS/StartTLS integration through
nwtls/MatrixSSL; - replacement or bypass of the original flat-file storage;
- later
libnwdsand libflaim-backed storage integration; - directory schema, bootstrap, and setup integration through
nwsetup.
The inspected tinyldap.tar snapshot is a small C project with one handwritten
Makefile. That Makefile builds several static archives and utilities, including
ASN.1 helpers, LDAP encode/decode helpers, LDIF parsing, auth helpers, a storage
archive, a tiny TLS archive, tinyldap, tinyldap_standalone, debug/test
programs, index tools, ACL tools, an LDAP client, and conversion utilities. It
also expects libowfat-style headers/libraries and, when not using the old dietlibc
path, links OpenSSL/crypt-style libraries for some helper code. For the
mars-nwe integration, that libowfat expectation is intentional: libowfat is a
hard bundled dependency under third_party/libowfat, exposed as OWFAT::owfat.
The CMake conversion should inventory those groups explicitly instead of copying
the old all target wholesale.
A reasonable first CMake split is:
tinyldap::asn1 ASN.1 scan/format helpers
tinyldap::ldap LDAP PDU scan/format/search-filter helpers
tinyldap::ldif LDIF parsing and LDAP matching helpers
tinyldap::auth password/auth helpers, later routed through libnwds
tinyldap::storage original mmap/flat-file storage, legacy/transition only
tinyldap::server original tinyldap server logic, adapted for nwdirectory
Optional/off-by-default targets can cover old tools such as parse, addindex,
dumpidx, idx2ldif, dumpacls, ldapclient, ldapdelete, asn1dump,
mysql2ldif, debug binaries, and tests. mars-nwe should not require these tools
for the normal nwdirectory build unless a migration/setup workflow explicitly
needs them.
The original tinyldap TLS code (tinytls.h, tls_*.c, fmt_tls_*.c, and
init_tls_context.c) should not become the long-term TLS stack for mars-nwe.
Keep it as reference material or disable it in the integrated build. The
project-facing nwdirectory service should use nwtls backed by MatrixSSL for
LDAPS/StartTLS, while the standalone tinyldap build may temporarily keep legacy
TLS behavior only if it is isolated behind CMake options and does not leak into
mars-nwe's TLS policy.
The original storage path is also transitional, but it must remain the first
working/tested backend while LDAPv2, LDAPv3 and schema import behaviour are made
compatible. Files such as mstorage.*, mduptab.*, strstorage.*, and the
data/index workflow described by the upstream README are useful for
understanding tinyldap's original database model. The mars-nwe storage path
should later move through a selected backend in libnwdirectory:
flatfile phase: nwdirectory -> libnwdirectory -> mstorage/mduptab flat files
FLAIM phase: nwdirectory -> libnwdirectory -> libnwflaim
NetWare API: nwsetup/nwnds/nwbind -> libnwds -> libnwdirectory -> selected storage
Do not make nwbind or future nwnds depend on tinyldap's flat-file storage or
on the LDAP protocol just because the LDAP service happens to be present. The
shared NetWare-facing directory API remains libnwds, and the storage selection
remains below libnwdirectory.
The CMake conversion should therefore support both use cases:
standalone upstream-style build:
cmake -S mars-tinyldap -B build-tinyldap
# produces tinyldap-named targets/tools
inside mars-nwe:
add_subdirectory(mars-tinyldap)
# produces the nwdirectory service/integration target
The architectural relationship remains:
LDAP/LDAPS client
-> nwdirectory service built from mars-tinyldap/tinyldap code
-> libnwds
-> libflaim-backed store
nwbind and future nwnds should not speak LDAP internally just because the LDAP
service exists. They should use libnwds directly, while nwdirectory
exposes LDAP as an external protocol view of the same directory data.
Versioning and local-change documentation
Each bundled or forked dependency should have a short mars-nwe note describing:
- upstream project and URL;
- pinned commit/tag or submodule branch policy;
- license;
- whether it is fixed, optional, or forked;
- which mars-nwe facade owns access to it;
- local changes carried by mars-nwe;
- CMake options used by the mars-nwe build;
- whether the component supports standalone builds, mars-nwe subdirectory builds, or both.
Do not silently update third-party submodules together with unrelated functional
changes. Dependency bumps should be separate patches. Forked root components
should keep a README.mars-nwe.md or equivalent local-change note so future
updates can be reviewed without guessing which changes are upstream and which are
project integration work.
Suggested build options:
WITH_ZLOG=ON/OFF/AUTO optional advanced logging backend
WITH_WOLFSSL=ON/OFF/AUTO bundled TLS backend; required when TLS features are enabled
WITH_NWDIRECTORY=ON/OFF build the mars-tinyldap/nwdirectory component
WITH_NWNDS=ON/OFF future NetWare 4.x/NDS compatibility layer
WITH_LIBFLAIM=ON/OFF/AUTO future directory store backend
WITH_TCP=ON/OFF future client-facing TCP/IP transport
Conservative builds should still be able to disable incomplete future components, but once a feature requires TLS, its supported TLS implementation is MatrixSSL.
Configuration redesign and future typed INI files
The current nw.ini/nwserv.conf format is historically user-editable, but it
is not an INI file in the usual named-section/key-value sense. It is a numbered
record format:
1 SYS /var/local/nwe/SYS kt 711 600
2 MARS
3 auto
51 1
52 .recycle .salvage
opt/nw.ini.hook.cmake is an install-time template for that legacy format. The
SMARTHOOK comments make selected numeric sections replaceable by tooling, but the
runtime parser still reads numbered records through helpers such as
get_ini_entry() and get_ini_int(). That format should remain supported for
compatibility, but it should not be the long-term administrator-facing format for
NetWare 4.x, directory, TLS, transport, and provider settings.
The future configuration direction should be a real named INI file, backed by a schema. JSON is useful for machine metadata, but it is not a good primary administrator-facing configuration format for mars-nwe. A named INI layout is closer to the existing text configuration style while making new subsystems much clearer:
[server]
name = MARS
compatibility = netware3
internal_network = auto
[volume.SYS]
path = /var/local/nwe/SYS
flags = kt
umask_dir = 0711
umask_file = 0600
[transport.ipx]
enabled = true
internal_network = auto
[transport.tcp]
enabled = false
listen = 0.0.0.0:524
[bindery]
enabled = true
backend = legacy
[directory]
enabled = false
store = libflaim
path = /var/lib/mars-nwe/directory
[ldap]
enabled = false
listen = 0.0.0.0:389
ldaps_listen = 0.0.0.0:636
[ldap.tls]
enabled = false
provider = wolfssl
cert = /etc/mars-nwe/ldap.crt
key = /etc/mars-nwe/ldap.key
[nds]
enabled = false
provider = nwnds
[provider-ipc]
mode = local
local_dir = /run/mars-nwe
The exact keys are placeholders, but the grouping is important:
- server identity and compatibility mode belong under
[server]; - volumes become repeatable named sections such as
[volume.SYS]; - transport settings are separate from NCP provider settings;
- Bindery, Directory, LDAP, and NDS configuration are distinct layers;
- TLS settings are attached to the protocol edge that uses them;
- internal provider IPC settings stay separate from client-facing transport.
For NetWare 4.x this is especially important. Do not overload old numeric
sections with directory, NDS, LDAP, TLS, libflaim, and provider-IPC options. A
numbered line such as 83 ... gives no useful hint to an administrator and makes
future nwsetup edits fragile. New 4.x-era features should be designed in the
named INI schema first and only mapped to legacy numeric entries where backwards
compatibility requires it.
The migration path should be:
- keep reading the legacy numbered config as today;
- add a typed config model in code (
NwConfig) whose fields are not tied to either file syntax; - load legacy numbered config into that model;
- load the new named INI format into the same model;
- make
nwsetupwrite the new named INI format atomically; - optionally provide import/export from legacy numeric config to named INI;
- only later consider generating legacy numeric sections from the typed model for older tools.
The parser/writer choice should be made with write support in mind. A read-only
INI library is enough for daemons, but not enough for nwsetup or a future web
configuration editor. The writer should be schema-driven and should write
atomically, but the generated INI is also administrator documentation. It must
not be rewritten into an undocumented minimal key/value dump.
The shipped default file should be a documented INI, not only a machine config.
Comments should explain the section purpose, defaults, safe values, compatibility
notes, and examples. nwsetup should therefore prefer one of these strategies:
comment-preserving edit:
read existing file as text
replace only known key value lines
preserve surrounding comments, unknown keys, and unknown sections
insert missing keys near their documented section when possible
or template regeneration:
regenerate from a full documented template/schema
include the explanatory comments again
preserve local overrides through the typed NwConfig model
Do not choose a writer that discards all comments unless nwsetup regenerates
from a complete documented template. Losing the documentation comments would be
a regression because the INI is expected to be the primary admin-facing reference
for normal deployments.
Practical library guidance:
- a read-only parser may be acceptable for runtime processes;
nwsetupneeds read/write/delete or a small project-owned writer;- comment-preserving round trips should not be required for correctness;
- generated files should use stable ordering so diffs are readable;
- secrets must not be emitted accidentally into world-readable configs;
- writes should use temporary files plus rename, with restrictive permissions.
Candidate approaches:
runtime read path:
nw_config_load() -> parse legacy numeric and/or named INI -> NwConfig
setup/write path:
nwsetup -> NwConfig -> schema-driven canonical INI writer -> atomic rename
This keeps the old nw.ini compatibility path alive while giving NetWare 4.x,
nwdirectory, nwnds, libflaim-backed storage, MatrixSSL TLS, and future
transport settings a configuration format that can be understood and edited by a
human.
Transport split for future TCP/IP support
Future TCP/IP support should be introduced as a transport code/library split, not as a new daemon. The transport layer is below the NCP dispatcher: it owns wire IO, peer addressing, framing, and transport-specific discovery or watchdog behavior. It does not own Bindery, Queue, Directory, File, Semaphore, or other NCP provider semantics.
The intended source-level split is:
src/nwtransport.c
common transport API and helpers
transport-neutral peer/session descriptors
dispatch to the selected transport implementation
src/nwipx.c
existing IPX-specific implementation
ipxAddr_t conversion and compatibility helpers
IPX socket send/receive
SAP/RIP, IPX watchdog, and IPX broadcast behavior where applicable
src/nwtcp.c
later TCP/IP implementation
TCP listener/session/framing code
IPv4/IPv6 peer address handling
no SAP/RIP assumptions
src/nwconn.c
NCP session logic
request decode, dispatch handoff, reply construction
should gradually use transport-neutral peer/session data
src/nwserv.c
process supervision and connection lifecycle
uses the transport layer for listener and peer management
These files should be linked into the existing nwserv/nwconn process model.
nwtransport is a boundary in the code, not an nwtransport process. Creating
a separate transport daemon would add an IPC hop for every NCP packet, complicate
disconnect/error handling, and make TCP stream ownership harder without adding a
clear NetWare service boundary.
The long-term direction is to remove raw IPX assumptions from higher layers.
Today, the connection path still exposes ipxAddr_t in important places. A
future cleanup should introduce a transport-neutral peer descriptor, for
example conceptually:
typedef enum {
NW_TRANSPORT_IPX,
NW_TRANSPORT_TCP
} NwTransportKind;
typedef struct {
NwTransportKind kind;
union {
ipxAddr_t ipx;
struct {
unsigned char addr[16];
unsigned short port;
unsigned char family;
} tcp;
} u;
} NwTransportPeer;
The exact structure should follow the existing mars-nwe style, but the ownership rule is the important part: NCP providers should not care whether a request came from IPX or TCP/IP. They should see a connection/session and an NCP request, not a raw network address type.
The transport API can start small. Useful conceptual operations are:
nwtransport_peer_equal();
nwtransport_peer_to_string();
nwtransport_recv();
nwtransport_send();
nwtransport_close_peer();
nwtransport_peer_kind();
As with the NCP context design, these names are placeholders. The first
implementation can wrap the existing IPX behavior and leave TCP stubs out until
there is a real TCP/IP target. The goal is to stop new code from spreading
ipxAddr_t into providers that should remain transport-independent.
IPX-specific behavior must remain isolated. SAP/RIP, IPX broadcast, and the
existing IPX watchdog behavior are compatibility details of the IPX transport or
its immediate nwserv integration. TCP/IP should not be forced to emulate IPX
SAP/RIP internally. If TCP/IP later needs discovery or service advertisement,
that should be designed as a TCP/IP-specific mechanism rather than hidden behind
old IPX-only assumptions.
The intended relationship is therefore:
IPX client -> nwipx -> nwtransport -> nwconn -> NCP dispatcher -> providers
TCP client -> nwtcp -> nwtransport -> nwconn -> NCP dispatcher -> providers
The provider/process rule still applies:
Provider boundary does not imply process boundary.
Transport boundary does not imply process boundary either.
Good future cleanup sequence:
- document the current IPX ownership in
nwserv.candnwconn.c; - add
nwtransport.c/transport headers as wrappers around existing IPX paths; - move IPX-only helpers into
nwipx.cwithout behavior changes; - gradually replace raw
ipxAddr_tuse in session-neutral code with a transport-neutral peer/session descriptor; - keep NCP providers and the endpoint audit table transport-independent;
- add
nwtcp.conly after the IPX wrapper boundary is stable.
This keeps TCP/IP support compatible with the broader redesign: transport IO is
separated from NCP semantics, but the existing nwserv/nwconn process model
remains intact.
Transport configuration model
The future transport configuration should keep TCP/IP and IPX under the same transport namespace while preserving their different setup requirements. TCP/IP only needs listeners and peer handling. IPX can additionally configure kernel IPX interfaces, frame types, internal network, auto-discovery and routes.
TCP/IP should use libowfat socket helpers where they fit the project style.
The audited mars-libowfat bundle exposes socket_tcp4() and socket_tcp6()
for TCP sockets, not generic socket4/socket6 entry points. Listener setup
should pair those with socket_bind4_reuse()/socket_bind6_reuse(),
socket_listen(), and the nonblocking/close-on-exec accept helpers. The
configuration should support a default listener and optional per-interface binds:
[transport.tcp]
enabled = true
listen = 0.0.0.0:524
listen6 = [::]:524
[transport.tcp.eth0]
enabled = true
interface = eth0
listen = 192.168.1.10:524
[transport.tcp.eth0.v6]
enabled = true
interface = eth0
listen6 = [fe80::1%eth0]:524
A later src/nwtcp.c should keep libowfat ownership visible and small: parse
IPv4/IPv6 listeners, create socket_tcp4()/socket_tcp6() descriptors, set
nonblocking/close-on-exec through the accept helpers where available, register
listeners with io_fd(), wait with the io_* readiness API, then hand accepted
sessions to nwtransport as transport-neutral peers. It should not create a
second NCP dispatcher and should not make namespace or bindery providers depend
on TCP-specific state.
IPX configuration should not depend on the external ipx-utils tools at
runtime. Those tools remain useful as fallback/debug/admin utilities, but
mars-nwe should eventually own the small kernel-ioctl setup path internally.
The later implementation can live in a file such as src/nwipx_config.c and be
called before the IPX transport starts.
The relevant kernel setup model is the same one used by ipx-utils:
socket(AF_IPX, SOCK_DGRAM, AF_IPX)
ioctl(SIOCSIFADDR, struct ifreq *) add/delete interface/internal net
ioctl(SIOCGIFADDR, struct ifreq *) check interface
ioctl(SIOCAIPXPRISLT) auto primary select
ioctl(SIOCAIPXITFCRT) auto interface create
ioctl(SIOCIPXCFGDATA) read config data
ioctl(SIOCADDRT) / ioctl(SIOCDELRT) route add/delete
If socket(AF_IPX, ...) fails while IPX transport or IPX kernel configuration
is requested, the error should be explicit: IPX transport was requested but
AF_IPX is unavailable.
Section names should encode the IPX frame token without dots. A literal
802.2 token is readable to a human, but it is awkward in a dotted INI
hierarchy because the dot already separates section components. Use normalized
frame tokens in section names and optionally accept a frame_type key for
compatibility.
[transport.ipx]
enabled = true
configure_kernel = true
auto_primary = false
auto_interface = false
[transport.ipx.local]
enabled = true
network = 0x00000001
node = auto
primary = true
[transport.ipx.8022.eth0]
enabled = true
interface = eth0
network = 0x00000002
node = auto
primary = true
[transport.ipx.8023.eth0]
enabled = false
interface = eth0
network = 0x00000003
node = auto
primary = false
[transport.ipx.etherii.eth0]
enabled = false
interface = eth0
network = 0x00000004
node = auto
primary = false
[transport.ipx.snap.eth0]
enabled = false
interface = eth0
network = 0x00000005
node = auto
primary = false
[transport.ipx.tr8022.tr0]
enabled = false
interface = tr0
network = 0x00000006
node = auto
primary = false
[transport.ipx.auto.eth0]
enabled = false
interface = eth0
network = auto
node = auto
primary = auto
[transport.ipx.route.00000009]
enabled = false
target = 0x00000009
router = 0x00000002:00:11:22:33:44:55
Parser rule:
transport.ipx.<frame>.<ifname>
<frame> := 8022 | 8023 | etherii | snap | tr8022 | auto
The internal network remains special because it is not bound to an Ethernet
frame type. Keep it under [transport.ipx.local], with IPX_INTERNAL and
IPX_FRAME_NONE in the kernel setup layer.
NCP providers remain transport-neutral in all of these configurations. MAC namespace support, LONG/OS2 namespace support, AFP metadata, bindery, queue, file, trustee and salvage handlers should not inspect whether the packet arrived through IPX or TCP/IP.
Optional userland IPX L2 backend and SPX boundary
The kernel IPX backend remains the first compatibility target because it matches
historical mars-nwe behaviour and can reuse the existing AF_IPX socket path
when a host still provides it. A second backend is nevertheless worth planning:
modern Linux systems often lack a usable IPX stack, and an Ethernet level IPX
backend would let mars-nwe serve NetWare 3.x clients without the kernel IPX
module.
The uploaded Rust nwserver source was reviewed as a design reference only. It
opens a datalink channel with pnet::datalink, filters Ethernet frames with
EtherType IPX (0x8137), builds the IPX header in userland, and dispatches by
IPX destination socket: NCP 0x0451, SAP 0x0452, and RIP 0x0453. Its README
also says that it supports only Ethernet_II IPX frames. That confirms that a
kernel-free Mars backend is feasible, but the GPL-3.0-or-later Rust source must
not be copied into this GPL-2.0-only tree.
Planned split:
src/nwtransport.c common transport boundary
src/nwipx.c IPX common packet/address helpers
src/nwipx_kernel.c existing AF_IPX/SOCK_DGRAM backend
src/nwipx_l2.c optional AF_PACKET/ETH_P_IPX backend
src/nwipx_config.c kernel IPX interface/route setup, kernel backend only
src/nwspx.c optional userland SPX layer, later and only if needed
src/nwtcp.c later TCP/IP NCP transport
nwipx_l2.c should start with Ethernet_II only. Additional frame formats such
as 8022, 8023, snap, and tr8022 can be added after the common IPX packet
boundary is stable. In L2 mode, mars-nwe owns SAP/RIP advertisement and basic
routing responses in userland because no kernel IPX stack is present to do that
work. NCP providers must still receive only transport-neutral sessions and NCP
requests.
Do not vendor a Linux capability library for this first step. The L2 backend
should simply try to open the packet socket, for example with
socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IPX)), and report missing privileges
clearly. Deployment decides whether the daemon runs as root, has
CAP_NET_RAW/CAP_NET_ADMIN, or uses a systemd unit with matching ambient
capabilities. If a later hardening patch needs privilege dropping, prefer an
optional system libcap path over adding a new bundled dependency.
The configuration model keeps the existing [transport.ipx.*] namespace and
selects a backend explicitly:
[transport.ipx]
enabled = true
backend = kernel ; kernel | l2
configure_kernel = true ; only meaningful for backend=kernel
[transport.ipx.etherii.eth0]
enabled = true
backend = l2
interface = eth0
network = 0x00000002
node = auto
primary = true
When backend = kernel, missing AF_IPX is an error unless another enabled
transport can serve the requested mode. When backend = l2, missing packet
socket privileges should produce a targeted message such as:
IPX L2 backend on eth0 requires CAP_NET_RAW/CAP_NET_ADMIN or root privileges.
Use backend=kernel on hosts with AF_IPX, or grant packet-socket privileges.
SPX must not depend on kernel SPX. If SPX is required for future compatibility,
implement it as a userland layer above the common IPX send/receive boundary. Do
not mix SPX connection state into NCP providers or namespace handlers. The
first L2 milestone is IPX Ethernet_II plus SAP/RIP plus NCP socket 0x0451; SPX
is a later audit/compatibility track, not a blocker for namespace work.
Linux 2.4.37.9 was audited and its disabled experimental SPX implementation is
kept in-tree as historical reference only: src/kernel/af_spx.c and
include/kernel/spx.h. The 2.4.37.9 net/ipx/Config.in comments out the
CONFIG_SPX prompt even though net/ipx/Makefile still has an af_spx.o rule.
Use that code to understand SPX headers, connection IDs, sequence/ack/allocation
fields, watchdogs, retransmit queues and disconnect handling; do not compile the
old kernel code or copy its old locking/socket architecture into Mars.
Logging subsystem and project-owned backends
The dispatch, provider, directory, and transport redesigns all need better logging than scattered ad-hoc debug messages. The goal is not only prettier logs. The important requirements are:
- consistent severity levels;
- consistent categories across processes and providers;
- request correlation from
nwserv/nwconnthrough provider handoff and back; - safe redaction of secrets before any backend sees the message;
- configurable routing to local files, syslog, or later remote collectors;
- auditable security events such as password recovery, TLS failures, rejected provider IPC, and directory/bootstrap changes.
The mars-nwe source should not call a third-party logging library directly from random endpoint handlers. It should grow a small internal facade first:
typedef enum {
NWLOG_CORE,
NWLOG_CONFIG,
NWLOG_TRANSPORT,
NWLOG_NCP,
NWLOG_HANDOFF,
NWLOG_BINDERY,
NWLOG_QUEUE,
NWLOG_DIRECTORY,
NWLOG_NDS,
NWLOG_LDAP,
NWLOG_AUTH,
NWLOG_ACL,
NWLOG_RECOVERY,
NWLOG_SECURITY
} NwLogCategory;
The normal facade level vocabulary is deliberately semantic, not a blind copy of
the legacy XDPRINTF thresholds:
typedef enum {
NWLOG_LEVEL_ERROR = 1, /* failed operation, protocol error, fatal setup */
NWLOG_LEVEL_WARN = 2, /* suspicious but recoverable condition */
NWLOG_LEVEL_INFO = 3, /* normal operational state change */
NWLOG_LEVEL_DEBUG = 4, /* local developer/bring-up diagnostic */
NWLOG_LEVEL_TRACE = 5 /* packet/message/handoff path trace */
} NwLogLevel;
nwlog_detail() is outside the administrator level ladder. It is the
MAINTAINER_BUILD-only sink for useful legacy 6..99 traces and must not be
controlled by nw.ini. In a maintainer build the compile-time build choice is
the gate: detail call sites may emit even when the normal configured level is
error, although normal debugging will usually also set the administrator level
to trace so 1..5 context is visible around the detail line.
NWLOG_LEVEL_DEBUG is for local diagnostic detail inside one subsystem or endpoint.
NWLOG_LEVEL_TRACE is for following packet, message or handoff flow across
connection, process, provider or transport boundaries. Trace is not a synonym
for raw sensitive dumps; those belong to nwlog_detail() when they are still
useful.
The normalized configuration should use one global default plus optional
process-level overrides. The old numeric entries 100 through 106 remain
accepted for compatibility, but new documentation and tools should prefer the
semantic form:
[logging]
level = info ; off|0, error|1, warn|12, info|123, debug|1234, trace|12345
backend = simple ; simple|syslog|journald|gelf|jsonfile later; facade API unchanged
[logging.process.nwserv]
level = info
[logging.process.ncpserv]
level = info
[logging.process.nwconn]
level = trace
[logging.process.nwbind]
level = debug
[logging.process.nwrouted]
level = warn
The normalized numeric form is a cumulative level mask, not a single enabled
digit: 0/off/none, 1/error, 12/warn/warning, 123/info,
1234/debug, and 12345/trace. Setting a process to trace therefore
means that process emits error, warn, info, debug, and trace
events. A process override replaces only
that process threshold; missing process entries inherit [logging] level. The
first implementation should not require per-category thresholds. Categories
are still recorded on every event for routing, filtering, and later backend
configuration, but the stable configuration contract is global plus process
overrides first.
Maintainer detail is deliberately not a config value. There is no
level = detail, no maintainer_detail = true, and no production INI switch
that enables nwlog_detail(). In normal builds detail call sites are no-ops.
In MAINTAINER_BUILD, detail output is controlled by the compile-time build
choice, while the ordinary normal-level threshold still controls the surrounding
normal log context (trace/12345 is usually the useful setting).
Legacy numeric sections map into the same model during migration:
| Legacy entry | Normalized process override |
|---|---|
100 |
IPX setup / future ipx process or transport helper |
101 |
nwserv |
102 |
ncpserv |
103 |
nwconn |
104 |
nwclient |
105 |
nwbind |
106 |
nwrouted |
Legacy entries historically accepted values 0..99; the compatibility parser
may continue to interpret old single-threshold values there while XDPRINTF
code is being migrated. The normalized [logging] and
[logging.process.<name>] form should document only the cumulative masks
0, 1, 12, 123, 1234, 12345 plus their semantic names. New
nwlog callers must not expose 6..99 as administrator-visible levels.
Conceptual call sites should be short and category-specific, not manual construction of large event structures in every handler:
nwlog_handoff(ctx, NWLOG_INFO,
"provider=%s request_id=%u selector=%s handoff=start",
provider_name, request_id, selector_path);
nwlog_recovery(ctx, NWLOG_WARN,
"admin password recovery requested dn=%s uid=%lu",
redacted_dn, (unsigned long)uid);
nwlog_ncp(ctx, NWLOG_TRACE,
"request_id=%u reply completion=0x%02x len=%u",
request_id, completion, reply_len);
nwlog_directory(ctx, NWLOG_INFO,
"schema migration step=%s", step_name);
The implementation can still normalize all events through a shared internal structure before they reach a backend:
struct nwlog_event {
NwLogCategory category;
NwLogLevel level;
const char *module;
const char *file;
int line;
uint32_t connection_id;
uint32_t request_id;
uint32_t sequence;
uint32_t task_id;
const char *ncp_path;
const char *provider;
const char *message;
};
void nwlog_emit(const struct nwlog_event *event);
Normal endpoint and provider code should use wrappers such as nwlog_ncp(),
nwlog_handoff(), nwlog_bindery(), nwlog_queue(), nwlog_directory(),
nwlog_nds(), nwlog_ldap(), nwlog_auth(), nwlog_acl(),
nwlog_recovery(), and nwlog_security(). Those wrappers populate the shared
nwlog_event fields from the current NCP/provider context and pass the result to
nwlog_emit(). Only unusual code paths should build nwlog_event manually.
The project-level layout should follow the existing include/source split:
include/nwlog.h public internal logging facade used by mars-nwe modules
src/nwlog.c facade implementation, redaction, common formatting
src/nwlog_simple.c simple stderr/stdout/file/callback backend
src/nwlog_syslog.c optional syslog(3)-style backend derived from simple
src/nwlog_gelf.c optional GELF UDP/TCP backend
src/nwlog_jsonfile.c optional JSON-lines file backend for Filebeat/Elastic Agent
Protocol handlers, providers, nwconn, nwserv, nwbind, future nwqueue,
nwdirectory, and nwnds should include include/nwlog.h only. They should
not include backend headers or call backend-specific macros directly.
That facade can initially keep using the existing mars-nwe logging functions,
stderr, or a small vendored backend. Later it may grow simple and advanced
backends behind the same API.
The legacy audit in doc/LOG_LEVEL_AUDIT.md changes the migration detail but not
the overall design. Old MARS already mostly behaves like a compact 1..5
verbosity scale, but the buckets are uneven: legacy 1 mixes errors, warnings
and notes, legacy 2..4 are various debug depths, and legacy 5 is verbose
safe debug/trace. The facade should therefore not preserve those overloaded
names. It should redistribute the normal administrator-visible ladder as
error, warn, info, debug and trace, while the compatibility layer still
accepts old numeric thresholds during migration. Values outside 1..5 are
exceptions and often represent maximum-debug/raw/special traces rather than
stable severities. The exception is explicit and narrow: legacy high levels
6..99 should migrate, when still needed, to nwlog_detail(), a
maintainer-build-only API for unsafe deep diagnostics.
There are several useful backend classes, but they should be implemented as small project-owned backends rather than through zlog:
- Simple built-in backend. A small in-tree backend handles stderr/stdout, optional local file output and callback hooks. It should emit structured, grep-friendly lines so systemd services can rely on journald capture without linking to libsystemd.
- Native journald backend, optional. If native journald fields are wanted,
add an optional build path using
sd_journal_send()behindnwlog. The default systemd path remains stderr/stdout capture, so libsystemd is never a hard dependency. - Syslog bridge backend. Classic syslog output should live in its own
backend using
openlog(),syslog()andcloselog()or the platform equivalent. - GELF backend. Implement a small GELF formatter/sender in mars-nwe when needed, initially using UDP/TCP sockets. No external Graylog library is required.
- JSON-lines backend. Write one JSON event per line to a file so Filebeat or Elastic Agent can ship logs. Do not implement the Beats protocol directly inside mars-nwe.
Before choosing any optional backend, packaging, license compatibility, portability, and maintenance state still need to be verified for the target distributions. The architecture must not depend on one backend being available.
The preferred dependency shape is therefore:
mars-nwe code
-> nwlog facade
-> simple backend: stderr/stdout for systemd/journald capture, optional local file/callback
-> native journald backend: optional libsystemd build path
-> syslog backend: classic syslog(3) output when explicitly configured
-> gelf backend: project-owned GELF formatter/sender
-> jsonfile backend: JSON-lines file for Filebeat/Elastic Agent
Do not make endpoint code depend on any backend-specific type or macro.
Keeping nwlog in the middle gives mars-nwe one place to:
- inject correlation fields such as
connection_id,request_id,sequence,task_id, provider name, and NCP selector path; - redact or suppress sensitive fields before formatting;
- enforce no-secret logging rules even when logs are routed to remote systems;
- keep a fallback backend for minimal builds or platforms without optional logging integrations;
- change or add backends later without touching protocol handlers.
Remote logging is useful, but it must be treated as a security boundary. A GELF or Graylog-style collector, syslog relay, pipe, or any other remote forwarding path must receive structured, redacted events only. It must never receive raw NCP request bodies, decoded handoff payloads, passwords, one-shot recovery tokens, private keys, or raw directory authentication material.
A future documented INI could expose the normal logging policy without forcing
admins to edit C-style backend internals directly. This policy covers ordinary
semantic levels only (error, warn, info, debug, trace); it must not
include a switch for maintainer-detail output:
[logging]
backend = simple ; simple, journald, syslog, gelf, jsonfile
level = info
legacy_threshold = 1 ; compatibility with INI 100..106 style debug levels
redact_secrets = yes
[logging.category]
ncp = info
handoff = info
auth = warn
recovery = warn
directory = info
transport = info
[logging.debug]
packet_hexdump = no
handoff_hexdump = no
unsafe_raw_payloads = no ; normal builds still redact/suppress raw secrets
nwlog_detail() is deliberately absent from this INI. It is only active in a
MAINTAINER_BUILD and is the only migration target for useful legacy 6..99
traces that may contain passwords, authentication material, raw packets, internal
buffers or alias/inode internals. In normal builds it must produce no output,
regardless of configured level, legacy_threshold or category settings. In a
maintainer build, however, the compile-time build flag is the authorization
decision: nwlog_detail() may emit regardless of whether the ordinary runtime
level is error, warn, info, debug or trace. It still remains absent
from nw.ini; administrators cannot enable it accidentally, and developers who
want useful surrounding context will normally also set the visible runtime level
to trace.
Raw packet or handoff hexdumps should be opt-in developer diagnostics, not normal admin logging. Even then, auth/password fields should be redacted where the layout is known. The safe default is length-only logging for sensitive payloads.
Important audit events should be logged even at normal levels:
- provider IPC connection accepted/rejected;
- provider IPC TLS/mTLS validation failure;
- directory store initialization and schema migration;
nwsetuppassword bootstrap or recovery actions;- bindery-to-directory migration actions;
- failed authentication attempts with redacted identities;
- NCP handoff timeout, dead provider, or mismatched reply correlation ID.
The logging cleanup should be a separate functional change from endpoint layout patches. Documentation-only endpoint audit patches may add log design notes, but they should not introduce new logging dependencies or change runtime logging behavior.
Logging connection
The dispatch redesign also supports the desired log cleanup. If every request has a context, logs can consistently include:
INFO NCP 23/109 DISPATCH type=0x2222 fn=0x17 sub=0x6d provider=nwbind/queue
INFO NCP 32/0 REPLY type=0x2222 fn=0x20 sub=0x00 result=0x00 len=4
WARN NCP 23/130 LAYOUT-MISMATCH sdk="32-bit JobNumber" code="16-bit parser"
Until the nwlog facade exists, endpoint-dispatch cleanup should still reuse
existing mars-nwe logging functions. Do not add direct backend calls or a parallel
logging path just to support one endpoint family. New namespace/libnwfs code may
wrap old logging locally, but the wrapper must keep the legacy 1..5 threshold
meaning and must not introduce raw/sensitive traces.
Migration plan
Phase 1: Name the existing conventions
Low risk. No behavior change.
- Add named constants or comments for the current
0,-1, and-2dispatcher results. - Keep existing control flow unchanged.
- Update comments so
return(-1)is never described ambiguously outside the exact dispatcher where it is meaningful.
Phase 2: Add an endpoint audit table
Low risk. Mostly documentation/debug.
- Add a table of known endpoints by request type, function, and subfunction.
- Mark provider, generation bucket, and implementation state.
- Use it to compare SDK/PDF/WebSDK coverage against actual handlers.
- Do not switch runtime dispatch to the table yet.
Phase 3: Introduce a thin NcpContext
Moderate risk if kept small.
- Wrap existing request and reply buffers without changing ownership.
- Use the context only in newly audited or newly implemented handlers.
- Keep old handlers callable until they are touched for another reason.
Phase 4: Convert small endpoint families first
Moderate risk, easy to test.
Good candidates:
0x2222/32old Semaphore calls;- direct calls such as End Of Job, Logout, and Negotiate Buffer Size;
- small message/station groups once their handoff has been audited.
Avoid converting queue and bindery first because they have more process coupling and more old/new layout variants.
Phase 5: Move runtime dispatch to tables gradually
Higher risk. Do this only after enough endpoint families have stable audit coverage and tests.
- Keep switch wrappers during the transition.
- Convert one family at a time.
- Preserve exact completion codes and reply lengths.
- Add targeted smoke tests for any family whose dispatch path changes.
Non-goals
This redesign should not:
- change protocol behavior merely to match a cleaner abstraction;
- remove NetWare 1.x/2.x/3.x compatibility paths;
- enable NetWare 4.x endpoints by default, or create stubs for NetWare 5.x/OES/MOAB/newer endpoints during the current planning scope;
- replace existing mars-nwe path, bindery, queue, AFP, trustee, or salvage backends with parallel databases;
- add a large external message bus dependency;
- rewrite all handlers in one patch;
- turn documentation-only endpoint audit patches into functional refactors.
Practical rule for future patches
For the ongoing endpoint documentation pass, keep doing the conservative thing:
- enumerate SDK/PDF/WebSDK/include endpoints for the family;
- compare them with actual
caselabels and forwarded destination handlers; - document missing, disabled, implemented, and later-generation slots;
- document request parser/handoff and response builder;
- record real layout differences, but do not change behavior in the same patch.
Functional cleanup should come later in small patches with tests.
Source license policy
The mars-nwe-owned C source and header files are GPL-2.0-only, with no
later-version grant. New and updated mars-nwe-owned .c and .h files should
carry SPDX-License-Identifier: GPL-2.0-only and keep copyright attribution for
Martin Stover as the original author plus Mario Fetka for current maintenance
work where project-level headers are added.
Third-party imports remain under their own upstream licenses inside the relevant
third_party/ subtree and must be documented separately.
Quota backend placement after 0343
The quota relocation keeps the two quota systems deliberately separated inside
libnwfs:
- generic quota frontend/API:
include/nwfs/quota.h,src/nwfs/quota/quota.c,nwfs_quota_* - NetWare metadata backend/API:
include/nwfs/nwquota.h,src/nwfs/quota/nwquota.c,nwfs_nwquota_* - volume-layer callers:
src/nwvolume.c
src/nwvolume.c remains the NCP/volume entry point and still owns Linux
quotactl() probing for now, but it no longer owns the volume-root
netware.userquota read/write/scan/adjust helpers. Those are now part of
nwfs so later namespace, owner-change, salvage, and directory-quota work can
share the same NetWare metadata accounting layer.
This is deliberately only the first relocation step: the confirmed 0340 semantics remain unchanged. The Linuxquota backend should move later, after the NWQUOTA relocation has passed the dual smoke again.
0344 quota backend naming
Quota code is split by backend so future BSD quota support does not get mixed with Linux-specific quotactl code:
include/nwfs/quota.h,src/nwfs/quota/quota.c: backend-neutral helpers only (nwfs_quota_*).include/nwfs/nwquota.h,src/nwfs/quota/nwquota.c: NetWare metadata/NWQUOTA backend only (nwfs_nwquota_*).include/nwfs/lnxquota.h,src/nwfs/quota/lnxquota.c: Linux kernel quotactl backend only (nwfs_lnxquota_*).
Do not merge these back together; a later BSD backend should use its own
bsdquota.c/h and nwfs_bsdquota_* names.
Quota backup and restore mirror
Linuxquota remains the primary backend whenever the kernel quota entry is
available. To support backup tools that preserve netware.* xattrs, successful
Linuxquota restriction changes are also mirrored to the volume-root
netware.userquota metadata.
The mirror is not authoritative while Linuxquota works. It is a restore source:
if a restored Linux filesystem supports quota but has no per-user kernel quota
entry yet, or reports an unlimited entry while restored metadata contains a finite
restriction, mars-nwe may read netware.userquota, write that restriction back
to Linuxquota, and then continue using Linuxquota as the primary backend.
This keeps the backend boundaries clear:
quota.c/h: common frontend helpers only.nwquota.c/h: NetWare metadata storage and metadata accounting.lnxquota.c/h: Linuxquotactl()access only.- future
bsdquota.c/h: BSD-specific quota access only.
NSS/libnwfs feature scope after namespace work
After the 0351 decision, the next implementation line starts by closing the NetWare-3.x quota block, then moves to NSS-derived namespaces. The rest of NSS should still be mined in a way that keeps protocol scope and backend readiness separate.
Directory disk-space restrictions are a good example. They are not solely a
NetWare 5.x feature in the NCP reference: classic directory disk-space
restriction calls exist in the NetWare 3.x/4.x family. That puts directory
quotas on the future MARS-NWE 3.x compatibility roadmap. The namespace-aware
variant belongs to the later NetWare 4.x line and should remain planned or
behind MARS_NWE_4 until the 3.x target is complete. Therefore the
dirQuotas.c model is worth adapting into libnwfs. Patch 0351 starts this
actively rather than keeping it dormant: 22/35 and 22/36 are wired to
netware.metadata.nwm_quota_limit, 22/40 reads Sequence as documented
Lo-Hi, and nwfs_dirquota_test covers the core arithmetic/model semantics
(4K block units, unlimited values, available-space calculation and over-limit
result calculation). Parent-min restriction selection and create/write/delete
adjustment hooks remain deeper NSS-alignment follow-up work; the classic 3.x
set/get/clear endpoint block itself is closed and audited in 0357.
For later NSS pieces such as data streams, extended attributes, object IDs,
search maps and salvage, use the same pattern: direct adaptation into
mars-nwe/libnwfs files, compile/link/logic CTests while dormant, and no
NCP-visible claims until the backend can answer truthfully. Classify each piece
by the NDK/Core Protocols generation before wiring it: 1.x/2.x/3.x belongs to
the active 3.x roadmap; 4.x belongs behind MARS_NWE_4; 5.x/OES/newer remains
notes/tests only. Compression in particular appears in later management and
namespace-aware families, so it must not synthesize fake current-compression
files, decompression lists or counters just to satisfy management selectors.
0351 3.x directory quota implementation note
The first NetWare-3.x directory-quota pass is active as of patch 0351. Documentation uses decimal NCP numbers, while the code uses the wire subfunction bytes:
22/35 -> 0x23 Get Directory Disk Space Restriction
22/36 -> 0x24 Set Directory Disk Space Restriction
22/40 -> 0x28 Scan Directory Disk Space
src/nwfs/quota/dirquota.c is the libnwfs home for portable directory-quota math and NSS/OES-compatible netware.metadata.nwm_quota_limit conversion. The live NCP parser stays in src/nwconn.c. 22/35 returns no entries for unrestricted directories and one current-directory entry for a finite restriction. 22/36 stores or clears the directory restriction in netware.metadata. 22/40 now reads its sequence field as documented Lo-Hi and continues to use the existing MARS DOS scan reply shape until AFP/MAC_RF resource fork data exists.
This closes the first 3.x directory-quota endpoint gap without pulling in the full NSS dirQuotas.c runtime/cache. Deeper NSS-style parent-min restriction selection, used-space adjustment hooks on create/delete/rename/write, and namespace-aware 87/39 remain follow-up work.
NCPFS directory-quota smoke
As of 0357 the classic 3.x directory-quota endpoint block is marked done/audited.
The implementation has two test layers. The CTest helper
nwfs_dirquota_test validates the libnwfs arithmetic and metadata conversion.
The live smoke nwfs_ncpfs_dirquota_smoke.sh validates the NetWare 3.x NCP
path: it uses libncp NCPC_SFN(22, 36) to send decimal 22/36 (wire/code
0x24) and NCPC_SFN(22, 35) for decimal 22/35 (wire/code 0x23). The
smoke intentionally verifies both the NCP readback and the host-side
netware.metadata result, including clear semantics: limit 0 removes the
active zMOD_DIR_QUOTA bit instead of leaving an active unlimited value. That
catches parser/wire mistakes and xattr-storage mistakes independently.
Patch 0357 only corrected the smoke validator: after 0356 the live clear output is
dirQuotaLimit=9223372036854775807 inactive, which is the intended cleared
metadata state, so the test must match the trailing status word exactly.
Quota smoke-test redesign note after 0381
The quota live-test direction is now an evidence-bundling wrapper rather than a
large number of separate manual commands. nwfs_ncpfs_all_quota_smoke.sh runs
the existing focused smokes against the two canonical volumes (QUOTA/Linuxquota
and SYS/NWQUOTA), captures one log per volume and a slice of nw.log starting
at the wrapper start offset, and writes a compressed bundle. This keeps live
failures reproducible without asking the tester to collect shell scrollback,
per-smoke logs and server logs by hand.
Do not convert this wrapper into normal CTest yet: with the current IPX/ncpfs
transport it needs a running mars_nwe and privileged mounts. The later test
harness redesign should use the existing config.h.cmake test-build path as the
base, create a disposable SYS tree and quota image, and start a local mars_nwe
instance once a TCP/IP NCP path is available. At that point the same wrapper can
become a fixture-driven integration test.
2026-06-12 all-smoke log access note
The all-in-one quota smoke keeps its temporary evidence directory world-readable and traversable immediately after creation, matching the dual userquota smoke. This is intentional because the live smokes are commonly run as root but their logs/archives are usually copied or uploaded later by a normal desktop user.
0383 all-smoke finalization fix
- The all-quota wrapper must never
exitfrom insiderun_logged; doing so stops the script after the first subtest (typically CTest) and prevents the later live smokes,nw.logslice, tar.gz and zip from being emitted. - Keep the archive outside the output directory (
/tmp/<timestamp>-quota-all-smoke.*) so tar/zip do not recursively include their own output file.
0384 Linuxquota clear/log cleanup
The all-quota smoke after 0383 showed the quota functionality green, but the
server log still contained an ugly QUOTA-side message during userquota remove:
Linux quota set unavailable ... result=set-failed; using nwquota metadata backend.
That is contrary to the backend ownership rule. On Linuxquota volumes the
kernel quota backend is authoritative; NWQUOTA metadata is only a mirror and a
restore source, not an alternate authority after a kernel set-failed.
0384 tightens this in two ways:
lnxquota.cno longer reuses the broaddqb_validmask returned byQ_GETQUOTAwhen callingQ_SETQUOTA; it marks only block limits valid. This matches the project-quota fix made earlier and avoids kernels rejecting attempts to set usage/grace/inode fields when mars-nwe only means to update a NetWare user-volume block restriction.nw_set_vol_restrictions()falls back to NWQUOTA only when Linux quota is unavailable (no-device,unsupported,probe-failed). A realset-failedremains a Linuxquota error instead of silently switching QUOTA to a parallel metadata-authoritative path.
Done / stabilized design decisions
Quota authority split
- Linuxquota-capable volumes use Linux quota as authoritative live state and enforcement. NetWare/NSS metadata is a mirror for backup/restore and tooling, not an independent authority.
- NWQUOTA/metadata-only volumes use NetWare metadata as both authoritative state and enforcement basis.
- Directory quota metadata uses
netware.metadata.nwm_quota_limitpluszMOD_DIR_QUOTA; user quota metadata uses canonicalnetware.userquota.0withreserved2=0. - No private MARS persistent usage xattr is part of the design.
- The live all-smoke and DOS board-tool quota smoke are the regression gates for this area.
NSS low-level library imports
- Prefer importing small GPL-2 NSS low-level library helpers with original file/API names where they can be compiled outside the NSS runtime.
- Imported compiled helpers should live directly in
libnwcoresource/include space rather than in an artificialnss/subpath; existing NSS SDK compatibility headers may keep their original source-like paths. - Initial compiled import is
bitmap.c/bitmap.hinlibnwcore; replace local duplicate bitmap/bit allocation helpers gradually only after tests cover each call site. - Keep libowfat-provided primitives where they are already the better fit; use NSS helpers where NetWare/NSS source compatibility or media-layout semantics matter.
Pinned terminal and INI backends
The first concrete toolbox/core dependency split is:
third_party/termbox2pinned tov2.5.0, used only belowlibnwtui;third_party/iniparserpinned tov4.2.6, used only belowlibnwcorenw_ini_*;libnwcoreowns both the firstnwlog_*implementation and the sharednw_ini_*wrapper;- tools never include
termbox2.horiniparser.hdirectly.