10.2 Dispatch and Authorisation
A parsed command runs a fixed sequence before it does anything.
- The shutdown gate. If peinit is shutting down, everything except
status,list,operation-status,boot,job-status,job-listandjob-stopis rejected.bootstays open because, like the service queries, it changes nothing. The three job commands stay open because a shutdown stops every submitted job anyway (§12.2), and an administrator watching that happen, or wanting a job gone sooner than the shutdown will manage, has a reason to ask. The gate runs before the access check, so during shutdown a caller who would have been denied is told the command is invalid for the current state rather than that they lack the right. - Resolve the target. A command naming no definition, and no
addressable definition-removed entry, returns
UNKNOWN_SERVICE; one naming no submitted job peinit holds returnsUNKNOWN_JOB. peinit does not synthesise a descriptor for something that does not exist. - AccessCheck. The caller's token, the target's descriptor, the generic mapping, the right the command requires (§4.6, §4.7), and an audit context naming the target: the service, the job, or the control door itself (§8.4). For a job the descriptor is the job's own and the mapping is the job mapping (§8.5).
- On denial, return
ACCESS_DENIED. The decision is recorded by KACS, askacs.audit.access.checkednaming the caller, the target and the access requested and granted, whenever the target's descriptor has a SACL that asks for it; every descriptor peinit builds itself audits every refusal. peinit writes no event of its own. Silent denial is not acceptable; a denial an administrator cannot see is indistinguishable from a bug, which is why the defaults audit, and why a descriptor written to the registry should keep a SACL that does (§4.6). - On grant, classify the command against the service's state (§10.3) and act.
10.2.1 Rights #
| Command | Right |
|---|---|
start | SERVICE_START |
stop | SERVICE_STOP |
restart | SERVICE_START and SERVICE_STOP, requested as one mask |
reload | SERVICE_INTERROGATE |
reset | SERVICE_STOP |
status | SERVICE_QUERY_STATUS |
list | Filtered per service by SERVICE_QUERY_STATUS |
operation-status | SERVICE_QUERY_STATUS on the target service |
job-status | JOB_QUERY on the job |
job-list | Filtered per job by JOB_QUERY |
job-stop | JOB_STOP on the job |
shutdown | SYSTEM_SHUTDOWN |
reload-config | SYSTEM_RELOAD_CONFIG |
boot | SYSTEM_QUERY_STATUS on the control descriptor, which by default every authenticated principal holds (§4.7) |
operation-status resolves the operation before it checks the right, so
an unknown identifier is reported as unknown regardless of who asked.
If the operation's service has since been discarded (§3.8), the right is
checked against the ServiceSecurity the service had when the operation
was created, which peinit records on the operation; a retained operation
therefore stays queryable for its retention window (a2) by exactly the
callers who could have queried the service.
job-status and job-stop resolve the job first too, and for them
that is the whole story: UNKNOWN_JOB for an identifier that names
nothing, ACCESS_DENIED for one that names a job the caller may not
touch. A job identifier is unguessable, so a caller that names one was
told it by something that had it, and there is no existence to hide.
job-stop creates no operation; it stops the job directly (§8.5) and,
with wait, registers a job wait on the connection answered by the
terminal view.
10.2.2 What the caller may do #
A status response carries granted, the service rights the caller
holds on the service, so that a client can offer only the commands that
will be allowed rather than learn the rest from ACCESS_DENIED (PSPU
§4.14). peinit finds them with step 3's AccessCheck — the caller's
token, the service's ServiceSecurity, the service mapping — asking
for MAXIMUM_ALLOWED instead of one right, once the query itself has
been allowed, and reports the service rights among what it grants.
A lifecycle command that
has nothing to do answers with the status (§10.3), and that carries
granted for its caller too.
Each job view on the control socket carries granted the same way:
the job rights the caller holds, from the AccessCheck against the job's
own descriptor with the job mapping (§8.5). job-stop with wait
finds them when it is taken and keeps them on the connection's wait,
because the terminal view is written by the run loop with no caller's
token to hand; the descriptor is fixed at submission, so they are the
same as at the answer. The jobs socket's view has no granted.
The MAXIMUM_ALLOWED check is a question, not a command. A right it
does not grant is left out of granted; nothing is refused, and peinit
answers the command as it would have without it.
10.2.3 Filtering #
list checks every service and partitions the result. Services the
caller can query are returned; services it cannot are omitted, and the
denials are left to each service's SACL rather than shown to the caller. A
caller with no query rights anywhere gets an empty list and a successful
response, not a denial . The
filtering keeps a service's state from whoever may not query it; it does
not hide that the service exists, which step 2 above already answers
for any command that names one (§4.6).
Definition-removed services are listed, and the list entry does not say
so. A status query on one does.
job-list is filtered the same way. The four request filters —
submitter, identity, logon_session, state — narrow the
candidates first; each survivor is then checked for JOB_QUERY against
its own descriptor, each denial is the job's SACL's to record, and the
response does not say whether a filter or a right removed an entry.
Terminal jobs still within their retention are listed; a caller that
wants only live ones filters by state.