# Peios Generic System Standards

> The cross-platform standards a system must implement to be Peios — contracts between two roles, either of which a third party may replace.

---

# 1.1 Scope

_Peios / Advanced Peios / PGSS / Introduction_

> What PGSS is — the cross-platform standards a system must implement to be Peios — and the test that separates a standard from a protocol.

This document defines the **Peios Generic System Standards (PGSS)**: the
cross-platform protocols and standards a system MUST implement in order
to be Peios.

A standard belongs in this document when three things are true of it.

**It is a conformance requirement.** A system that does not offer the
protocol, at the specified path, with the specified semantics, is not
Peios. Each standard here is a bar to clear, not a recommendation to
weigh.

**It belongs to no implementation.** A PGSS standard describes a contract
between two roles, not the behaviour of a particular program. Mainline
ships an implementation of each role; nothing in this document depends on
that implementation, or describes it.

**Either role may be replaced.** A third party MAY ship its own
implementation of one role — or of both — and interoperate with the other
unchanged. A standard that cannot survive that substitution is a
description of one system rather than a contract between two, and does
not belong here.

For each standard, this document covers:

- the channel it is offered on, and the access control governing it
- message framing, encoding, and the rules under which the format may be
  extended
- the messages exchanged, their fields, and the order in which they are
  exchanged
- the obligations binding on each role, including what a role MUST
  establish for itself rather than believe from a message
- the conformance requirements for each role

This document does not cover:

- How an implementation reaches the answers it gives — that is precisely
  what different systems exist to do differently
- The binary structures these protocols carry — defined in PCDS
- Protocols spoken across the kernel boundary — defined in PSPK
- Protocols between foundational userspace components — defined in PSPU
- Interfaces particular to one Mainline component — defined in the
  specification of the component that offers them

## 1.1.1 Distinguishing a standard from a protocol

The anthology's three protocol documents are told apart by what happens
when you disagree with one.

Disagreeing with a standard in this document means shipping something
that is not Peios.

Disagreeing with PSPK or PSPU means shipping a system built from
different parts — a different kernel subsystem, or a different set of
userspace components. That is a design choice, not a conformance
failure.

> [!NOTE]
> The consequence of the second and third properties together is that
> conformance is checked against the protocol, never against Mainline. A
> system that speaks a standard correctly conforms to it, whatever it
> runs behind the channel and however it reaches its answers.

---

# 1.2 Conventions

_Peios / Advanced Peios / PGSS / Introduction_

> The normative keywords PGSS uses, and the shared conventions it inherits from the PCSA conventions book.

The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY in this
document are to be interpreted as described in RFC 2119. Text set off as
a note is informative, not normative.

Everything else — roles, byte order, sizes, layout tables, notation,
strings, timestamps, citation, and the external standards this anthology
depends on — is defined in the Conventions book and is not restated
here. PGSS departs from none of it.

Where a chapter needs a convention of its own, that chapter states it.

---

# 2.1 Scope and Roles

_Peios / Advanced Peios / PGSS / Logon_

> What PGSS Logon specifies and who its parties are — obtaining a token from an authority, resolving identity, and the boundaries the authority does not cross.

This chapter specifies **PGSS Logon**: the protocol by which a caller
obtains a KACS token and a logon session from an authentication
authority, and the protocol by which any program on the system resolves
an identity it already holds into a name, a SID, or the attributes a
POSIX program expects of one.

Two roles participate.

The **authority** is the process that listens on the logon socket. It
decides whether a logon succeeds, mints the resulting token, creates the
logon session, and answers identity lookups. There MUST be at most one
authority on a running system.

The **client** connects and speaks on a principal's behalf. It proposes
what kind of logon it wants, renders prompts and returns answers without
interpreting them, and receives the token and installs it itself. A
client is not trusted: everything it sends is a claim (§2.4).

The **principal** is the identity a logon is *for* — the person or
service being authenticated. The principal is not a party to the
conversation.

Both roles are publicly implementable. A third party MAY ship a
different authority, a different logon originator, or both, and
interoperate with the other half unchanged.

This chapter covers:

- the two channels an authority offers, and the access control
  governing each (§2.5, §2.14)
- message framing, header layout, and the rules under which the format
  may be extended (§2.6)
- the messages of the logon conversation, their fields, and their
  encodings (§2.7 to §2.10)
- the shape of a conversation: who speaks, in what order, and how it
  terminates (§2.3)
- the transfer of the minted token to the caller, and the
  session-profile values a logon originator needs in order to start a
  session (§2.9)
- credential handling obligations binding on both roles (§2.11, §2.12)
- what an authority MUST establish for itself rather than believe from
  a message (§2.4)
- resolving a principal to a name, a SID, a POSIX identifier, or the
  attributes a POSIX program expects of one (§2.13 to §2.18)
- how a bare name is resolved when more than one source could answer it
  (§2.15)
- the obligations binding on each role (§2.19)

This chapter does not cover:

- Tokens, SIDs, privileges, integrity levels, logon sessions, logon
  types, and access checks — described in the Peios Kernel TRM. SIDs
  and security descriptors are specified in PCDS.
- How an authority decides *whether* a credential is valid, where it
  keeps identity, which principals exist, or what they are called.
  Those belong to the authority's own design. Mainline's authority
  federates them over PSI, the Principal Source Interface, specified in
  PSPU §2.
- Password change, credential enrolment, and account administration,
  which are not specified.
- Service startup and ordering, described in the peinit TRM.

The distinction in the second point is the load-bearing one. PGSS Logon
specifies how a caller *asks* and how an authority *answers*. It says
nothing about how the authority reaches its answer, because that is
exactly what different systems will do differently, and constraining it
would make the standard a description of one implementation rather than
a contract between two.

> [!NOTE]
> Mainline's authority is `authd`, which federates authentication to
> separate principal-source processes over PSI (PSPU §2). That
> architecture is
> *one* way to satisfy this chapter. An authority that consults a single
> built-in database, or a remote directory, or a hardware token reader,
> conforms equally well provided it speaks this protocol correctly.

## 2.1.1 What the authority does not do

An authority MUST NOT be a process factory. It does not fork the
principal's shell, does not learn about controlling terminals,
environments, or session leadership, and does not decide what the caller
does with the token it receives.

The caller installs the token and proceeds. This keeps the most
privileged process on the system out of the business of launching
arbitrary programs, and it means a client can obtain a token for a
purpose the authority need never have anticipated.

> [!NOTE]
> This is why `AccessGranted` carries a descriptor and a session
> identifier and nothing else. There is no field describing what should
> happen next, because nothing about what happens next is the
> authority's concern.

## 2.1.2 Authentication and derivation

Two acts, deliberately separated.

**Authentication** establishes that the principal is who they claim. Its
output is an identity and nothing more.

**Derivation** constructs the token: which SIDs it carries, which
privileges, at what integrity level, with what projected identifiers.
Its inputs are the authenticated identity *and local policy*.

The separation matters because the second is where a machine's own rules
apply. An identity established elsewhere — by a directory, by a remote
authority — does not carry entitlements onto this machine with it. What
that identity *means* here is decided here, every time, by the authority
applying local policy.

An authority MUST perform derivation itself. It MUST NOT accept a token,
a privilege set, or an integrity level supplied by any other party,
whatever its trust level.

## 2.1.3 The token this chapter does not describe

This chapter governs the conversation and the delivery of its result. It
does not govern the *contents* of the token that results. What SIDs,
privileges, and integrity level a principal receives is derivation, and
derivation is the authority's judgement applied to local policy.

A caller that receives a token from this protocol has been told who it
may act as. It has not been told, and MUST NOT infer, anything further
about how that conclusion was reached.

---

# 2.2 Terminology

_Peios / Advanced Peios / PGSS / Logon_

> Terms this chapter borrows unchanged from the Peios Kernel TRM and PCDS, and the one it has to qualify — logon type.

Terms defined in the Peios Kernel TRM (token, logon session, privilege,
integrity level, logon type) and in PCDS (SID, security descriptor,
DACL) are used here with the same meaning and are not redefined.

**Authority.** The process that listens on the logon socket, decides
whether a logon succeeds, mints the resulting token, creates the logon
session, and answers identity lookups. There is at most one authority on
a running system.

**Client.** A process that connects to the authority. Also called the
*originator* when the emphasis is on whose identity the authority
verifies. There are two client roles — originating a logon and looking
an identity up — and they are independent (§2.19).

**Principal.** The identity a logon is *for* — the person or service
being authenticated. The principal is not a party to the conversation;
the client speaks on their behalf.

**Conversation.** One logon connection's exchange, from the client's
opening message to a terminal message from the authority. One connection
carries exactly one conversation.

**Round.** One `CredentialRequest` from the authority and the
`CredentialResponse` that answers it. A conversation MAY take several
rounds.

**Terminal message.** `AccessGranted` or `AccessDenied`. Exactly one is
sent, and nothing follows it.

**Credential material.** Any byte sequence a principal supplies as proof
of identity — a password, a one-time code, a response from a token.
Distinguished throughout from a **verifier**, which is what an authority
stores and which MUST NOT be usable as credential material.

**Prompt.** A request from the authority for one piece of credential
material, carrying enough description for a client to render it without
understanding what it is for.

**Derivation.** The authority's construction of a token's contents — its
SIDs, privileges and integrity level — from the authenticated identity
and local policy. Distinct from *authentication*, which establishes only
that the principal is who they claim.

**Source.** A party an authority consults in order to answer a question
about identity. Whether an authority has sources at all, and what they
are, is its own design; the term appears here only where the protocol's
behaviour depends on there being more than one possible answerer
(§2.15, §2.18).

## 2.2.1 A note on "logon type"

`LogonType` (§2.7) describes the *kind* of session being established —
interactive, network, batch, service. It is a property of the situation,
not of the principal, and the same principal may hold several sessions
of different types at once. Its values and meanings are defined by KACS
and described in the Peios Kernel TRM; this chapter carries it but does
not define it. The values are listed for reference in §2.B.

---

# 2.3 The Conversation

_Peios / Advanced Peios / PGSS / Logon_

> A logon is a conversation rather than a call — who may speak when, how it is bounded, and how it ends.

A logon is a conversation, not a call. The client opens it, the
authority asks for whatever the principal's policy requires, and the
authority ends it.

```
client                                   authority
  |                                          |
  |------------- LogonStart ---------------->|
  |                                          |
  |<--------- CredentialRequest -------------|   round 1
  |---------- CredentialResponse ----------->|
  |                                          |
  |<--------- CredentialRequest -------------|   round 2 (if required)
  |---------- CredentialResponse ----------->|
  |                                          |
  |<-- AccessGranted (+ token fd) -----------|   terminal
  |         or AccessDenied                  |
```

## 2.3.1 Why a conversation

The value of this shape is that **the client stays generic**. It does
not know what a password is. It renders the prompts it is given,
collects the answers, and returns them.

That is what makes multi-factor authentication,
password-expiry-forces-change, smartcards, or a policy that asks for a
second factor only from an unfamiliar host, changes to *authorities*
rather than to every client on the system. A protocol that named its
credential kinds would have to be revised, and every client rebuilt, for
each one.

Most logons are one round: the authority asks for everything the
principal's policy requires, in one array, and decides. The
conversational shape exists for what one round cannot express — choosing
between authentication paths, or a shared account where one credential
unlocks a requirement for another.

## 2.3.2 Sequence rules

A conversation MUST proceed as follows.

1. The client sends exactly one `LogonStart`. It MUST be the first
   message. An authority MUST reject any conversation that opens with
   something else.
