2445 lines
95 KiB
Markdown
2445 lines
95 KiB
Markdown
# mars-nwe NCP dispatch redesign notes
|
|
|
|
This file collects design notes for a possible cleanup of the internal NCP
|
|
handoff path. It is intentionally separate from `TODO.md`: the TODO file should
|
|
track concrete bugs and endpoint audit follow-ups, while this file describes a
|
|
larger architecture direction 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.
|
|
|
|
## Current problem
|
|
|
|
The current NCP path grew around several cooperating processes and handlers:
|
|
|
|
- `nwconn.c` owns the connection/session side and receives most packets first.
|
|
- `nwbind.c` handles 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.c` by returning `-1` from the `nwconn.c`
|
|
dispatcher.
|
|
- Some calls are forwarded with saved request state by returning `-2`, so that
|
|
`nwconn.c` can do post-processing after `nwbind.c` has 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.md` can 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:
|
|
|
|
```text
|
|
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:
|
|
|
|
1. decode the packet envelope;
|
|
2. identify the endpoint;
|
|
3. decode the endpoint request body;
|
|
4. execute the backend operation;
|
|
5. encode the endpoint reply body;
|
|
6. 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:
|
|
|
|
```c
|
|
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:
|
|
|
|
- `function` identifies the first NCP selector byte;
|
|
- `selector[]` identifies any nested selector path after that byte;
|
|
- `request` and `request_len` are the bytes after the already-decoded envelope;
|
|
- `reply` and `reply_len` are the bytes before the common NCP response envelope;
|
|
- `completion` is 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:
|
|
|
|
```c
|
|
#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:
|
|
|
|
```c
|
|
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:
|
|
|
|
```c
|
|
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:
|
|
|
|
```c
|
|
{ 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 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:
|
|
|
|
```text
|
|
request decode
|
|
-> validation
|
|
-> backend operation
|
|
-> reply encode
|
|
```
|
|
|
|
For complex endpoints this could become explicit helper functions:
|
|
|
|
```c
|
|
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.c` mutates the request before forwarding;
|
|
- whether `nwbind.c` or another provider builds the final reply;
|
|
- whether `nwconn.c` expects post-processing after the provider reply.
|
|
|
|
Examples of handoff cases that need this clarity:
|
|
|
|
- Queue calls where `nwconn.c` expands paths or inserts job file handles before
|
|
`nwbind.c` sees 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:
|
|
|
|
```text
|
|
Forward to nwbind with the original subfunction byte and payload unchanged.
|
|
No nwconn post-processing is expected; nwbind builds the completion-only reply.
|
|
```
|
|
|
|
or:
|
|
|
|
```text
|
|
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.c` post-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:
|
|
|
|
```text
|
|
Every provider process returns exactly one internal handoff reply for every
|
|
internal handoff request it accepts.
|
|
```
|
|
|
|
That internal reply is not the same thing as a client-visible NCP reply. A
|
|
provider may explicitly say that no client reply should be sent, but it should
|
|
still send a formal internal result back to the caller. This avoids silent
|
|
success/failure paths and makes timeout/error handling deterministic.
|
|
|
|
Conceptual reply kinds:
|
|
|
|
```c
|
|
typedef enum {
|
|
NW_HR_REPLY, /* provider produced a client NCP reply payload */
|
|
NW_HR_NO_REPLY, /* provider handled it; 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:
|
|
|
|
```text
|
|
kind = NW_HR_REPLY
|
|
completion = 0x00
|
|
reply_len = 0
|
|
```
|
|
|
|
The true "do not answer the client" case is explicit:
|
|
|
|
```text
|
|
kind = NW_HR_NO_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:
|
|
|
|
```c
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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 `nwbind` replies;
|
|
- 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:
|
|
|
|
```c
|
|
#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_ERROR` with an internal error code;
|
|
- provider returned `NW_HR_FORWARD` to 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:
|
|
|
|
```text
|
|
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_REPLY` is 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:
|
|
|
|
```text
|
|
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_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.
|
|
|
|
### Migration order for handoff normalization
|
|
|
|
The safe order is:
|
|
|
|
1. document current `nwconn`/`nwbind` handoff behavior;
|
|
2. add names for current magic values without changing behavior;
|
|
3. add a small wrapper such as `ncp_handoff_to_provider()` that still calls the
|
|
old path internally;
|
|
4. introduce a formal internal reply object in the wrapper;
|
|
5. make the wrapper always return a formal reply, including `NO_REPLY`;
|
|
6. centralize final client reply sending in `nwconn` for converted paths;
|
|
7. only then attach future providers such as `nwqueue` or `nwnds`.
|
|
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
client -> nwconn -> direct provider IPC -> provider -> nwconn -> client
|
|
```
|
|
|
|
not like this:
|
|
|
|
```text
|
|
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 `nwserv` to understand every provider payload;
|
|
- fewer decoded password/auth/directory payloads visible to the supervisor;
|
|
- easier provider-specific timeouts and back-pressure;
|
|
- clearer ownership: `nwconn` owns the client connection, providers own their
|
|
service logic, and `nwserv` owns 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 wolfSSL/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:
|
|
|
|
```text
|
|
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; wolfSSL-backed TLS with mutual authentication is required.
|
|
```
|
|
|
|
So the local default may remain simple and compatible:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
nwconn -> provider over TCP:
|
|
wolfSSL 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:
|
|
|
|
```text
|
|
Client NCP/NDS transport:
|
|
compatibility first; no blanket TLS requirement for classic clients.
|
|
|
|
LDAP/LDAPS/StartTLS:
|
|
handled by nwdirectory; wolfSSL is appropriate at that network edge.
|
|
|
|
Internal provider IPC over TCP:
|
|
always wolfSSL/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 wolfSSL uses in the long-term design:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
libflaim
|
|
persistent embedded database engine
|
|
|
|
libdirectory
|
|
shared internal directory API/library used by nwbind, nwnds, nwdirectory,
|
|
and setup/provisioning tools
|
|
owns the mars-nwe object model, schema helpers, indexes, ACL/auth
|
|
primitives, and persistence glue above libflaim
|
|
|
|
directory core/store
|
|
the data model and persistent store exposed through libdirectory
|
|
persists its data through libflaim
|
|
|
|
nwdirectory
|
|
mars-nwe service name for the integrated tinyldap-derived LDAP service
|
|
owns LDAP/LDAPS/StartTLS protocol handling
|
|
uses wolfSSL only at the LDAP network/TLS edge
|
|
calls the directory core/store, not Bindery or NDS packet handlers
|
|
|
|
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`.
|
|
|
|
`libdirectory` is the important internal boundary. It should be a real shared
|
|
API/library, not just a documentation label, because both `nwbind` and future
|
|
`nwnds` need directory data without speaking LDAP to each other. The library can
|
|
start small, but it should provide the common operations that legacy Bindery,
|
|
NDS compatibility, LDAP, and setup code all need:
|
|
|
|
```c
|
|
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,
|
|
`libdirectory` 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 one core:
|
|
|
|
```text
|
|
+----------------------+
|
|
| directory core/store |
|
|
| backed by libflaim |
|
|
+----------+-----------+
|
|
^
|
|
+---------------+---------------+
|
|
| |
|
|
nwdirectory nwnds
|
|
tinyldap-based LDAP/LDAPS NetWare 4.x/NDS semantics
|
|
frontend, wolfSSL TLS edge NCP/NDS compatibility layer
|
|
^ ^
|
|
| |
|
|
LDAP clients NetWare/NDS clients
|
|
```
|
|
|
|
The legacy Bindery service should also move toward this shared store over time:
|
|
|
|
```text
|
|
NetWare 3.x client -> Bindery NCP -> nwbind -> directory core/store -> libflaim
|
|
LDAP client -> LDAP/LDAPS -> nwdirectory -> directory core/store -> libflaim
|
|
NetWare 4.x client -> NDS/NCP -> nwnds -> directory core/store -> libflaim
|
|
```
|
|
|
|
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:
|
|
|
|
```text
|
|
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`, `nwnds`, and `nwdirectory` should all use `libdirectory`, or a clearly
|
|
defined IPC protocol modeled after the same directory API, to reach the directory
|
|
store.
|
|
|
|
FLAIM should therefore be treated as the long-term persistent storage engine for
|
|
the directory core, not as an LDAP-only database. `libdirectory` owns the schema,
|
|
object model, indexes, transactions, ACL checks, and authentication primitives
|
|
that the protocol/provider layers need. `nwdirectory` exposes those objects
|
|
through LDAP; `nwnds` exposes them through NDS semantics; `nwbind` exposes them
|
|
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 `libdirectory` directly:
|
|
|
|
```text
|
|
nwsetup -> libdirectory -> libflaim
|
|
```
|
|
|
|
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`, and `nwdirectory` will 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.
|
|
|
|
### 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:
|
|
|
|
```text
|
|
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
|
|
-> libdirectory schema objects
|
|
-> libflaim-backed store
|
|
```
|
|
|
|
`nwsetup` should eventually support both a native NetWare 4.11 schema import path
|
|
and an LDIF import/export path:
|
|
|
|
```text
|
|
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 `libdirectory`.
|
|
|
|
The import implementation should live below `nwsetup`, not inside protocol
|
|
handlers:
|
|
|
|
```text
|
|
nwsetup
|
|
-> schema import layer
|
|
-> NetWare 4.11/native schema reader
|
|
-> .SCH fragment reader
|
|
-> LDIF reader/writer
|
|
-> libdirectory 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.dat` or the installed tree;
|
|
- whether all core classes/attributes are present as ASN.1-like `.SCH` text,
|
|
binary records, or both;
|
|
- mapping of NetWare/NDS syntax IDs to `libdirectory` internal 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:
|
|
|
|
```text
|
|
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 `libdirectory` 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. `libdirectory`
|
|
should have a small central OID/prefix-map module that can map between:
|
|
|
|
```text
|
|
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`:
|
|
|
|
```text
|
|
first setup:
|
|
nwsetup asks interactively for the initial Admin/Supervisor password
|
|
nwsetup calls libdirectory authentication primitives
|
|
libdirectory 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:
|
|
|
|
```text
|
|
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 `libdirectory`, 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:
|
|
|
|
```text
|
|
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 libdirectory
|
|
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:
|
|
|
|
1. add the design boundary and naming notes first;
|
|
2. import or integrate tinyldap under the project-facing `nwdirectory` name;
|
|
3. keep client-facing wolfSSL usage confined to the LDAP/LDAPS/StartTLS
|
|
network edge initially; internal TCP-based provider IPC may also use wolfSSL
|
|
later, but only as a separate mTLS configuration;
|
|
4. introduce `libdirectory` before making Bindery depend on it;
|
|
5. add `nwsetup` as the direct bootstrap/provisioning tool for the initial
|
|
libflaim-backed directory store;
|
|
6. map selected `nwbind` objects/properties to `libdirectory` only after the
|
|
legacy behavior is documented;
|
|
7. add `nwnds` later as an NDS semantic layer, not as an LDAP wrapper;
|
|
8. 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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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/libflaim
|
|
planned fixed directory storage engine used through libdirectory
|
|
mars-nwe-maintained import/fork of the FLAIM source
|
|
converted to a real standalone-and-subdirectory CMake build
|
|
|
|
third_party/zlog
|
|
optional advanced logging backend used only through nwlog
|
|
|
|
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 libdirectory/libflaim instead of tinyldap's original flat files
|
|
```
|
|
|
|
### TLS dependency rule
|
|
|
|
wolfSSL should be the fixed TLS implementation shipped with the tree, similar in
|
|
spirit to the bundled yyjson dependency. Do not design the first TLS integration
|
|
as an abstract zoo of OpenSSL, LibreSSL, wolfSSL, and other providers. wolfSSL is
|
|
portable and can be configured through CMake for the needs of the target server,
|
|
so it should be the implementation used for:
|
|
|
|
- LDAP/LDAPS/StartTLS at the `nwdirectory` network edge;
|
|
- any future TCP-based internal provider IPC, where wolfSSL-backed mTLS is
|
|
required;
|
|
- future TLS-capable admin or service endpoints, if they are explicitly added.
|
|
|
|
However, mars-nwe code should still not include wolfSSL headers everywhere. TLS
|
|
call sites should go through a small internal facade:
|
|
|
|
```text
|
|
include/nwtls.h
|
|
src/nwtls.c
|
|
src/nwtls_wolfssl.c
|
|
```
|
|
|
|
The facade is not meant to promise equal OpenSSL/LibreSSL support. Its purpose
|
|
is to keep certificate loading, policy checks, mTLS requirements, error logging,
|
|
secure defaults, and wolfSSL-specific setup in one place. If another TLS backend
|
|
is ever evaluated much later, it can be hidden behind the same API, but the
|
|
planned bundled backend is wolfSSL.
|
|
|
|
Protocol handlers and provider code should therefore use `nwtls` concepts, not
|
|
raw wolfSSL types. Example ownership:
|
|
|
|
```text
|
|
nwdirectory LDAP listener -> nwtls -> wolfSSL
|
|
provider TCP IPC -> nwtls -> wolfSSL/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.
|
|
|
|
### zlog dependency rule
|
|
|
|
`zlog` should be treated as an optional advanced logging backend under
|
|
`third_party/zlog`, not as a direct API used by handlers. The mars-nwe public
|
|
logging API remains `include/nwlog.h`. If zlog is enabled, it is reached through
|
|
`src/nwlog_zlog.c` only:
|
|
|
|
```text
|
|
endpoint/provider code -> nwlog -> optional zlog backend
|
|
```
|
|
|
|
This keeps secret redaction, structured correlation fields, fallback logging, and
|
|
future backend changes centralized. zlog can provide administrator-controlled
|
|
category/rule/format routing, but it must never receive raw decoded NCP or
|
|
handoff payloads from bypass paths.
|
|
|
|
### 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:
|
|
|
|
```text
|
|
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 `FLAIM::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:
|
|
|
|
```text
|
|
required first targets:
|
|
FLAIM::ftk # cross-platform toolkit used by libflaim
|
|
FLAIM::flaim # classic FLAIM database library used by libdirectory
|
|
|
|
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
|
|
```
|
|
|
|
`FLAIM::flaim` depends on the FTK toolkit, 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 wolfSSL 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. A narrowly scoped CMake option may use wolfSSL's OpenSSL-compatibility
|
|
headers only inside `third_party/libflaim`, for example:
|
|
|
|
```text
|
|
FLAIM_USE_WOLFSSL_OPENSSL_COMPAT=ON/OFF/AUTO
|
|
```
|
|
|
|
That option would be an import/portability bridge for FLAIM only. It must not
|
|
change the mars-nwe TLS policy, expose OpenSSL-compatible types outside the
|
|
FLAIM/FTK build, or make LDAP/provider IPC use OpenSSL APIs. If possible, the
|
|
classic `FLAIM::ftk` and `FLAIM::flaim` targets should disable old TLS-facing
|
|
code entirely. If crypto primitives are required, prefer a small private shim in
|
|
the FLAIM build over leaking wolfSSL/OpenSSL-compat headers into `libdirectory`,
|
|
`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/libdirectory.h` or an equivalent internal
|
|
`libdirectory` API. Only the `libdirectory` 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 `libdirectory`.
|
|
|
|
### Forked tinyldap / nwdirectory rule
|
|
|
|
`tinyldap` is different from yyjson, wolfSSL, zlog, 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 `nwdirectory` service;
|
|
- LDAP/LDAPS/StartTLS integration through `nwtls`/wolfSSL;
|
|
- replacement or bypass of the original flat-file storage;
|
|
- later `libdirectory` and 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. The CMake
|
|
conversion should inventory those groups explicitly instead of copying the old
|
|
`all` target wholesale.
|
|
|
|
A reasonable first CMake split is:
|
|
|
|
```text
|
|
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 libdirectory
|
|
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 wolfSSL 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. 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,
|
|
but the mars-nwe `nwdirectory` target should move toward:
|
|
|
|
```text
|
|
nwdirectory -> libdirectory -> libflaim
|
|
```
|
|
|
|
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 directory API remains the internal boundary.
|
|
|
|
The CMake conversion should therefore support both use cases:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
LDAP/LDAPS client
|
|
-> nwdirectory service built from mars-tinyldap/tinyldap code
|
|
-> libdirectory
|
|
-> libflaim-backed store
|
|
```
|
|
|
|
`nwbind` and future `nwnds` should not speak LDAP internally just because the LDAP
|
|
service exists. They should use `libdirectory` 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:
|
|
|
|
```text
|
|
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 wolfSSL.
|
|
|
|
## 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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```ini
|
|
[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:
|
|
|
|
1. keep reading the legacy numbered config as today;
|
|
2. add a typed config model in code (`NwConfig`) whose fields are not tied to
|
|
either file syntax;
|
|
3. load legacy numbered config into that model;
|
|
4. load the new named INI format into the same model;
|
|
5. make `nwsetup` write the new named INI format atomically;
|
|
6. optionally provide import/export from legacy numeric config to named INI;
|
|
7. 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:
|
|
|
|
```text
|
|
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;
|
|
- `nwsetup` needs 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:
|
|
|
|
```text
|
|
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, wolfSSL 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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```c
|
|
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:
|
|
|
|
```c
|
|
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:
|
|
|
|
```text
|
|
IPX client -> nwipx -> nwtransport -> nwconn -> NCP dispatcher -> providers
|
|
TCP client -> nwtcp -> nwtransport -> nwconn -> NCP dispatcher -> providers
|
|
```
|
|
|
|
The provider/process rule still applies:
|
|
|
|
```text
|
|
Provider boundary does not imply process boundary.
|
|
Transport boundary does not imply process boundary either.
|
|
```
|
|
|
|
Good future cleanup sequence:
|
|
|
|
1. document the current IPX ownership in `nwserv.c` and `nwconn.c`;
|
|
2. add `nwtransport.c`/transport headers as wrappers around existing IPX paths;
|
|
3. move IPX-only helpers into `nwipx.c` without behavior changes;
|
|
4. gradually replace raw `ipxAddr_t` use in session-neutral code with a
|
|
transport-neutral peer/session descriptor;
|
|
5. keep NCP providers and the endpoint audit table transport-independent;
|
|
6. add `nwtcp.c` only 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.
|
|
|
|
## Logging subsystem and optional zlog backend
|
|
|
|
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`/`nwconn` through 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:
|
|
|
|
```c
|
|
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;
|
|
```
|
|
|
|
Conceptual call sites should be short and category-specific, not manual
|
|
construction of large event structures in every handler:
|
|
|
|
```c
|
|
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_DEBUG,
|
|
"reply completion=0x%02x len=%u", 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:
|
|
|
|
```c
|
|
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:
|
|
|
|
```text
|
|
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_zlog.c optional zlog backend, if enabled at build time
|
|
```
|
|
|
|
Protocol handlers, providers, `nwconn`, `nwserv`, `nwbind`, future `nwqueue`,
|
|
`nwdirectory`, and `nwnds` should include `include/nwlog.h` only. They should
|
|
not include zlog headers or call zlog 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.
|
|
|
|
There are three useful backend classes:
|
|
|
|
1. **Simple built-in backend.** A tiny C backend derived from `rxi/log.c` can be
|
|
imported into the source tree for simple builds. It should be treated as
|
|
vendored implementation code, renamed and namespaced for mars-nwe. The base
|
|
upstream code only provides local-style logging primitives such as stderr,
|
|
file output, and callback hooks; it is not an out-of-the-box syslog, GELF, or
|
|
remote routing solution. That is fine for the built-in backend, but mars-nwe
|
|
must adapt it for the project requirements: redaction hooks, category mapping,
|
|
context/correlation fields, optional file output, and journald-friendly
|
|
stderr/stdout formatting should live behind `nwlog`, not in endpoint code.
|
|
|
|
This backend is the right default for systemd deployments: services can log to
|
|
stderr/stdout and let journald capture, timestamp, rotate, and forward logs
|
|
according to the unit configuration. mars-nwe should still emit structured,
|
|
grep-friendly lines so `journalctl -u ...` remains useful without requiring a
|
|
heavier routing backend.
|
|
|
|
The project layout should make the imported code clearly internal, for
|
|
example:
|
|
|
|
```text
|
|
include/nwlog_simple.h internal/simple backend declarations, if needed
|
|
src/nwlog_simple.c imported/adapted rxi/log.c-style backend
|
|
```
|
|
|
|
Imported symbols such as `log_info`, `log_error`, or `log_add_fp` must be
|
|
renamed, hidden, or made `static` so they do not leak into the global mars-nwe
|
|
namespace. Public entry points should look like `nwlog_simple_init()` and
|
|
`nwlog_simple_emit()`.
|
|
2. **Syslog bridge backend.** If classic syslog integration is wanted, do not
|
|
overload the simple backend with syslog-specific behavior. Instead clone the
|
|
adapted simple backend structure into a separate implementation:
|
|
|
|
```text
|
|
include/nwlog_syslog.h internal syslog backend declarations, if needed
|
|
src/nwlog_syslog.c syslog(3)-style backend derived from simple
|
|
```
|
|
|
|
`nwlog_syslog.c` may reuse the same level/category mapping, redaction hooks,
|
|
event formatting helpers, and callback shape as `nwlog_simple.c`, but its
|
|
output path should be explicit: `openlog()`, `syslog()`, and `closelog()` or
|
|
the platform equivalent. This keeps `backend = simple` predictable for
|
|
stderr/stdout/file/journald use, while `backend = syslog` clearly means the
|
|
traditional syslog path. Provider and endpoint code must not care which of
|
|
the two is active.
|
|
3. **Advanced routing backend.** `zlog` remains the preferred candidate for
|
|
administrator-controlled routing because it is a C logging library with
|
|
category, format, and rule based configuration. That model fits mars-nwe
|
|
well: code can emit category-specific events such as `ncp`, `handoff`,
|
|
`queue`, `directory`, `auth`, or `transport`, while the administrator decides
|
|
in the logging configuration whether those categories go to a file,
|
|
stdout/stderr, syslog-style output, a pipe, or an external log-forwarder path.
|
|
The zlog project documentation describes these three core concepts as
|
|
categories, formats, and rules, where rules bind a category/level to an output
|
|
and format.
|
|
|
|
Before choosing any 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:
|
|
|
|
```text
|
|
mars-nwe code
|
|
-> nwlog facade
|
|
-> simple backend: src/nwlog_simple.c from imported/adapted rxi/log.c-style code
|
|
-> stderr/stdout for systemd/journald, optional local file/callback
|
|
-> syslog backend: src/nwlog_syslog.c derived from the simple backend shape
|
|
-> classic syslog(3) output when explicitly configured
|
|
-> optional advanced backend: src/nwlog_zlog.c using zlog
|
|
-> admin-configured zlog rules/formats/outputs
|
|
```
|
|
|
|
Do not make endpoint code depend on `zlog_category_t` or zlog macros directly.
|
|
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 zlog;
|
|
- 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 logging policy without forcing admins
|
|
to edit C-style backend internals directly:
|
|
|
|
```ini
|
|
[logging]
|
|
backend = simple ; simple, syslog, zlog
|
|
level = info
|
|
redact_secrets = yes
|
|
config = /etc/mars-nwe/zlog.conf
|
|
|
|
[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
|
|
```
|
|
|
|
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;
|
|
- `nwsetup` password 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:
|
|
|
|
```text
|
|
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 zlog calls or a parallel
|
|
logging path just to support one endpoint family.
|
|
|
|
## Migration plan
|
|
|
|
### Phase 1: Name the existing conventions
|
|
|
|
Low risk. No behavior change.
|
|
|
|
- Add named constants or comments for the current `0`, `-1`, and `-2`
|
|
dispatcher 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/32` old 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:
|
|
|
|
1. enumerate SDK/PDF/WebSDK/include endpoints for the family;
|
|
2. compare them with actual `case` labels and forwarded destination handlers;
|
|
3. document missing, disabled, implemented, and later-generation slots;
|
|
4. document request parser/handoff and response builder;
|
|
5. record real layout differences, but do not change behavior in the same patch.
|
|
|
|
Functional cleanup should come later in small patches with tests.
|