2. The authority sends zero or more `CredentialRequest` messages. Each
   MUST be answered by exactly one `CredentialResponse` before the
   authority sends anything further.
3. The authority sends exactly one terminal message, `AccessGranted` or
   `AccessDenied`.
4. Both parties close the connection.

The authority MAY send a terminal message at any point after
`LogonStart`, including before any `CredentialRequest`. Zero rounds is a
conforming conversation: an authority that can decide from `LogonStart`
alone — a pre-authenticated caller, or a refusal on logon type — is not
required to ask for anything.

A client MUST NOT send a `CredentialResponse` that was not solicited by
a `CredentialRequest`. An authority MUST reject one that was not.

## 2.3.3 Bounding the conversation

An authority MUST bound the number of rounds it will conduct and MUST
bound the time it will wait for a `CredentialResponse`. Neither bound is
fixed by this chapter, since both are policy. An authority that exhausts
either MUST terminate with `AccessDenied` carrying `ConversationLimit`
rather than closing silently, so that the client can distinguish a
policy limit from a crash.

## 2.3.4 Termination

Exactly one terminal message is sent. After it, the authority MUST NOT
send anything further on that connection, and MUST close it.

A connection that closes without a terminal message is an *abnormal*
termination. A client MUST treat it as a failed logon and MUST NOT retry
automatically, since the reason is unknown and may be a policy refusal
the authority could not express.

---

# 2.4 What an Authority Must Not Trust

_Peios / Advanced Peios / PGSS / Logon_

> Nothing a client sends is trusted. Peer identity comes from the kernel, and every asserted field is a proposal to be constrained.

Nothing a client sends is trusted. This section states the rule once,
because every field in §2.7 is subject to it.

## 2.4.1 Peer identity

An authority MUST establish the connected peer's identity from the
**connected socket**, by reading the peer's token from the kernel. It
MUST NOT take the peer's identity from any message body, because there
is no field in which a client could put it that the client could not
also lie in.

An authority MUST NOT use `SO_PEERCRED` for this purpose. It answers a
similar-looking question and is the wrong answer: it returns the
*projected* UID, which cannot distinguish an authenticated principal
from an unauthenticated process running under the same projection, and
carries none of the token's SIDs, groups, integrity level or privileges.

> [!NOTE]
> On Peios many principals project to the same identifier, so a
> projected UID frequently identifies nobody in particular. Two
> different principals can share one. The peer token is the only thing
> that answers "who is this?" exactly.

## 2.4.2 Logon type

`LogonStart.logon_type` is a **proposal**, not an instruction.

Only the caller knows whether an inbound connection is an interactive
shell or a batch command, so the caller has to be the one to say. But an
authority MUST constrain the proposal against what that verified peer is
permitted to request. Otherwise anything that can reach the socket can
mint itself an interactive session, and the logon type — which access
control decisions depend on — becomes a value chosen by the least
trusted party in the exchange.

## 2.4.3 Identifier

The identifier names the principal a logon is *for*. It is a claim about
who is being authenticated, and it is what the subsequent credential
exchange exists to test. An authority MUST NOT treat an identifier as
established until authentication has succeeded.

## 2.4.4 Everything else

`tty` and `remote_host` are unverified context. An authority MAY record
them, MAY use them in policy, and MUST NOT treat them as established
facts. A client that lies about its remote host is not prevented from
doing so by this protocol.

## 2.4.5 Access to the socket

Access to the logon socket MUST be controlled by a security descriptor.

It MUST NOT be controlled by process integrity level. PIP *can* gate a
socket, but using it here would require every future caller — a
graphical greeter, a web console, a remote access daemon — to be signed
at high trust merely to *collect a password*. Collecting a credential is
not a privileged act; deciding whether it is correct is, and that
decision happens on the other side of the socket.

Rate limiting is defence in depth. It is not the access control, and an
authority MUST NOT rely on it as such.

---

# 2.5 The Logon Channel

_Peios / Advanced Peios / PGSS / Logon_

> /run/logon.sock — the normative path, its access control, one conversation per connection, and why the channel does not multiplex.

## 2.5.1 Socket

An authority MUST listen on a `SOCK_STREAM` Unix domain socket at:

```
/run/logon.sock
```

The path is normative. It is the standard's path, not an
implementation's, and a client MUST NOT require configuration to find
it.

## 2.5.2 Access control

The socket MUST carry a security descriptor granting connect access to
the principals permitted to originate logons. See §2.4 for why this, and
not process integrity, is the control.

## 2.5.3 One conversation per connection

A connection carries exactly one conversation. The connection **is** the
conversation's identity.

There is therefore no correlation identifier in the header, and none is
needed: a message belongs to the conversation it arrived on. This
removes a class of error and attack — a forged or confused identifier
cannot attach a credential response to somebody else's logon, because
there is no identifier to forge.

It also bounds the credential's lifetime by the connection's, which
makes that the kernel's job to enforce rather than the authority's to
remember.

An authority MUST close the connection after sending its terminal
message. A client MUST close after receiving one.

> [!NOTE]
> A protocol whose connections are long-lived and shared reaches the
> opposite conclusion, and multiplexes. PSI does (PSPU §2.7), and so
> does this chapter's own identity channel (§2.14). The difference is not
> inconsistency: a logon client's connection exists for one logon and
> is discarded, so paying for multiplexing here would buy nothing and a
> correlation identifier would only add something to forge.

## 2.5.4 Concurrency

An authority MUST serve conversations concurrently. A logon that stalls
— a principal who walks away mid-prompt — MUST NOT prevent other logons
from proceeding.

An authority MUST bound the number of conversations it will serve at
once, and MUST bound the time a conversation may remain open. Both are
policy; neither is fixed here.

## 2.5.5 Descriptor passing

The channel MUST support ancillary data (`SCM_RIGHTS`). The token is
transferred as a file descriptor alongside `AccessGranted` — see §2.9.

---

# 2.6 Message Framing

_Peios / Advanced Peios / PGSS / Logon_

> The header both channels share — magic, version, message type and size limit — and how a message body may be extended.

Both of an authority's channels use the framing specified here without
modification. The identity socket's departures are stated in §2.14; they
concern which message types are served where, not the encoding.

## 2.6.1 Header

Every message begins with a 12-byte header:

| Offset | Size | Field | Value |
|---|---|---|---|
| 0 | 4 | `magic` | `PGSL` (`50 47 53 4c`) |
| 4 | 2 | `version` | `1` |
| 6 | 2 | `msg_type` | See §2.A |
| 8 | 4 | `total_len` | Header plus body, in bytes |

`total_len` counts the header. A message is therefore self-delimiting
from its first 12 bytes, and a reader can size its buffer before reading
the body.

## 2.6.2 Magic

The magic MUST be checked on every message and MUST cause an immediate,
whole-connection failure when wrong.

This is not decoration. Peios protocols share this codec and this header
layout, and some of them share message bodies. A socket plugged into the
wrong daemon would otherwise *partially* work, which is far worse than
failing outright — a subtle misbehaviour several fields in, rather than
a hard error on byte zero. A protocol reusing this codec MUST take a
distinct magic.

## 2.6.3 Version

A peer receiving a `version` it does not implement MUST refuse the
message. An authority MUST refuse with `AccessDenied` carrying
`UnsupportedVersion` where it can still encode one.

## 2.6.4 Size limit

A message MUST NOT exceed **65536 bytes** in total. A decoder MUST
reject a header declaring more, without reading the body, and MUST
reject one declaring fewer than the header's own length.

## 2.6.5 Message type

The high bit of `msg_type` marks a message sent by the **authority**.
Client messages have it clear. This is a readability property rather
than a security one — a peer MUST validate the message type it received
against what it expected, not merely against the direction bit.

## 2.6.6 Encoding

All multi-byte integers are little-endian.

**Strings** are UTF-8 and are **not NUL-terminated**. A string is
encoded as a `u32` byte count followed by exactly that many bytes. A
decoder MUST reject a string whose bytes are not valid UTF-8.

An empty string and an absent optional string are encoded identically,
as a zero byte count. A field documented as optional is therefore absent
when empty, and an implementation MUST NOT distinguish the two.

**Byte strings** are encoded the same way and carry no encoding
requirement.

**Arrays** are encoded as a `u32` element count followed by that many
length-framed structures. Every array in this chapter has a stated
maximum; an encoder MUST refuse to produce a longer one and a decoder
MUST reject one it receives. The same holds for every stated byte limit.

Two distinct length mechanisms therefore appear, and confusing them is
the most likely implementation error:

- **Byte strings and strings** are prefixed with a `u32` byte count, and
  the bytes follow immediately.
- **Structures and array elements** are prefixed with a `u32` byte count
  of the *whole structure*, and a decoder MUST skip to the structure's
  declared end after reading the fields it knows.

The second is what makes the format extensible.

## 2.6.7 Body extensibility

The body is a single length-framed structure, and **every structure and
every array element within it is length-framed too**.

A decoder reads the fields it knows and then skips to the structure's
declared end. A field appended by a newer peer is therefore stepped over
rather than mistaken for the next field — which is what makes the format
extensible *inside arrays*, where ignoring trailing bytes at the message
level would not help.

A structure that ends before a field an older encoder never wrote is not
an error. A decoder that reaches the end of a structure's body where an
optional trailing field would have been MUST substitute that field's
documented default (§2.7, §2.9) rather than failing.

The rules this keeps working under are binding on anyone revising this
chapter:

- Fields are **appended only**. Never reordered, never removed, never
  changed in meaning.
- A new field MUST be **optional with a safe default**, because older
  peers will not send it and will not read it.
- Adding a value to an enumeration is a **breaking change** and requires
  a version bump, because every peer is required to understand every
  value it is sent.

The last rule is the one that surprises people. An unknown enumeration
value cannot be safely ignored: a client that skipped an unrecognised
credential type would silently fail to collect something the authority
required, and an authority that skipped an unrecognised logon type would
grant the wrong kind of session.

There are exactly two exceptions, and both are stated where they apply:
`LogonStart.supported_credential_types`, which is a statement of
capability rather than an instruction (§2.7), and the field mask of
`Lookup`, whose reply says which bits it answered (§2.16).

---

# 2.7 LogonStart

_Peios / Advanced Peios / PGSS / Logon_

> The client's opening message — logon type, identifier, tty and remote host, and the credential types it can collect.

`msg_type` = `0x0001`. Client to authority. Opens the conversation; MUST
be the first message.

## 2.7.1 Layout

| Field | Encoding | Limit |
|---|---|---|
| `logon_type` | `u8` | §2.B |
| `identifier_type` | `u8` | §2.B |
| `identifier` | length-framed bytes | 1024 |
| `tty` | string | 128 |
| `remote_host` | string | 256 |
| `supported_credential_types` | length-framed bytes, one `u8` per type | 32 |

Everything here is *asserted by the client*, and §2.4 governs all of it.

## 2.7.2 logon_type

The kind of session the client is asking for. **A proposal**, which the
authority MUST constrain against the verified peer — see §2.4.

Values are defined by KACS and listed for reference in §2.B.

## 2.7.3 identifier_type and identifier

Together these name the principal. `identifier_type` says how to read
`identifier`; `identifier` is **opaque bytes**, not a string.

Bytes rather than a string because an identifier is not always text. A
certificate thumbprint, a smartcard serial, or a binary principal name
are all reasonable identifiers, and a protocol that insisted on UTF-8
would exclude them. An authority that expects text MUST validate the
encoding itself.

An identifier MAY be empty. An empty identifier means *the principal is
not named here* — the authority is expected to determine it from the
credential, as with a smartcard that carries its own identity.

`identifier_type` is distinct from `credential_type` (§2.8): this is the
claim of identity, that is the proof. A passkey names a principal and
proves them in one artefact; a username names them and proves nothing.

## 2.7.4 tty and remote_host

Unverified context, both optional, both empty when absent.

`tty` names the terminal the logon is happening on, where there is one.
`remote_host` names where a network logon came from.

An authority MAY use either in policy and MAY record either in its audit
trail. It MUST NOT treat either as established. A client that lies about
them is not prevented from doing so.

## 2.7.5 supported_credential_types

Every credential type this client can render, one byte each.

This is the client declaring its capabilities, and it is **binding on
the authority**: an authority MUST NOT send a prompt for a credential
type absent from this list (§2.8).

A client that supports nothing sends an empty list. That is meaningful
rather than degenerate — it says "I can complete a logon that requires
no interaction, and nothing else" — and an authority MUST either
complete the logon without prompting or deny it.

### 2.7.5.1 The two exceptions this field carries

`supported_credential_types` is an optional trailing field, and it is
the only enumeration in this chapter whose unrecognised values are
dropped rather than refused. Both departures from §2.6 are deliberate,
and both are what make adding a credential type workable at all.

**An absent field means `Password`, not an empty list.** A decoder that
reaches the end of the body where this field would have been MUST read
it as a client supporting exactly the credential types that existed
before the field did — which is `Password`, and only `Password`. A
client predating the field could render nothing else. An absent field
and an explicitly empty one are therefore *different statements*, and an
implementation MUST NOT collapse them: the empty list is how a client
asks for a logon that requires no interaction, and reading it as
`Password` would turn that request into a prompt.

This is the one place where an empty length-framed field is not
equivalent to an absent one (§2.6).

**An unrecognised value is dropped, not refused.** A decoder MUST
discard a credential type it does not recognise and proceed with the
rest, leaving the intersection of what the client claims and what the
decoder understands. This is safe here and nowhere else, because the
field is a statement of capability rather than an instruction: an
authority that ignores a type it has never heard of merely declines to
use it, which is the correct outcome.

Without this, a client that learned a new credential type could not
speak to an older authority at all — the authority would be obliged by
§2.6 to refuse the whole message. The capability list exists precisely
so that an authority using a new type cannot reach an older client; it
would be self-defeating if a *client* using a new type could not reach
an older authority either.

---

# 2.8 Credential Exchange

_Peios / Advanced Peios / PGSS / Logon_

> CredentialRequest and CredentialResponse — messages, prompts, the client capability rule, and what an empty request means.

## 2.8.1 CredentialRequest

`msg_type` = `0x8001`. Authority to client.

| Field | Encoding | Limit |
|---|---|---|
| `messages` | array of *Message* | 8 |
| `prompts` | array of *Prompt* | 16 |

**Message:**

| Field | Encoding | Limit |
|---|---|---|
| `severity` | `u8` | §2.B |
| `text` | string | 512 |

**Prompt:**

| Field | Encoding | Limit |
|---|---|---|
| `credential_ref` | `u32` | — |
| `credential_type` | `u8` | §2.B |
| `credential_name` | string | 128 |

Both arrays MAY be empty.

### 2.8.1.1 Messages

Text for the principal to read, before any prompt is presented. "Your
password expires in three days"; "Authenticating against the local
source".

A client MUST display messages it receives, in order, before the prompts
in the same request. It MUST NOT interpret them, and MUST NOT vary its
behaviour on their content.

### 2.8.1.2 Prompts

Each prompt asks for one piece of credential material.

`credential_ref` identifies the prompt within the conversation. The
client echoes it back unchanged in its answer. It MUST be unique among
the prompts of a single request. An authority MAY reuse a value in a
later round.

`credential_name` is what to show the principal — "Password",
"Verification code". It is a display string. A client MUST NOT branch on
it; `credential_type` is what says how to collect the answer.

### 2.8.1.3 The client capability rule

An authority **MUST NOT** send a prompt whose `credential_type` is
absent from the client's `supported_credential_types` (§2.7).

This is a hard requirement rather than a courtesy. A client that
receives a prompt it cannot render has no good option: failing the logon
punishes the principal for a mismatch neither party chose, and *guessing*
— collecting a line of text for a credential type that is not a password
— may echo a secret to the screen. An authority that cannot proceed
within the client's declared capabilities MUST deny the logon instead.

A client that nevertheless receives an unrenderable prompt MUST fail the
conversation rather than guess.

### 2.8.1.4 Empty requests

A `CredentialRequest` with no prompts is valid. It carries messages
only, and the client MUST answer it with a `CredentialResponse` carrying
no answers. This is how an authority conveys information mid-conversation
without asking for anything.

## 2.8.2 CredentialResponse

`msg_type` = `0x0002`. Client to authority.

| Field | Encoding | Limit |
|---|---|---|
| `answers` | array of *Answer* | 16 |

**Answer:**

| Field | Encoding | Limit |
|---|---|---|
| `credential_ref` | `u32` | — |
| `data` | length-framed bytes | 32768 |

`credential_ref` MUST match a prompt from the request being answered.
`data` is the material the principal supplied — opaque bytes, no
encoding implied.

A client SHOULD answer every prompt it was given. An authority MUST NOT
assume it has: a missing answer is a failed logon, not a protocol
violation, and MUST be treated as such rather than as grounds to tear
down the connection.

An authority MUST match answers to prompts by `credential_ref` and MUST
NOT rely on ordering.

Both parties encode and decode this message under the obligations of
§2.12. The encoded message holds the credential just as much as the
`data` field inside it does.

> [!NOTE]
> A client that cannot collect an answer — the principal pressed escape
> — MAY send a response omitting it rather than closing the connection.
> That gives the authority the opportunity to deny cleanly with a reason
> the principal can read.

---

# 2.9 AccessGranted

_Peios / Advanced Peios / PGSS / Logon_

> The terminal success message — the token passed as a file descriptor and never named, the session id, and the profile that comes with it.

`msg_type` = `0x8002`. Authority to client. One of the two terminal
messages; nothing follows it.

| Field | Encoding |
|---|---|
| `session_id` | `u64` |
| `profile` | length-framed structure, optional |

**Ancillary data: the token, as one file descriptor via `SCM_RIGHTS`.**

## 2.9.1 The descriptor

The token MUST be transferred as a file descriptor attached to this
message. It MUST NOT be named, and there MUST be no path, handle, or
identifier by which another process could reach it.

Possession of the conversation is what confers the token. There is no
window in which a minted token exists under a name something else could
open, and no lookup that could be raced or guessed.

A client MUST read the descriptor from the ancillary data of this
message. An `AccessGranted` arriving without one is a protocol violation
and MUST be treated as a failed logon — a client MUST NOT proceed as
though a logon succeeded when it holds no token.

## 2.9.2 session_id

The logon session the token belongs to. The client MAY record it, MAY
report it, and needs it to relate this logon to the kernel's session
records.

It is informational to the protocol. The token is the thing that confers
authority; the session identifier merely names the session the token
already belongs to.

## 2.9.3 profile

Where the session starts, and what to call the principal. A
length-framed structure, so it can grow without displacing anything
appended to `AccessGranted` after it.

| Field | Encoding | Limit |
|---|---|---|
| `home` | string | 4096 |
| `shell` | string | 4096 |
| `display_name` | string | 256 |

`profile` is an optional trailing field. **Every field may be empty**,
and an empty field means *the authority did not say*. An authority that
knows nothing about home directories is conforming, and so is one that
omits the whole structure — a client MUST treat an absent `profile`
exactly as it treats one whose fields are all empty.

A client MUST have a fallback for each field and MUST NOT treat an empty
value as an error.

`home` and `shell`, when non-empty, MUST be absolute paths. A client
MUST NOT execute a relative `shell` or resolve a relative `home` against
its own working directory. An authority with no value for a field MUST
leave it empty rather than invent one.

### 2.9.3.1 Why this is on the terminal message

Nothing here is an access-control input. No ACL names a home directory,
and the token carries none of these fields — so this is not identity,
and an authority that got it wrong would produce an inconvenient session
rather than an unsafe one.

It is here because the authority has just read these values in order to
decide the logon, and a caller that needs them in order to *start a
session* would otherwise have to ask a second time. A logon originator
cannot `chdir` or `exec` without them.

### 2.9.3.2 What this is not

It is not a directory lookup, and it MUST NOT be treated as one. It
answers for exactly one principal — the one who just authenticated — at
exactly one moment. It cannot answer for anybody else, it cannot answer
for a principal who never logged on, and nothing in this protocol says
what it means an instant later.

Resolving arbitrary principals to home directories or shells is §2.16's,
and a client that caches these values as though they had come from there
has misread them.

### 2.9.3.3 display_name

A human's name for a human to read.

It is deliberately **not** a GECOS field. GECOS is a comma-separated
`/etc/passwd` artefact carrying a name, an office, and two telephone
numbers in one string; a protocol that carried one would oblige every
client to parse it apart, and would tie this chapter to a file format it
has nothing to do with.

An implementation that must produce a GECOS field renders it *from*
this, not the other way round.

> [!NOTE]
> A client is not obliged to *use* any of this. Falling back to a fixed
> shell, or ignoring `display_name` entirely, is conforming. The fields
> exist so that a client which wants them does not need a second
> protocol.

## 2.9.4 What the client does next

Installs the token, if that is what it wanted. The authority is not
involved and MUST NOT be told (§2.1).

A client that decides not to install the token MUST close the
descriptor. A logon session whose token is never installed still exists
as far as the kernel is concerned.

A client SHOULD NOT fail a logon because a `home` it was given does not
exist. The principal has authenticated and holds a token; refusing to
start their session over a missing directory turns a cosmetic problem
into being locked out. Starting elsewhere and saying so is the better
failure.

---

# 2.10 AccessDenied

_Peios / Advanced Peios / PGSS / Logon_

> The terminal failure message — a deliberately small denial vocabulary, and what a denial must never reveal.

`msg_type` = `0x8003`. Authority to client. The other terminal message;
nothing follows it.

| Field | Encoding | Limit |
|---|---|---|
| `denial` | `u32` | §2.B |
| `reason` | string | 512 |

## 2.10.1 denial

A code from a deliberately small vocabulary (§2.B), for a client that
needs to *act* differently — retry, offer a different account, report a
system fault.

`PermissionDenied` and `LogonTypeNotPermitted` are distinct on purpose:
the first says the peer may not use this socket for this at all, the
second that it may originate logons but not of this kind. A client can
act differently on each.

## 2.10.2 reason

Text for the principal to read. A client SHOULD display it and MUST NOT
interpret it.

The division is the same one `CredentialRequest` makes between
`credential_type` and `credential_name`: the machine-readable field is
small and stable, the human-readable one is free.

`reason` MUST NOT narrow an `AuthenticationFailed` into a specific
cause.

## 2.10.3 What a denial must not reveal

The denial vocabulary deliberately does **not** distinguish "no such
principal" from "wrong credential". Both are `AuthenticationFailed`.

A protocol that distinguished them would make account enumeration a
supported feature: anyone able to reach the socket could test names and
learn which exist. An authority MUST NOT provide that distinction
through the denial code, through `reason`, or through observable timing.

The timing obligation is the one most often missed, and it binds even
though this chapter cannot check it. An authority whose
unknown-principal path returns faster than its wrong-credential path has
published the distinction it just declined to state — see §2.12.

> [!NOTE]
> An authority MAY record the distinction in an audit trail an
> administrator can read. What it must not do is return it to the
> caller.

---

# 2.11 Credential Types

_Peios / Advanced Peios / PGSS / Logon_

> What a credential type tells a client to collect, why the registry is kept short, and why credentials cross the socket in the clear.

A credential type tells a client **how to collect** an answer. It does
not tell it what the answer means.

| Value | Name | Collection |
|---|---|---|
| 1 | Password | A line of text, not echoed |

The registry is deliberately short. A type is added when a client would
need to collect something differently, not when an authority acquires a
new way of checking something.

> [!NOTE]
> A one-time code is collected exactly as a password is — a line of
> text, not echoed — so it needs no new type. An authority asks for it
> with `credential_type = Password` and a `credential_name` of
> "Verification code". A hardware token that requires a challenge to be
> relayed to a reader *would* need a new type, because no existing
> collection method produces the answer.
>
> This is the test to apply: does an existing client, understanding only
> the types it already has, collect the right thing? If yes, no new type
> is needed.

## 2.11.1 Adding a type

Adding a value to this enumeration is a **breaking change** and requires
a version bump (§2.6).

An authority MUST NOT send a prompt for a type absent from the client's
`supported_credential_types`, so an authority using a new type simply
cannot reach an older client — which is the correct outcome, and is why
the capability list exists. The converse — a newer *client* reaching an
older authority — is what the dropping rule in §2.7 exists for.

## 2.11.2 Why credentials cross in the clear

Credential material travels this socket as **plaintext**. That is
deliberate, and the alternative is worse.

Challenge-response requires the verifier to store something it can
recompute the response from: either the plaintext, or a value that is
*password-equivalent*. NTLM works exactly this way — the stored hash is
as good as the password, forever, which is why pass-the-hash has been
the most valuable credential on a Windows network for twenty years.

A modern verifier — argon2id and its relatives — is deliberately **not**
password-equivalent and cannot answer a challenge. That is the property
that makes stealing the store meaningfully weaker than knowing the
passwords.

So challenge-response would trade a permanent weakness at rest for
protection of a channel that is not the weak point. Anyone able to read
this socket can already `ptrace` the process holding the password.

### 2.11.2.1 What would change the answer

A PAKE — OPAQUE, SRP — gives both properties at once: nothing
password-equivalent at rest, and no plaintext on the wire. It is worth
revisiting if an authority ever authenticates **across a network without
TLS**.

It is not worth its complexity for a local, kernel-mediated socket,
where the threat it defends against is already able to do worse.

### 2.11.2.2 What follows

Because plaintext crosses the wire, bounding how long it survives
becomes an obligation of both roles rather than a nicety. See §2.12.

---

# 2.12 Credential Handling

_Peios / Advanced Peios / PGSS / Logon_

> The obligations that make plaintext on the wire defensible — bounded lifetime, zeroing, and what must never reach a log.

These bind both roles. They are stated normatively because plaintext on
the wire (§2.11) is only defensible if its lifetime is short.

## 2.12.1 Bounding lifetime

An implementation MUST hold credential material in memory that is
**erased before it is released**, and the erasure MUST NOT be removable
by an optimising compiler.

The obligation extends to **the encoded message**, not only to the
credential field. A `CredentialResponse` holds the password just as much
as the answer inside it does, and a buffer wiped at one level while
another copy is dropped unerased achieves nothing.

An implementation MUST erase:

- the buffer credential material was read into;
- any buffer it was copied into during encoding or decoding, including
  an intermediate allocation abandoned by a buffer that grew;
- any structure holding it once the conversation reaches a terminal
  state.

The middle clause is the one an implementation is most likely to miss.
A growable buffer that reallocates while a message is being encoded
leaves a complete copy of everything written so far in the abandoned
allocation, and erasing the buffer that survives does not touch it.
Reserving the encoded size before writing avoids the problem entirely.

## 2.12.2 What this does not promise

It guarantees that *these* buffers are erased before their memory is
reused. It cannot guarantee anything about copies made elsewhere — a
string the caller parsed a credential out of, a register or stack slot
the optimiser chose, a terminal's own input buffer. Those belong to
whoever made them.

Nor does it defend against a hostile kernel, a core dump, or swap. Those
are addressed elsewhere: process integrity protection, and disabling
core dumps for the authority.

## 2.12.3 Logging and diagnostics

An implementation MUST NOT write credential material to a log, an audit
record, an error message, or a debugging aid.

A type carrying credential material SHOULD render, under whatever debug
formatting its language provides, as a redaction rather than as its
contents. A stray diagnostic print is the most common way material
escapes a process that was otherwise careful, and the defence is to make
the careless thing produce nothing useful.

## 2.12.4 Collection

A client MUST collect a `Password` without echoing it. Where it cannot
establish that echo is suppressed, it MUST NOT collect the credential
anyway.

A client MUST NOT silently truncate an answer. `data` admits 32768 bytes
(§2.8); a client whose collection method admits fewer MUST fail the
conversation rather than send a prefix of what the principal supplied,
which would present as a wrong credential and leave the remainder in
whatever buffer it was read from.

## 2.12.5 Timing

An authority MUST NOT allow the *time* it takes to reach a denial to
distinguish an unknown principal from a bad credential (§2.10).

This is more demanding than it looks with a memory-hard verifier,
because the natural implementation returns immediately when a principal
does not exist and spends tens of milliseconds when one does. An
authority MUST perform equivalent work in both cases — verifying against
a decoy verifier that no credential matches is the usual construction.

> [!NOTE]
> A decoy derived from randomness at start-up, rather than from a
> constant, is not merely unguessable but not stable between boots
> either. An implementation built this way runs exactly one verification
> on both paths, including for a principal that requires no credential
> at all.

---

# 2.13 Identity Lookup

_Peios / Advanced Peios / PGSS / Logon_

> The second half of the chapter — turning an identity you already hold into something displayable, on a second socket rather than a second standard.

Sections 2.3 to 2.12 specify how a caller obtains a token. The remainder
of this chapter specifies how any program on the system turns an
identity it already holds into something it can display or compare: a
name into a SID, a SID into a name, a POSIX identifier into either.

It exists because a Linux program calls `getpwuid` and has never heard
of a token. Without this surface, every principal an authority mints
appears throughout the system as a bare number.

## 2.13.1 Why the authority answers this

An authority may federate identity to separate sources, and a source
counts POSIX identifiers **relative to a range the authority assigns
it**. The authority adds the base. No message in either direction
carries an absolute identifier to or from a source: a source asserts
relative numbers, and is asked by relative number or by SID.

The arithmetic that makes a number absolute therefore exists in exactly
one place, and only that place can invert it. No source can be asked
"who is uid 1001000", because no source is ever asked an absolute
number at all. The authority is not merely a convenient place to put
this — it is the only party the protocol permits to answer.

The same holds for names. A bare name may exist in more than one source,
and which one wins is a property of the system rather than of any source
in it (§2.15).

> [!NOTE]
> An authority that keeps all identity in one built-in database still
> satisfies this, and for it the rebasing question does not arise. The
> requirement is on the answer, not on how the answer is reached — as
> everywhere else in this chapter.

## 2.13.2 A second socket, not a second standard

Identity lookup is served on `/run/ident.sock` (§2.14), separately from
`/run/logon.sock`, by the same authority speaking the same framing
(§2.6).

The separation is **not** for isolation. One authority answers both, so
a defect or a hang in either reaches the other regardless; a second
socket buys nothing there and this chapter does not pretend otherwise.

It is for **admission**. A listening socket has one accept queue. A
single directory listing is thousands of lookups and a filesystem walk
is millions, where logons are a handful per boot — so a shared socket
would let an ordinary `find` fill the queue that an administrator needs
in order to sign in and stop it. Two sockets means the two populations
of caller cannot starve each other, whatever load either is under.

The second reason is access control. The set of programs that may
originate a logon is small and enumerable; the set that may look up a
name is every program on the system. Those want different security
descriptors, and a descriptor is a property of a socket.

## 2.13.3 Not a conversation

A logon is stateful: the connection *is* the conversation, and needs no
correlation identifier (§2.5).

A lookup is not. Requests are independent, a connection carries as many
as a client cares to send, and replies MAY return in an order other than
the one requests arrived in. Each request therefore carries a `tag` that
its reply echoes (§2.14).

> [!NOTE]
> That is what allows an authority to satisfy several outstanding
> requests from one connection together — against a remote source, in a
> single query — without a delay window that would tax a caller waiting
> on a single answer. Nothing here requires it, and an authority that
> answers strictly in order, one request at a time, is fully conformant.

## 2.13.4 Not authentication

No credential ever crosses this socket. There is no message with which a
client could offer one and none with which an authority could ask.

Everything here returns the kind of data a POSIX system has historically
kept in a world-readable file. An authority MAY nonetheless restrict
individual fields (§2.16), and the request names the fields it wants
precisely so that it can.

## 2.13.5 Not in this version

**Privileges, integrity levels, owner and default DACL are not returned
by any message here.** They are not identity: nothing stores them, and
an authority computes them from local policy at the moment it derives a
token (§2.1).

They are, however, deliberately *reserved* rather than excluded. An
authority that cannot be asked what a policy would produce can only be
verified by signing someone in and observing the result. A future
revision is expected to add a distinct request of the shape:

```
Evaluate { key, logon_type } -> { privileges, integrity, owner, default_dacl }
```

— a token that is derived and then discarded. It is parameterised by
logon type because the answer genuinely depends on it: an authority adds
SIDs reflecting *how* a principal signed in (§2.16), so "what privileges
does this principal have" has no single answer.

A revision MUST NOT instead add privileges or integrity as fields of
`Lookup` (§2.16). A field of a record describes something a source
holds; this does not.

---

# 2.14 The Identity Channel

_Peios / Advanced Peios / PGSS / Logon_

> /run/ident.sock — its path, access control, framing, multiplexing and bounds, and why it carries no descriptors.

## 2.14.1 Socket

An authority MUST listen on a `SOCK_STREAM` Unix domain socket at:

```
/run/ident.sock
```

The path is normative, for the same reason `/run/logon.sock` is: a name
resolver linked into every process on the system cannot be asked to find
it by configuration.

An authority MUST listen on both sockets. Offering one without the other
is not conformance — a system whose principals cannot be named is as
broken as one whose principals cannot sign in.

## 2.14.2 Access control

The socket MUST carry a security descriptor granting connect access to
every principal that runs ordinary programs.

This is the opposite posture to `/run/logon.sock`, and deliberately so.
A descriptor that withheld connect access would not protect anything: it
would make `ls -l` print numbers for the principals it excluded, while a
principal who *can* connect learns the same names either way.
Restriction, where an authority wants it, belongs on individual fields
(§2.16) — not on reaching the socket at all.

## 2.14.3 Framing

Messages use the header, magic, version, size limit and body
extensibility rules of §2.6 without modification. The message types
served here are disjoint from those of the logon socket (§2.A).

A message of one socket's range arriving on the other MUST be refused.
An authority MUST NOT serve a `Lookup` received on `/run/logon.sock`,
and MUST NOT serve a `LogonStart` received on `/run/ident.sock`.

> [!NOTE]
> Sharing the magic is what makes that a clean refusal rather than a
> parse failure. A peer connected to the wrong socket gets "this message
> is not served here" from byte six, instead of discovering the mistake
> several fields into a structure it has misread.

## 2.14.4 Multiplexing

A connection carries any number of requests. A client MAY have more than
one outstanding at a time, and an authority MAY answer them in any
order.

Every request carries a `tag`, a `u32` chosen by the client, and its
reply carries the same value. The tag is **in the message body**, not
the header, so that the header of §2.6 is unchanged.

A client MUST NOT reuse a tag while a request bearing it is outstanding.
An authority MUST echo the tag it received and MUST NOT interpret it
otherwise.

A client MUST NOT assume replies arrive in request order. An authority
that answers strictly in order is conformant; a client that depends on
it is not.

> [!NOTE]
> The contrast with §2.5 is deliberate rather than inconsistent. A logon
> connection exists for one logon and is discarded, so multiplexing
> would buy nothing and a correlation identifier would only add
> something to forge. A lookup connection is opened by a name resolver
> that may issue thousands of requests, and the identifier costs four
> bytes.

## 2.14.5 Concurrency and bounds

An authority MUST serve connections concurrently, and MUST NOT let a
slow answer on one connection delay answers on another.

An authority MUST bound the number of connections it will accept and the
number of requests it will hold outstanding per connection, and SHOULD
refuse further requests on a connection that exceeds the second rather
than closing it.

An authority MUST NOT wait indefinitely for a source. A request that
cannot be answered within the authority's own bound MUST be answered
`Unavailable` (§2.18). The bound is on the request, not on each source
consulted: an authority that walks several sources in turn MUST NOT let
the total exceed what it would have allowed one.

## 2.14.6 No descriptor passing

Nothing is transferred over this channel by descriptor. An authority
MUST ignore ancillary data received here.

## 2.14.7 Peer identity

An authority that restricts any field (§2.16) MUST establish peer
identity from the connected socket's peer token, as §2.4 requires, and
MUST NOT take it from a message body.

An authority that restricts no field MAY skip establishing peer
identity, since it would make no decision with it.

---

# 2.15 Names and Resolution

_Peios / Advanced Peios / PGSS / Logon_

> A name is opaque to the client — comparison, reserved characters, qualified names, and the search order an authority applies.

## 2.15.1 The name is opaque to the client

A name is carried as a single string. A client MUST forward what it was
given, unchanged, and MUST NOT parse, split, qualify, case-fold, or
otherwise interpret it.

All interpretation is the authority's.

This is the load-bearing rule of the section. If a client resolved part
of a name itself — chose which source to consult, or which of two
candidates wins — then that policy would live in every client
separately, and a native tool could resolve `jack` to a different
principal than a POSIX name resolver on the same machine. Two components
disagreeing about who a name refers to is not a display inconsistency;
it is a program acting on one principal's behalf while checking
another's access.

One authority means one answer, and it means a name resolves identically
here and in a logon (§2.7), because the same resolution serves both.

## 2.15.2 Comparison

Name comparison is **ASCII case-insensitive**.

This is normative rather than each source's own choice, because a system
may have more than one source and they must not disagree about whether
`JACK` is `jack`. An authority MUST establish the comparison for itself
rather than delegate it to whatever a source happens to do.

## 2.15.3 Reserved characters

A principal or group name MUST NOT contain any of:

| Character | Reserved for |
|---|---|
| `@` | Qualified names (below) |
| `\` | Qualified names, in the form some callers will type |
| `/` | Path separation — a name reaches a filesystem as a home directory |
| `:` | Field separation in POSIX `passwd` and `group` records |
| `,` | Member and subfield separation in POSIX `group` and GECOS records |

A name MUST NOT contain a byte outside the printable ASCII range
`0x20`–`0x7e`, and MUST NOT begin or end with a space.

An authority MUST refuse to create a name violating these rules, MUST
refuse one it is asked to resolve, and MUST refuse one asserted by a
source. The third is the one that matters: a name reaching a caller from
a source has crossed a boundary the other two never did, and it is the
one that ends up in a `passwd`-format record, an audit line, or a log.

The excluded control characters are the record separators. A name
carrying a newline could forge a whole line in a `passwd`-format file,
an audit record, or a log — and the damage is done by the reader, so it
cannot be prevented at the point the name is displayed.

The rules apply to every name an authority emits, including the names
carried alongside SIDs in references (§2.16), not only to the name a
request was keyed on.

> [!NOTE]
> The ASCII restriction is not an assumption that names are English. It
> is a decision to defer confusable and normalisation handling rather
> than get them wrong: `jack` spelled with a Cyrillic `а` renders
> identically to `jack` and is a different principal, and the same name
> in NFC and NFD is two byte sequences that a comparison will call two
> people. Relaxing this later is backward compatible; tightening it
> later would mean renaming principals that already exist.

## 2.15.4 Qualified names

A name MAY be qualified with a realm, written `name@realm`.

**No realm syntax is defined in this version.** An authority MUST refuse
a name containing `@`, and the character is reserved so that a principal
literally named `jack@local` cannot come into existence before the
syntax does.

Nothing here changes when realms arrive: a qualified name is a different
string in the same field, parsed by the same authority. That is why the
field is one opaque string rather than a structured pair — a structure
would have put the parsing in the client, which the first rule of this
section forbids.

## 2.15.5 Search order

A bare name is resolved by consulting sources in an order that is
**local configuration of the authority**.

An authority MUST resolve in a configured order, MUST NOT derive that
order from the order in which sources registered, and MUST stop at the
first source that answers.

> [!NOTE]
> Registration order is whatever the boot happened to do. Making it
> decide who `jack` is would mean a slow disk could change which
> principal a name refers to.

## 2.15.6 Answers an authority holds itself

An authority MAY answer from its own knowledge, without consulting any
source. Well-known principals and groups — those whose SIDs are fixed by
the system rather than issued by anybody — are the ordinary case: their
names, and the fact that nothing records their membership (§2.16), are
properties of the system.

Such an answer is subject to every rule in this section. In particular
an authority MUST apply the reserved-character and comparison rules to
it, and MUST NOT return an answer for a name it would have refused.

An answer of this kind is not an outage and does not make a reply
`Unavailable`, whatever the state of the configured sources. It is also
not a source: an authority MUST NOT report it in the `incomplete` list
of an enumeration (§2.17), and MUST NOT count it as a source having
answered for the purpose of §2.18.

## 2.15.7 Bare in, qualified out

Every successful reply carries the resolved **SID** and the **canonical
qualified name**, whatever form the request used (§2.16).

A caller that looked up a bare name can therefore always tell which
principal it got, compare two answers for identity, and record an
unambiguous name in a log. Ambiguity is permitted in what a caller may
ask; it is not permitted in what an authority answers.

Where no realm syntax exists, a canonical qualified name is
indistinguishable from a bare one, and the SID alongside it is what
carries the unambiguity. A client MUST NOT rely on the two being
distinguishable, in either direction: it MUST NOT assume a returned name
is unqualified, and MUST NOT treat one that is as a failure to
canonicalise.

## 2.15.8 Shadowing

Adding a source ahead of another in the search order changes which
principal a bare name resolves to. The new principal has a different
SID, so every security descriptor naming the old one stops applying to
the person now signing in under that name.

This is inherent to a flat namespace and this chapter does not forbid
it. An authority SHOULD detect it at logon — where the cost is one
additional query per sign-in rather than one per lookup — and SHOULD
record that a bare name resolved while another source could also have
answered.

An authority MUST NOT fail a logon because it could not complete that
check. A source being unreachable is not a reason to refuse a principal
whose own source answered.

---

# 2.16 Lookup

_Peios / Advanced Peios / PGSS / Logon_

> One request that answers every question a name resolver asks — keys, fields, memberships, and the present/withheld distinction.

One request answers every question a name resolver asks.

## 2.16.1 Lookup

`msg_type` = `0x0010`. Client to authority.

| Field | Encoding | Limit |
|---|---|---|
| `tag` | `u32` | §2.14 |
| `key_type` | `u8` | §2.B |
| `name` | string | 256 bytes |
| `sid` | length-framed bytes (SID) | 68 bytes |
| `unix_id` | `u32` | |
| `kind` | `u8` | §2.B |
| `fields` | `u32` | §2.B |

Exactly one of `name`, `sid` and `unix_id` is meaningful, selected by
`key_type`. An encoder MUST leave the others empty or zero, and a
decoder MUST ignore them.

### 2.16.1.1 key_type

| Value | Name | Answers |
|---|---|---|
| 1 | `Name` | `getpwnam`, `getgrnam` |
| 2 | `Sid` | Rendering a security descriptor |
| 3 | `UnixId` | `getpwuid`, `getgrgid` |

`UnixId` is a key even though **no source is ever asked one**. An
authority converts the number to a source and a relative identifier by
the range arithmetic it assigned, and asks that source by relative
identifier or by SID.

A source therefore never receives an absolute number here, exactly as it
never receives one during a logon. The property that made the authority
the only possible answerer (§2.13) is the same property that keeps this
side of it confined.

### 2.16.1.2 kind

| Value | Name |
|---|---|
| 0 | `Any` |
| 1 | `Principal` |
| 2 | `Group` |

A request for `Principal` MUST NOT be answered with a group, and a
request for `Group` MUST NOT be answered with a principal. Where the key
matches only an object of the other kind, the outcome is `NotFound`
(§2.18).

An authority MUST establish this for itself. Where the object came from
a source, the authority MUST check the kind it received against the kind
that was asked for, rather than relaying the source's answer and letting
the client discover the mismatch.

> [!NOTE]
> POSIX keeps users and groups in separate namespaces, so `getpwnam` and
> `getgrnam` can be asked the same string and expect different objects.
> Carrying the kind on the request is what lets both be served correctly
> without requiring an authority to forbid the collision.

### 2.16.1.3 fields

A bitmask of the attributes the reply should carry. Identity is not
among them: every successful reply carries the SID, the canonical
qualified name, and the kind actually found, and a client cannot decline
them.

| Bit | Name | Value encoding |
|---|---|---|
| 0 | `UNIX_ID` | `u32` |
| 1 | `PRIMARY_GROUP` | reference |
| 2 | `HOME` | string, 4096 bytes |
| 3 | `SHELL` | string, 4096 bytes |
| 4 | `DISPLAY_NAME` | string, 256 bytes |
| 5 | `GROUPS` | array of references, 128 |
| 6 | `MEMBERS` | array of references, 256 |
| 7 | `CLAIMS` | array of claim entries, 64 |
| 8 | `ENABLED` | `u8` |

An authority MUST ignore a bit it does not implement, and MUST NOT
report it (below). Claim entries use the claim attribute format PCDS
§5.9 specifies.

> [!NOTE]
> Field bits are the one place in this chapter where an enumeration may
> grow without a version bump (§2.6). It is safe here, and only here,
> because the reply says which fields it is answering: an older
> authority ignoring a newer bit is reported as not implementing it,
> rather than silently omitting something a client believed it had asked
> for.

Requesting only what is wanted is not primarily a bandwidth measure — a
`getpwuid` wants nearly every field anyway. It matters for the caller
that resolves a dozen SIDs to render a security descriptor and wants
only names, and it is the seam at which an authority can restrict
individual fields without restricting the socket (§2.14).

## 2.16.2 LookupReply

`msg_type` = `0x8010`. Authority to client.

| Field | Encoding | Limit |
|---|---|---|
| `tag` | `u32` | §2.14 |
| `outcome` | `u8` | §2.B |
| `sid` | length-framed bytes (SID) | 68 bytes |
| `qualified_name` | string | 512 bytes |
| `kind_found` | `u8` | §2.B |
| `present` | `u32` | §2.B |
| `withheld` | array of withheld entries | 32 |
| `values` | array of length-framed values | 32 |

Where `outcome` is not `Found`, everything after it MUST be empty or
zero, and a client MUST NOT read it.

`kind_found` MUST be `Principal` or `Group`, never `Any`.

### 2.16.2.1 present, withheld and values

`values` holds one length-framed value for each bit set in `present`, in
**ascending bit order**. Each is length-framed so that a client can step
over a value whose field it does not recognise.

Every bit set in the request is in exactly one of three states:

- **set in `present`** — its value is in `values`;
- **listed in `withheld`** — with a reason, below;
- **in neither** — this authority does not implement the field.

The third state is a statement about the authority, not about the
object. An authority that implements a field MUST place every request
for it in one of the first two states, whatever happened underneath: a
field an authority supports but could not obtain is `Absent`,
`Declined`, `Restricted` or `TooLarge`, never silence. Reporting it as
neither would tell the client the authority cannot answer that question
at all, and a client is entitled to stop asking.

A withheld entry is a length-framed structure:

| Field | Encoding | Limit |
|---|---|---|
| `field` | `u32` | one bit, §2.B |
| `reason` | `u8` | §2.B |

| Reason | Meaning |
|---|---|
| `Absent` | The field has no value. |
| `Restricted` | The caller may not have this field. |
| `Declined` | The source will not produce it. |
| `TooLarge` | It exists and exceeds one reply. Use `Enumerate` (§2.17). |

Distinguishing these is the point of the structure. An empty member list
and a source that refuses to enumerate members are the same bytes to a
POSIX caller, and an administrator diagnosing a system needs to know
which one happened.

### 2.16.2.2 References

`PRIMARY_GROUP`, `GROUPS` and `MEMBERS` carry references rather than
bare SIDs:

| Field | Encoding | Limit |
|---|---|---|
| `sid` | length-framed bytes (SID) | 68 bytes |
| `name` | string | 512 bytes |
| `unix_id` | `u32` | |

An empty `name` means the authority has no name for that SID; a
`unix_id` of **zero** means it has no number for it. Both are ordinary
answers for a SID belonging to no source on this machine.

Zero is the only encoding of "no number". An authority MUST NOT
substitute a POSIX identifier that a caller could mistake for a real
one — a projection onto `nobody` is a rendering decision belonging to
whatever produces a `passwd` record, and putting it on the wire
destroys the distinction the client needs in order to make it.

Carrying the name is what keeps the round-trip discipline below intact.
A reply of bare SIDs would make a single `getgrnam` into one request
plus one per member.

## 2.16.3 Memberships

`GROUPS` on a principal is the membership question, and it is the
direction sources actually hold — the same one a logon uses. An
authority MUST answer it whenever it can answer anything about the
principal at all.

`MEMBERS` on a group is the reverse index, and it is not owed the same
guarantee.

An authority MUST return `MEMBERS` as withheld, rather than as an error
or an empty list, when it will not or cannot produce it:

- **`Declined`** where the source will not enumerate the group's
  members.
- **`TooLarge`** where the membership exceeds what one reply can carry.
- **`Absent`** where the group is not one that has recorded members at
  all.

That last case is not a limitation. A group's membership may be
**recorded** — held by a source, as a local group's is — or it may be a
**rule** an authority applies when it derives a token. Nothing records
who belongs to `Everyone`; an authority adds it to every token it mints.
Groups reflecting *how* a principal signed in are further still from a
recorded membership: they are properties of a logon rather than of a
principal, and the same principal is in one at a console and not over a
network.

An authority MUST report `Absent` for such a group rather than
manufacturing a list, and MUST NOT report `Declined`, which would
suggest an answer exists somewhere.

> [!NOTE]
> This asymmetry is deliberate and follows the shape of the data. "Which
> groups is this principal in" is answered by every source cheaply,
> because it is how memberships are stored. "Who is in this group" needs
> an index in the other direction, over a set that may be the whole of
> an organisation — historically the reliable way to make a directory
> server fall over from a filesystem listing.

## 2.16.4 One round trip

An authority MUST be able to answer each of the following in a single
request:

| Caller wants | Request |
|---|---|
| A `passwd` record | `Lookup{key_type: UnixId or Name, kind: Principal, fields: UNIX_ID \| PRIMARY_GROUP \| HOME \| SHELL \| DISPLAY_NAME}` |
| A `group` record | `Lookup{key_type: UnixId or Name, kind: Group, fields: UNIX_ID \| MEMBERS}` |
| A principal's groups | `Lookup{key_type: Name, kind: Principal, fields: GROUPS}` |
| A name for a SID | `Lookup{key_type: Sid, kind: Any, fields: 0}` |

This is a design constraint on future revisions as much as a statement
about this one. A name resolver is called from every process on the
system, synchronously, and a field that cannot be fetched alongside the
record it belongs to turns one lookup into several.

---

# 2.17 Enumeration

_Peios / Advanced Peios / PGSS / Logon_

> Walking every principal or every member of a large group, with a cursor and no completeness guarantee.

`Lookup` answers about one object that a caller can already name.
Enumeration answers when it cannot: walking every principal on the
system, or every member of a group too large for one reply.

## 2.17.1 Enumerate

`msg_type` = `0x0011`. Client to authority.

| Field | Encoding | Limit |
|---|---|---|
| `tag` | `u32` | §2.14 |
| `kind` | `u8` | §2.B |
| `fields` | `u32` | §2.B |
| `of_key_type` | `u8` | §2.B |
| `of_name` | string | 256 bytes |
| `of_sid` | length-framed bytes (SID) | 68 bytes |
| `of_unix_id` | `u32` | |
| `cursor` | length-framed bytes | 256 bytes |

`kind` MUST NOT be `Any`. A caller enumerating is filling a `passwd` or
a `group` table, and the two are separate.

### 2.17.1.1 of

Where `of_key_type` is zero, the request enumerates every object of
`kind` that the authority knows.

Where it is non-zero, the named object MUST be a group, and the request
enumerates **that group's members**. This is the continuation path for a
`MEMBERS` field withheld as `TooLarge` (§2.16): the same answer, paged.
An authority that withholds `MEMBERS` as `TooLarge` MUST serve this
mode, since there is otherwise no way to obtain what it said existed.

> [!NOTE]
> Members are a field of `Lookup` for the common case and a mode of
> `Enumerate` for the large one, rather than a message of their own. The
> field keeps `getgrnam` to one round trip where the group is small,
> which is nearly always; this keeps the large case correct without a
> third request type or a partial member list, which would be worse than
> no list at all.

### 2.17.1.2 cursor

Empty on the first request. On a continuation it MUST be the `next`
returned by the immediately preceding reply.

A cursor is **opaque**. A client MUST NOT construct, parse or modify
one, and MUST NOT present one to a different authority or after
reconnecting.

An authority MUST reject a cursor it did not issue, or one it can no
longer honour, with `Malformed` (§2.18). It MUST NOT silently restart
the enumeration, and MUST NOT answer with an empty page and an empty
`next`: the first hands the caller a second copy of the beginning under
the impression it is continuing, and the second reports a truncated walk
as a complete one.

## 2.17.2 EnumerateReply

`msg_type` = `0x8011`. Authority to client.

| Field | Encoding | Limit |
|---|---|---|
| `tag` | `u32` | §2.14 |
| `outcome` | `u8` | §2.B |
| `entries` | array of entries | 256 |
| `next` | length-framed bytes | 256 bytes |
| `incomplete` | array of strings | 32 |

An entry is a length-framed structure carrying the same fields as a
successful `LookupReply` (§2.16), from `sid` through `values`.

An empty `next` means the enumeration is complete. A non-empty `next`
means there is more, **even if `entries` was empty** — an authority may
return a short or empty page while working through a source.

A client MUST continue until `next` is empty. A client MUST NOT infer
completion from an empty page.

A client MUST NOT report an enumeration as complete when it stopped for
any other reason. A transport failure, a timeout, or an outcome other
than `Found` mid-walk is a truncated enumeration, and a client that
presents it to its caller as the end of the list has manufactured an
empty system out of an outage — the same error §2.18 forbids on a single
lookup, at a scale where nothing records that it happened.

## 2.17.3 incomplete

Each string names a source that did not contribute: because it declined
to enumerate, or because it could not be reached.

An authority MUST list every such source. A client displaying an
enumeration SHOULD say that it is partial, and MUST NOT discard the list
without doing so.

> [!NOTE]
> A short list that looks complete is the failure this field exists to
> prevent. An administrator reading an account listing has no way to
> tell a machine with four principals from a machine whose directory did
> not answer, and the difference decides whether they are looking at the
> whole picture.

## 2.17.4 No completeness guarantee

An authority MUST NOT be required to enumerate.

A source may hold more principals than a reply, a page, or an
administrator's patience can carry, and a source backed by a remote
directory may be able to answer any single question while being quite
unable to answer all of them. `incomplete` is the honest outcome, not a
degraded one.

A caller MUST NOT treat enumeration as a way to test whether a principal
exists. `Lookup` answers that, exactly, at any scale.

---

# 2.18 Outcomes

_Peios / Advanced Peios / PGSS / Logon_

> The outcome every identity-channel reply carries — NotFound, Unavailable and Refused — and why these are not a security boundary.

Every reply on the identity channel carries an outcome.

| Value | Name | Meaning |
|---|---|---|
| 1 | `Found` | The request was answered. |
| 2 | `NotFound` | No such object, and every source that could have said so was asked. |
| 3 | `Unavailable` | A source that could have answered did not. |
| 4 | `Refused` | The caller may not make this request. |
| 5 | `Malformed` | The request could not be understood. |

Unlike the denial codes of §2.10, these are not a security boundary.
`NotFound` reveals that a name is unused, which is what a name lookup is
for.

An `EnumerateReply` carries an outcome on the same terms. An authority
MUST NOT report `Found` on a page it could not produce.

## 2.18.1 NotFound and Unavailable

**If any source that could have answered was unavailable, the outcome is
`Unavailable` — even if every source that did answer said no.**

This is the most important rule in this part of the chapter, and it is
stated normatively rather than left to implementations because it is
invisible when wrong.

An authority is expected to cache. A cache that is told `NotFound` will
store an absence, and if that absence was really a source being
unreachable, it has memoised an outage as a fact. The account comes back
when the source does; the cached answer does not. A principal is then
unable to sign in, or a file is shown as owned by a number, for as long
as the entry lives — with nothing in the system still recording that
anything failed.

`Unavailable` is not cacheable, and that is the whole of the difference.

An authority MUST NOT report `NotFound` unless every source in the
search order (§2.15) that could have answered was consulted and
answered. A source that does not serve lookups at all is not such a
source and does not need to be asked; a source that does, and could not
be reached, is.

> [!NOTE]
> The rule binds even where an authority does not cache, because its
> clients may. A name resolver is entitled to remember an answer for the
> length of a process, and it can only do that safely if the two are
> distinguishable.

## 2.18.2 Unavailable and the search order

An authority MUST consult sources in the configured order and MUST stop
at the first that answers, so an unreachable source **later** in the
order does not make an answer `Unavailable`. It was never going to be
asked.

An unreachable source **earlier** in the order does, even if a later one
holds a matching name — because the earlier source is the one whose
answer would have won.

> [!NOTE]
> Returning the later source's principal in that situation would be
> worse than failing. The name would resolve to a different SID than it
> does when the system is healthy, so access decisions would be made
> against the wrong principal precisely while something is broken.

## 2.18.3 Refused

Reserved. No field in this version is restricted by default, and an
authority that restricts none will never send it.

It exists because the field mask (§2.16) is where restriction belongs,
and a request refused in its entirety needs an answer that is not
`NotFound`. An authority restricting a *field* MUST use the `Restricted`
withheld reason instead, and MUST still answer the rest of the request.

Because it is reserved, `Refused` MUST NOT be used for anything else. A
source that will not answer, a mode an authority has not implemented, or
a question it cannot serve are not the caller lacking permission, and
reporting them as `Refused` tells a client to stop asking on behalf of
this caller when the answer would be the same for every caller. Those
outcomes are `Unavailable`, `Absent`, or `Malformed` as the case
requires.

An authority MUST NOT use `NotFound` in place of `Refused` or of a
restricted field. Concealing a principal's existence from a caller that
may not read their shell protects nothing — the SID is already in the
file listing that prompted the lookup — and would make an authorization
decision indistinguishable from an empty system.

## 2.18.4 Timing

The prohibition in §2.10 on distinguishing an unknown principal from a
bad credential does not apply here. There is no credential, and
`NotFound` is an ordinary answer that this chapter states plainly.

An authority MUST NOT allow the *presence* of an answer in a cache to be
observable to a caller that would be refused the answer itself. Where no
field is restricted, nothing is refused, and the requirement is vacuous.

---

# 2.19 Conformance

_Peios / Advanced Peios / PGSS / Logon_

> Every requirement of this chapter collected by role, for an implementation claiming to be a PGSS Logon authority or client.

A conforming implementation MUST satisfy every requirement in this
chapter. This section collects them by role.

## 2.19.1 Authority obligations

An implementation claiming to be a PGSS Logon authority MUST satisfy all
of the following.

### 2.19.1.1 Channel

1. Listen on `/run/logon.sock` as a `SOCK_STREAM` Unix domain socket
   (§2.5).
2. Control access to it with a security descriptor, not with process
   integrity and not with POSIX permission bits (§2.4).
3. Serve conversations concurrently, so that one stalled logon does not
   block others (§2.5).
4. Support `SCM_RIGHTS` on the channel (§2.9).

### 2.19.1.2 Framing

5. Reject any message whose magic is not `PGSL`, as a whole-connection
   failure (§2.6).
6. Reject any message whose version it does not implement, answering
   `UnsupportedVersion` where it can still encode a denial (§2.6).
7. Reject any message declaring more than 65536 bytes, or fewer than a
   header, without reading the body (§2.6).
8. Skip to a structure's declared end after reading known fields, rather
   than assuming its length, and substitute a documented default for an
   optional trailing field a peer did not write (§2.6).

### 2.19.1.3 Conversation

9. Require `LogonStart` as the first message, and reject a conversation
   opening otherwise (§2.3).
10. Send exactly one terminal message, nothing after it, and close the
    connection (§2.3).
11. Bound the number of rounds, the time spent awaiting an answer, and
    the time a conversation may remain open, and terminate with
    `ConversationLimit` on exhausting any of them (§2.3, §2.5). A bound
    reached MUST produce a terminal message; closing the connection
    silently is what the code exists to prevent.
12. Reject a `CredentialResponse` it did not solicit (§2.3).
13. Match answers to prompts by `credential_ref`, never by position, and
    treat a missing answer as a failed logon rather than as grounds to
    tear down the connection (§2.8).

### 2.19.1.4 Trust

14. Establish peer identity from the connected socket's peer token,
    never from a message body, and never via `SO_PEERCRED` (§2.4).
15. Treat `logon_type` as a proposal and constrain it against the
    verified peer (§2.4).
16. Treat `identifier`, `tty` and `remote_host` as unverified claims
    (§2.4).

### 2.19.1.5 Derivation

17. Perform derivation itself, and never accept a token, privilege set
    or integrity level from another party (§2.1).
18. Never fork or exec on the client's behalf; never learn about
    terminals, environments or session leadership (§2.1).

### 2.19.1.6 Prompting

19. Never send a prompt for a credential type absent from the client's
    `supported_credential_types`, and deny the logon instead if it
    cannot proceed within them (§2.8).
20. Ensure `credential_ref` is unique among the prompts of one request
    (§2.8).

### 2.19.1.7 Result

21. Transfer the token as a file descriptor in the ancillary data of
    `AccessGranted`, never by name (§2.9).
22. Never distinguish an unknown principal from a bad credential — by
    denial code, by reason text, or by timing (§2.10, §2.12).
23. Send `home` and `shell` as absolute paths, or empty, whatever their
    provenance; an authority with no value for a profile field MUST
    leave it empty rather than invent one (§2.9).

### 2.19.1.8 Credential handling

24. Erase credential material, and every buffer it was encoded and
    decoded through — including an allocation abandoned by a buffer that
    grew — before that memory is released (§2.12).
25. Never write credential material to a log, audit record, error
    message or diagnostic (§2.12).

### 2.19.1.9 Identity lookup

26. Listen on `/run/ident.sock` as well as `/run/logon.sock`, as a
    `SOCK_STREAM` Unix domain socket (§2.14).
27. Grant connect access to that socket, by security descriptor, to
    every principal that runs ordinary programs (§2.14).
28. Refuse a message of one socket's range received on the other
    (§2.14).
29. Echo each request's `tag`, and never interpret it otherwise
    (§2.14).
30. Serve requests concurrently, and never let a slow answer on one
    connection delay another (§2.14).
31. Bound the time spent answering a request, and answer `Unavailable`
    rather than waiting indefinitely (§2.14).
32. Ignore ancillary data received on the identity socket (§2.14).

### 2.19.1.10 Resolution

33. Interpret names itself, and never require a client to parse, qualify
    or case-fold one (§2.15).
34. Compare names ASCII case-insensitively, establishing the comparison
    itself rather than delegating it to a source (§2.15).
35. Refuse a name containing a reserved character, a byte outside
    `0x20`–`0x7e`, or a leading or trailing space — whether created
    locally, received in a request, asserted by a source, or carried
    alongside a SID in a reference (§2.15).
36. Resolve bare names in a configured order, never in registration
    order, stopping at the first source that answers (§2.15).
37. Carry the resolved SID and the canonical qualified name on every
    successful reply, whatever form the request used (§2.15).

### 2.19.1.11 Answers

38. Never answer a `Principal` request with a group, or a `Group`
    request with a principal, and establish the kind itself rather than
    relaying a source's (§2.16).
39. Ignore a field bit it does not implement, and never report it as
    present (§2.16).
40. Report each requested field it implements as present or as withheld
    with a reason, never as neither (§2.16).
41. Withhold `MEMBERS` with a reason rather than returning a partial or
    empty list, where it will not or cannot produce it (§2.16).
42. Report `Absent` rather than `Declined` for a group whose membership
    is a rule rather than a record (§2.16).
43. Encode "no POSIX identifier" as zero, and never substitute a number
    a caller could mistake for a real one (§2.16).
44. Answer a `passwd` record, a `group` record, a principal's
    memberships, or a name for a SID, each in one request (§2.16).
45. Never report `NotFound` unless every source in the search order that
    could have answered was consulted and answered (§2.18).
46. Report `Unavailable` where a source earlier in the search order
    could not be reached (§2.18).
47. Never report `NotFound` in place of `Refused` or of a `Restricted`
    field, and never report `Refused` for anything other than a caller
    that may not make the request (§2.18).
48. List every source that did not contribute to an enumeration, and
    never report `Found` on a page it could not produce (§2.17, §2.18).
49. Reject a cursor it did not issue, or can no longer honour, with
    `Malformed` — never by restarting the walk and never by reporting it
    complete (§2.17).
50. Serve member enumeration where it withholds `MEMBERS` as `TooLarge`
    (§2.17).

## 2.19.2 Client obligations

There are two client roles, and they are independent. A program may be
either, both, or neither: a logon originator never looks a principal up;
a name resolver does the reverse.

An implementation originating logons MUST satisfy obligations 1 to 20.
An implementation performing identity lookup MUST satisfy 21 to 27.

### 2.19.2.1 Conversation

1. Send exactly one `LogonStart`, as the first message (§2.3).
2. Answer each `CredentialRequest` with exactly one `CredentialResponse`
   (§2.3).
3. Never send a `CredentialResponse` that was not solicited (§2.3).
4. Treat a connection that closes without a terminal message as a failed
   logon, and not retry automatically (§2.3).

### 2.19.2.2 Framing

5. Reject any message whose magic is not `PGSL` (§2.6).
6. Reject any message whose version it does not implement (§2.6).
7. Skip to a structure's declared end after reading known fields (§2.6).

### 2.19.2.3 Capabilities

8. Never declare in `supported_credential_types` a credential type it
   cannot render (§2.7). Declaring fewer than it can render is
   permitted, and an empty list is how a client asks for a logon
   requiring no interaction.
9. Fail the conversation, rather than guess, if it receives a prompt it
   cannot render (§2.8).

### 2.19.2.4 Rendering

10. Display received messages, in order, before the prompts of the same
    request (§2.8).
11. Not interpret or branch on message text, `credential_name`, or
    `reason` (§2.8, §2.10).
12. Echo `credential_ref` back unchanged (§2.8).

### 2.19.2.5 Result

13. Read the token descriptor from the ancillary data of
    `AccessGranted`, and treat an `AccessGranted` without one as a
    failed logon (§2.9).
14. Close the descriptor if it does not install the token (§2.9).
15. Have a fallback for every `profile` field, and treat an empty field,
    and an absent `profile`, identically and not as an error (§2.9).
16. Never execute a relative `shell`, nor resolve a relative `home`
    against its own working directory (§2.9). A `shell` containing no
    separator is relative, and MUST NOT be resolved against a search
    path.
17. Never treat `profile` as a directory lookup, nor cache it as an
    answer about any principal other than the one who just authenticated
    (§2.9).

### 2.19.2.6 Credential handling

18. Collect a credential only where it can establish that its collection
    method meets the type's requirements, and never collect a `Password`
    with echo enabled (§2.12).
19. Never silently truncate an answer to fit its own buffer (§2.12).
20. Erase credential material, and every buffer it was collected and
    encoded through, before that memory is released, and never write it
    to a log, error message or diagnostic (§2.12).

### 2.19.2.7 Identity lookup

21. Forward a name unchanged, and never parse, split, qualify or
    case-fold one (§2.15).
22. Never reuse a `tag` while a request bearing it is outstanding, and
    never assume replies arrive in request order (§2.14).
23. Treat a cursor as opaque: never construct, parse or modify one, nor
    present one to a different authority or across a reconnection
    (§2.17).
24. Continue an enumeration until `next` is empty, never infer
    completion from an empty page, and never present a walk it abandoned
    for any other reason as a complete one (§2.17).
25. Surface an enumeration's `incomplete` list rather than discarding it
    (§2.17).
26. Distinguish `NotFound` from `Unavailable`, and never cache the
    second as though it were the first (§2.18).
27. Never use enumeration to test whether a principal exists (§2.17).

Obligations 24 and 26 bind a client that caches for the lifetime of a
single process just as they bind an authority. A resolver that remembers
"no such user" through an outage will keep reporting it after the outage
ends, and one that reports a failed walk as an empty system does the
same thing to every principal at once.

## 2.19.3 What a client is not required to do

A client is **not** required to understand what any credential type
means, what any message says, or why a logon was denied. It renders what
it is given and returns what it collects.

That is the property the whole conversational shape exists to produce,
and a client that starts reasoning about the content of prompts has
given it up — it will need changing the next time an authority's policy
does.

---

# Appendix 2.A Message Reference

_Peios / Advanced Peios / PGSS / Logon_

> Every message type by number and socket, the protocol constants, and the field limits.

## 2.A.1 Messages

### 2.A.1.1 On `/run/logon.sock`

| `msg_type` | Message | Direction | Defined in |
|---|---|---|---|
| `0x0001` | `LogonStart` | client → authority | §2.7 |
| `0x0002` | `CredentialResponse` | client → authority | §2.8 |
| `0x8001` | `CredentialRequest` | authority → client | §2.8 |
| `0x8002` | `AccessGranted` | authority → client | §2.9 |
| `0x8003` | `AccessDenied` | authority → client | §2.10 |

### 2.A.1.2 On `/run/ident.sock`

| `msg_type` | Message | Direction | Defined in |
|---|---|---|---|
| `0x0010` | `Lookup` | client → authority | §2.16 |
| `0x0011` | `Enumerate` | client → authority | §2.17 |
| `0x8010` | `LookupReply` | authority → client | §2.16 |
| `0x8011` | `EnumerateReply` | authority → client | §2.17 |

The high bit marks a message sent by the authority (§2.6). The two
ranges are disjoint, and a message of one range MUST be refused on the
other socket (§2.14).

## 2.A.2 Protocol constants

| Constant | Value | Defined in |
|---|---|---|
| Logon socket path | `/run/logon.sock` | §2.5 |
| Identity socket path | `/run/ident.sock` | §2.14 |
| Magic | `PGSL` (`50 47 53 4c`) | §2.6 |
| Version | `1` | §2.6 |
| Header size | 12 bytes | §2.6 |
| Maximum message size | 65536 bytes | §2.6 |

## 2.A.3 Field limits

| Field | Maximum | Defined in |
|---|---|---|
| `identifier` | 1024 bytes | §2.7 |
| `tty` | 128 bytes | §2.7 |
| `remote_host` | 256 bytes | §2.7 |
| `supported_credential_types` | 32 entries | §2.7 |
| `messages` | 8 entries | §2.8 |
| `prompts` | 16 entries | §2.8 |
| `answers` | 16 entries | §2.8 |
| `text` | 512 bytes | §2.8 |
| `credential_name` | 128 bytes | §2.8 |
| `data` | 32768 bytes | §2.8 |
| `home` | 4096 bytes | §2.9 |
| `shell` | 4096 bytes | §2.9 |
| `display_name` | 256 bytes | §2.9 |
| `reason` | 512 bytes | §2.10 |
| `name` (lookup key) | 256 bytes | §2.16 |
| `sid` | 68 bytes | §2.16 |
| `qualified_name` | 512 bytes | §2.16 |
| `withheld` | 32 entries | §2.16 |
| `values` | 32 entries | §2.16 |
| reference `name` | 512 bytes | §2.16 |
| `HOME`, `SHELL` | 4096 bytes | §2.16 |
| `DISPLAY_NAME` | 256 bytes | §2.16 |
| `GROUPS` | 128 entries | §2.16 |
| `MEMBERS` | 256 entries | §2.16 |
| `CLAIMS` | 64 entries | §2.16 |
| `of_name` | 256 bytes | §2.17 |
| `cursor`, `next` | 256 bytes | §2.17 |
| `entries` | 256 entries | §2.17 |
| `incomplete` | 32 entries | §2.17 |
| `incomplete` source name | 32 bytes | §2.17 |

The 68-byte SID maximum is a property of the SID encoding rather than of
this chapter: an eight-byte prelude plus the fifteen sub-authorities the
one-byte count admits. It is specified in PCDS.

An encoder MUST refuse to produce a field exceeding its maximum; a
decoder MUST reject one it receives (§2.6).

---

# Appendix 2.B Enumerations

_Peios / Advanced Peios / PGSS / Logon_

> Every enumerated value the protocol carries — logon and identifier types, credentials, severities, denial codes, keys, kinds, fields and outcomes.

Adding a value to any enumeration here is a breaking change requiring a
version bump — see §2.6. The single exception is the field mask, noted
below.

## 2.B.1 Logon types

Carried in `LogonStart.logon_type` (§2.7) as a `u8`. Semantics are
defined by KACS and described in the Peios Kernel TRM; this table is for
reference.

| Value | Name |
|---|---|
| 2 | Interactive |
| 3 | Network |
| 4 | Batch |
| 5 | Service |
| 8 | NetworkCleartext |
| 9 | NewCredentials |

The gaps are deliberate: the numbering follows KACS, and values it does
not define are not available here.

## 2.B.2 Identifier types

Carried in `LogonStart.identifier_type` (§2.7) as a `u8`.

| Value | Name | `identifier` holds |
|---|---|---|
| 1 | Username | A principal name |

## 2.B.3 Credential types

Carried in `Prompt.credential_type` (§2.8) and in
`LogonStart.supported_credential_types` (§2.7) as a `u8`. See §2.11 for
when a new one is warranted.

| Value | Name | Collection |
|---|---|---|
| 1 | Password | A line of text, not echoed |

## 2.B.4 Message severities

Carried in `Message.severity` (§2.8) as a `u8`.

| Value | Name |
|---|---|
| 0 | Info |
| 1 | Error |

## 2.B.5 Denial codes

Carried in `AccessDenied.denial` (§2.10) as a `u32`.

| Value | Name | Meaning |
|---|---|---|
| 1 | `MalformedRequest` | The message could not be understood. |
| 2 | `UnsupportedVersion` | The protocol version is not implemented. |
| 3 | `PermissionDenied` | The peer may not originate this logon at all. |
| 4 | `AuthenticationFailed` | The principal is unknown, or the credential is wrong. Deliberately one code — see §2.10. |
| 5 | `LogonTypeNotPermitted` | The peer may not request this kind of session. |
| 6 | `AccountRestricted` | The principal exists and authenticated, but policy refuses this logon. |
| 7 | `AuthorityUnavailable` | The authority cannot reach what it needs to decide. |
| 8 | `ConversationLimit` | Too many rounds, or too long without an answer. |
| 9 | `Internal` | The authority failed for a reason it will not describe. |

## 2.B.6 Key types

Carried in `Lookup.key_type` (§2.16) and `Enumerate.of_key_type`
(§2.17) as a `u8`. Zero in `of_key_type` means the field is unused.

| Value | Name | Key is in |
|---|---|---|
| 1 | `Name` | `name` |
| 2 | `Sid` | `sid` |
| 3 | `UnixId` | `unix_id` |

## 2.B.7 Object kinds

Carried in `Lookup.kind`, `LookupReply.kind_found` and `Enumerate.kind`
(§2.16, §2.17) as a `u8`.

| Value | Name |
|---|---|
| 0 | `Any` |
| 1 | `Principal` |
| 2 | `Group` |

`Any` is not valid in `kind_found` or in `Enumerate.kind`.

## 2.B.8 Fields

Carried in `Lookup.fields`, `Enumerate.fields` and `LookupReply.present`
(§2.16) as a `u32` bitmask, and in a withheld entry's `field` as a
single bit.

| Bit | Name | Value encoding |
|---|---|---|
| 0 | `UNIX_ID` | `u32` |
| 1 | `PRIMARY_GROUP` | reference |
| 2 | `HOME` | string |
| 3 | `SHELL` | string |
| 4 | `DISPLAY_NAME` | string |
| 5 | `GROUPS` | array of references |
| 6 | `MEMBERS` | array of references |
| 7 | `CLAIMS` | array of claim entries |
| 8 | `ENABLED` | `u8` |

This is the sole exception to the rule above. A bit MAY be added without
a version bump, because a reply states which fields it answered and an
authority MUST ignore a bit it does not implement (§2.16).

## 2.B.9 Lookup outcomes

Carried in `LookupReply.outcome` and `EnumerateReply.outcome` (§2.18) as
a `u8`.

| Value | Name |
|---|---|
| 1 | `Found` |
| 2 | `NotFound` |
| 3 | `Unavailable` |
| 4 | `Refused` |
| 5 | `Malformed` |

## 2.B.10 Withheld reasons

Carried in a withheld entry's `reason` (§2.16) as a `u8`.

| Value | Name | Meaning |
|---|---|---|
| 1 | `Absent` | The field has no value. |
| 2 | `Restricted` | The caller may not have this field. |
| 3 | `Declined` | The source will not produce it. |
| 4 | `TooLarge` | It exists and exceeds one reply; use `Enumerate` (§2.17). |

---

# Appendix 2.C Prior Art

_Peios / Advanced Peios / PGSS / Logon_

> Where PGSS Logon sits against Windows LSA, PAM and POSIX name resolution — and which resemblances are deliberate.

## 2.C.1 Windows LSA

The closest predecessor is the Windows Local Security Authority and its
`LsaLogonUser` interface. What is taken and what is deliberately left is
worth stating, because the resemblance is close enough that the
differences matter.

**Taken.** The separation of *authentication* from *derivation* —
establishing who someone is, and then constructing a token for them, are
different acts by different rules. The idea that a logon produces both a
token and a session, and that the session records which authority
vouched for it. The vocabulary of logon types.

**Diverged.** LSA's authentication packages are DLLs loaded into the LSA
process, so a defect in any package is a defect in the most privileged
process on the system. Nothing in PGSS Logon admits an in-process
extension point; an authority that federates does so across a process
boundary of its own choosing.

**Rejected.** The challenge-response family (NTLM and successors).
Challenge-response requires the verifier to store something a response
can be recomputed from, which is password-equivalent material — the
pass-the-hash failure mode, where stealing the store is as good as
knowing the passwords. This chapter requires the opposite property: what
is stored MUST NOT be usable to authenticate. See §2.11.

## 2.C.2 PAM

Pluggable Authentication Modules supplies the conversation shape: an
authority that asks for what it needs, a client that renders prompts
without understanding them, and several rounds where policy requires
them. That shape is why adding a credential type is a change to
authorities and not to every client, and it is adopted wholesale.

What is not adopted is PAM's stacking, in which a credential is offered
to each module in turn until one accepts. Trying each source *with the
password* hands every source the credentials of every other source's
users, including on typos. Where an authority federates, resolution MUST
select the answering party before any credential is collected.

PAM is also an in-process module system, and the objection under
*Diverged* applies to it equally.

## 2.C.3 POSIX name resolution

The identity-lookup half of this chapter answers the questions
`getpwnam`, `getpwuid`, `getgrnam`, `getgrgid` and `getgrouplist` ask,
and its field mask, reference encoding and one-round-trip requirement
are shaped by what those calls need in one go.

What is not adopted is the `passwd` record itself. GECOS is not carried
(§2.9), the flat `name:uid:gid` tuple is replaced by a SID plus a set of
requested fields, and the reserved-character rules (§2.15) exist
precisely because a name that reaches those callers ends up in a
colon-and-comma-separated line that nothing else validates.

Nor is the assumption that an absence is authoritative. A world-readable
file cannot be unreachable, so POSIX has no vocabulary for "the source
that would have known did not answer"; §2.18 exists because a federated
authority does, and reporting that as "no such user" memoises an outage
as a fact.

## 2.C.4 Design influences

**Descriptor-passing over ambient authority.** The token is transferred
as a file descriptor rather than named, so that possession of the
conversation is what confers it. There is no window in which a minted
token exists under a name another process could reach for.

**One conversation per connection.** The connection *is* the
conversation's identity, so no correlation identifier exists to be
forged or confused. This is deliberately unlike the identity channel of
§2.14, and unlike PSI (PSPU §2.7), both of which multiplex because
their connections are long-lived.

**Capability declaration over negotiation.** A client states what it can
render and the authority works within it, rather than the two agreeing a
version or a profile. That is what lets a credential type be added to
authorities without a flag day, and it is why the capability list is the
one enumeration whose unknown values are dropped rather than refused
(§2.7).
