mirror of
https://gitlab.com/openconnect/ocserv.git
synced 2026-08-09 09:51:49 +08:00
requirements: enhanced with general implementation requirements
Signed-off-by: Nikos Mavrogiannopoulos <n.mavrogiannopoulos@gmail.com>
This commit is contained in:
@@ -306,16 +306,18 @@ Do not edit the generated `*.pb-c.c` / `*.pb-c.h` files by hand.
|
|||||||
|
|
||||||
### Canonical Technology Choices
|
### Canonical Technology Choices
|
||||||
|
|
||||||
One tool for each job. These decisions are not open for per-feature debate — departing
|
One tool for each job. These decisions are requirements, not preferences — the
|
||||||
from any of them requires a design-discussion issue and explicit maintainer approval.
|
normative form with acceptance criteria is in
|
||||||
|
`doc/requirements/internal/general.md`. Departing from any of them requires a
|
||||||
|
design-discussion issue and explicit maintainer approval.
|
||||||
|
|
||||||
| Concern | Tool | Rule |
|
| Concern | Tool | Requirement |
|
||||||
|---------|------|------|
|
|---------|------|-------------|
|
||||||
| Memory management | talloc | Use `talloc_zero`, `talloc_strdup`, `talloc_array`, etc. everywhere. **Exception:** use `gnutls_malloc()` / `gnutls_free()` only for memory whose lifetime GnuTLS owns — never mix the two for the same allocation. Check every return before use. Error paths: `goto cleanup` with a single freeing label. |
|
| Memory management | talloc | Use `talloc_zero`, `talloc_strdup`, `talloc_array`, etc. everywhere. Exception: `gnutls_malloc()`/`gnutls_free()` only for GnuTLS-owned memory — never mix the two for the same allocation. Error paths: `goto cleanup` with a single freeing label. See **REQ-GEN-TECH-001**. |
|
||||||
| Cryptography | GnuTLS / nettle | No OpenSSL. Call GnuTLS through `src/tlslib.c` wrappers — not directly from worker or main code. |
|
| Cryptography | GnuTLS / nettle | No OpenSSL. Call GnuTLS through `src/tlslib.c` wrappers — not directly from worker or main code. See **REQ-GEN-TECH-002**. |
|
||||||
| IPC serialization | protobuf-c | All cross-process messages are defined in `src/ipc.proto` and `src/ctl.proto`. Regenerate C bindings with `protoc-c` after any `.proto` edit; never hand-edit generated files. |
|
| IPC serialization | protobuf-c | All cross-process messages are defined in `src/ipc.proto` and `src/ctl.proto`. Regenerate C bindings with `protoc-c` after any `.proto` edit; never hand-edit generated files. See **REQ-GEN-TECH-003**. |
|
||||||
| Configuration parsing | inih (INI) | Flat `key = value` pairs; bracketed sub-sections for per-module options (`auth = pam[...]`). No new structured sub-formats (no JSON, YAML, or additional config files for things expressible as key=value). |
|
| Configuration parsing | inih (INI) | Flat `key = value` pairs; bracketed sub-sections for per-module options (`auth = pam[...]`). No new structured sub-formats (no JSON, YAML, or additional config files for things expressible as key=value). See **REQ-GEN-TECH-004**. |
|
||||||
| Utility constructs | CCAN (`src/ccan/`) | Check here before writing a new helper or adding a dependency. CCAN provides hash tables, linked lists, string utilities, and more as copy-in modules with no extra build overhead. To bring in a new CCAN module, copy it from https://github.com/rustyrussell/ccan into `src/ccan/` and add it to the build. |
|
| New external dependency | requires approval | Check `src/ccan/` first; consider a trivial inline implementation; if neither suffices, open a design-discussion issue before adding. See **REQ-GEN-TECH-005**. |
|
||||||
|
|
||||||
### Design Principles
|
### Design Principles
|
||||||
|
|
||||||
@@ -325,15 +327,11 @@ cross-module state, callbacks, and "utility" files are a design smell — if you
|
|||||||
requires them, reconsider the module boundary first. When complexity must be hidden,
|
requires them, reconsider the module boundary first. When complexity must be hidden,
|
||||||
hide it inside a module with a clean header interface; do not scatter it.
|
hide it inside a module with a clean header interface; do not scatter it.
|
||||||
|
|
||||||
**Resist dependency growth.** Before adding anything external: (1) check `src/ccan/`
|
**Resist dependency growth.** See **REQ-GEN-TECH-005** for the normative rule.
|
||||||
first — it likely has what you need; (2) consider a trivial inline implementation over
|
In short: check `src/ccan/` first, then consider a trivial inline implementation;
|
||||||
a new dependency; (3) if a new external library is truly necessary, open a design
|
only then open a design-discussion issue for a new external library.
|
||||||
discussion issue. Every dependency is a build, packaging, and security-audit cost paid
|
|
||||||
forever.
|
|
||||||
|
|
||||||
**Configuration stays in INI.** Do not introduce embedded structured formats for things
|
**Configuration stays in INI.** See **REQ-GEN-TECH-004** for the normative rule.
|
||||||
expressible as flat key=value pairs. Complexity in configuration is a cost paid by every
|
|
||||||
administrator forever.
|
|
||||||
|
|
||||||
**Explicit over implicit.** No auto-discovery, no runtime plugin loading, no silent
|
**Explicit over implicit.** No auto-discovery, no runtime plugin loading, no silent
|
||||||
defaults that change behavior. Every configurable behavior must be expressible in
|
defaults that change behavior. Every configurable behavior must be expressible in
|
||||||
@@ -341,51 +339,27 @@ defaults that change behavior. Every configurable behavior must be expressible i
|
|||||||
|
|
||||||
### Code Style
|
### Code Style
|
||||||
|
|
||||||
- C99 standard
|
See **REQ-GEN-STYLE-001** and **REQ-GEN-STYLE-002** for the normative rules.
|
||||||
- Linux kernel coding style: tabs, 8-space tab width, 80-column limit
|
In brief:
|
||||||
- Format check: `clang-format --dry-run -Werror <file>` (run on all files under `src/` and `tests/`)
|
|
||||||
|
- C99 standard; Linux kernel coding style (tabs, 8-space tab width, 80-column limit)
|
||||||
|
- `clang-format --dry-run -Werror` must pass on all files under `src/` and `tests/`
|
||||||
- Header guards: `#ifndef FILENAME_H` / `#define FILENAME_H` / `#endif /* FILENAME_H */`
|
- Header guards: `#ifndef FILENAME_H` / `#define FILENAME_H` / `#endif /* FILENAME_H */`
|
||||||
- **Comments:** Prefer self-documenting code — meaningful names and short functions
|
- `#ifdef` in function bodies: ≤ 1 nesting level, ≤ 5 lines per branch; optional
|
||||||
that do one thing. Add a comment only when the *why* is non-obvious: a hidden
|
features use the stub pattern; `#endif` always annotated
|
||||||
constraint, a protocol expectation, or a workaround. Do not comment what the
|
- **Comments:** Prefer self-documenting code. Add a comment only when the *why* is
|
||||||
code does; well-named identifiers already do that.
|
non-obvious: a hidden constraint, a protocol expectation, or a workaround. Do not
|
||||||
|
comment what the code does; well-named identifiers already do that.
|
||||||
### Preprocessor Conditionals
|
|
||||||
|
|
||||||
Deeply nested or long `#ifdef` chains obscure the actual code flow and make review
|
|
||||||
difficult. The rules:
|
|
||||||
|
|
||||||
- **`#ifdef` in a function body**: one level of nesting maximum, at most 5 lines per
|
|
||||||
branch. If the block is longer, extract it into a separately-defined function.
|
|
||||||
- **Stub pattern for optional features**: define a no-op (or error-returning) stub in
|
|
||||||
the header for the disabled case, so call sites need no `#ifdef` at all:
|
|
||||||
```c
|
|
||||||
#ifdef HAVE_SECCOMP
|
|
||||||
int worker_apply_seccomp_filter(worker_st *ws);
|
|
||||||
#else
|
|
||||||
static inline int worker_apply_seccomp_filter(worker_st *ws) { return 0; }
|
|
||||||
#endif
|
|
||||||
```
|
|
||||||
- **Significant feature code** (more than one function) belongs in its own `.c` file
|
|
||||||
included or excluded by the build system — not behind inline conditionals.
|
|
||||||
The existing `src/auth/` and `src/acct/` structure is the model to follow.
|
|
||||||
- Always annotate `#endif` with the condition it closes: `#endif /* HAVE_SECCOMP */`.
|
|
||||||
|
|
||||||
### Testing New Functionality
|
### Testing New Functionality
|
||||||
|
|
||||||
Every new feature or bug fix is incomplete without tests:
|
See **REQ-GEN-TEST-001** for the normative rule. In brief:
|
||||||
|
|
||||||
- Write a **positive test** (correct behavior when the feature is exercised) and a
|
- Write a **positive test** and a **negative test** for every feature or fix.
|
||||||
**negative test** (correct rejection of bad input or error conditions).
|
- For security-relevant code (auth, cookies, IPC validation) write the negative test first.
|
||||||
- For security-relevant code (auth, cookies, IPC validation) the negative test is the
|
- For **bug fixes**, confirm the reproducing test fails before applying the fix.
|
||||||
more important of the two — write it first.
|
- Tests must be **self-diagnosing** — failure explainable from output alone; shell tests
|
||||||
- For **bug fixes**, write the test that reproduces the bug and confirm it fails *before*
|
print `"FAIL: expected X, got Y"`; C unit tests print the failing condition and values.
|
||||||
applying the fix. A test written after the fix cannot prove it is meaningful.
|
|
||||||
- **Tests must be self-diagnosing.** A failure must be explainable from the test output
|
|
||||||
alone — no silent exit-code-only failures that require local reproduction to understand.
|
|
||||||
Shell tests must print what they were testing and why it failed (e.g. `echo "FAIL: expected
|
|
||||||
X, got Y"`). C unit tests must print the failing condition and relevant values before
|
|
||||||
returning non-zero. Reviewers will reject tests whose failures are opaque.
|
|
||||||
- Register all new tests in `tests/meson.build`.
|
- Register all new tests in `tests/meson.build`.
|
||||||
|
|
||||||
### Adding Configuration Options
|
### Adding Configuration Options
|
||||||
@@ -405,11 +379,10 @@ See `src/auth/plain.c` or `src/auth/pam.c` for reference.
|
|||||||
|
|
||||||
### Platform Portability
|
### Platform Portability
|
||||||
|
|
||||||
ocserv is a Linux service. BSD (FreeBSD, OpenBSD) compatibility is maintained on a
|
See **REQ-GEN-COMPAT-001** for the normative rule. In brief: Linux is the primary
|
||||||
best-effort basis: patches should not gratuitously break BSD, but Linux-only features
|
target; BSD is best-effort. Guard Linux-specific code with `#ifdef __linux__`. Note
|
||||||
are accepted. When adding Linux-specific code, use `#ifdef __linux__` so BSD builds
|
in `doc/ocserv.8.md` any option whose behavior differs on BSD (e.g. no live reload
|
||||||
continue to compile. On BSD, the absence of procfs means configuration changes
|
without procfs).
|
||||||
require a server restart — document this if your change is affected by it.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ the file.
|
|||||||
|
|
||||||
| Document | ID prefix | Process(es) | Sources |
|
| Document | ID prefix | Process(es) | Sources |
|
||||||
|----------|-----------|-------------|---------|
|
|----------|-----------|-------------|---------|
|
||||||
|
| `internal/general.md` | `REQ-GEN-` | all (policy) | `AGENTS.md`, `doc/ocserv.8.md`, `doc/sample.config` |
|
||||||
| `internal/ipc.md` | `REQ-IPC-` | all (cross-process) | `src/ipc.proto`, `src/ctl.proto`, `doc/design.md#ipc-communication*` |
|
| `internal/ipc.md` | `REQ-IPC-` | all (cross-process) | `src/ipc.proto`, `src/ctl.proto`, `doc/design.md#ipc-communication*` |
|
||||||
| `internal/config.md` | `REQ-CONFIG-` | all (cross-process) | `src/config.c`, `src/config-ports.c`, `src/config-kkdcp.c`, `src/subconfig.c`, `src/sup-config/file.c`, `src/cfg.proto`, `src/vpn.h`, `src/vhost.h`, `doc/sample.config`, `tests/check-config-scope.py`, `tests/config-inherit.c` |
|
| `internal/config.md` | `REQ-CONFIG-` | all (cross-process) | `src/config.c`, `src/config-ports.c`, `src/config-kkdcp.c`, `src/subconfig.c`, `src/sup-config/file.c`, `src/cfg.proto`, `src/vpn.h`, `src/vhost.h`, `doc/sample.config`, `tests/check-config-scope.py`, `tests/config-inherit.c` |
|
||||||
| `internal/sec-mod.md` | `REQ-SECMOD-` | sec-mod | `src/sec-mod*.c`, `src/sec-mod-auth.h`, `src/auth/*`, `src/acct/*` |
|
| `internal/sec-mod.md` | `REQ-SECMOD-` | sec-mod | `src/sec-mod*.c`, `src/sec-mod-auth.h`, `src/auth/*`, `src/acct/*` |
|
||||||
@@ -58,7 +59,10 @@ REQ-<PREFIX><CATEGORY>-<NNN>
|
|||||||
- `<PREFIX>` identifies the document (table above); `IPC` has no
|
- `<PREFIX>` identifies the document (table above); `IPC` has no
|
||||||
further category — IDs are `REQ-IPC-NNN`, grouped by message name in
|
further category — IDs are `REQ-IPC-NNN`, grouped by message name in
|
||||||
the document body.
|
the document body.
|
||||||
- `<CATEGORY>` for `internal/*` documents uses the tags from
|
- `<CATEGORY>` for `internal/general.md` uses: `SEC`, `TECH`, `STYLE`,
|
||||||
|
`TEST`, `COMPAT` (cross-cutting policy categories; see that file's
|
||||||
|
frontmatter for definitions).
|
||||||
|
- `<CATEGORY>` for other `internal/*` documents uses the tags from
|
||||||
`requirements-from-implementation.md`: `INIT`, `AUTH`, `ACCT`,
|
`requirements-from-implementation.md`: `INIT`, `AUTH`, `ACCT`,
|
||||||
`SESSION`, `CFG`, `NET`, `SEC`, `ERR`, `TEARDOWN`.
|
`SESSION`, `CFG`, `NET`, `SEC`, `ERR`, `TEARDOWN`.
|
||||||
- `<CATEGORY>` for `protocol/unified.md` uses the tags from
|
- `<CATEGORY>` for `protocol/unified.md` uses the tags from
|
||||||
|
|||||||
@@ -0,0 +1,410 @@
|
|||||||
|
---
|
||||||
|
title: general and cross-cutting requirements
|
||||||
|
generator: requirements-elicitation
|
||||||
|
process: all
|
||||||
|
id-prefix: REQ-GEN
|
||||||
|
categories:
|
||||||
|
SEC: security and privilege invariants
|
||||||
|
TECH: canonical technology stack constraints
|
||||||
|
STYLE: code style and structure rules enforced by CI or review
|
||||||
|
TEST: test quality requirements
|
||||||
|
COMPAT: platform portability policy
|
||||||
|
sources:
|
||||||
|
- AGENTS.md
|
||||||
|
- doc/design.md
|
||||||
|
- doc/ocserv.8.md
|
||||||
|
- doc/sample.config
|
||||||
|
- .gitlab-ci.yml
|
||||||
|
---
|
||||||
|
|
||||||
|
# General and Cross-Cutting Requirements
|
||||||
|
|
||||||
|
This document captures requirements that span all three processes (main,
|
||||||
|
sec-mod, worker) or that encode project-wide policy — constraints on how
|
||||||
|
ocserv may evolve that cannot be attributed to a single subsystem or source
|
||||||
|
file.
|
||||||
|
|
||||||
|
Process-specific behavioral requirements belong in their own documents
|
||||||
|
(`internal/config.md`, `internal/authentication.md`, etc.), even when they
|
||||||
|
carry security implications. A requirement belongs here only when it applies
|
||||||
|
regardless of which subsystem is being changed.
|
||||||
|
|
||||||
|
The `AGENTS.md` "Canonical Technology Choices" table cites these IDs as its
|
||||||
|
authoritative source; the table in that file is a quick-reference summary only.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SEC — security and privilege invariants
|
||||||
|
|
||||||
|
### REQ-GEN-SEC-001 — A semantic change to an operator-managed file MUST NOT silently grant more access than the previous semantics allowed
|
||||||
|
|
||||||
|
**Requirement:** Any change to the interpreted meaning of a value in an
|
||||||
|
operator-managed artifact — `ocserv.conf`, a plain-auth password file, an OTP
|
||||||
|
file, a supplemental per-user/per-group config file, or any other file whose
|
||||||
|
path appears in `ocserv.conf` and whose content is authored by the
|
||||||
|
administrator — MUST NOT, on upgrade, cause a client or user to gain access to
|
||||||
|
groups, VPN tunnels, or network resources that the administrator did not
|
||||||
|
explicitly grant under the previous version's documented semantics.
|
||||||
|
|
||||||
|
If such a meaning change is necessary, exactly one of the following MUST apply:
|
||||||
|
|
||||||
|
(a) **Explicit opt-in.** The administrator must take a deliberate action —
|
||||||
|
setting a new configuration option, or editing the affected file with
|
||||||
|
awareness of the new semantics — before the new, broader behavior takes
|
||||||
|
effect. The default for the new option MUST preserve the old behavior.
|
||||||
|
|
||||||
|
(b) **Startup rejection.** ocserv detects the old value at startup, logs a
|
||||||
|
clear error message identifying the affected option and file, and exits
|
||||||
|
with `EXIT_FAILURE`, forcing the administrator to migrate before the
|
||||||
|
server will accept connections.
|
||||||
|
|
||||||
|
Silent reinterpretation that expands access — a value that previously granted
|
||||||
|
no access or limited access and, after an upgrade, grants broader access
|
||||||
|
without any administrator action — MUST NOT be introduced, regardless of how
|
||||||
|
useful the new semantics would be for fresh deployments.
|
||||||
|
**Strength:** MUST NOT
|
||||||
|
**Status:** DERIVED
|
||||||
|
**Source:** AGENTS.md; `doc/ocserv.8.md`; `doc/sample.config`
|
||||||
|
**Acceptance:** [SEC] code-review — every proposed change that alters the
|
||||||
|
interpretation of an existing value in an operator-managed file MUST be
|
||||||
|
evaluated against this requirement before merging.
|
||||||
|
|
||||||
|
Concrete failing example (gitlab#748): changing the `*` entry in a plain-auth
|
||||||
|
passwd file's group field from "no effective group membership" (current
|
||||||
|
behavior: `break_group_list` discards single-character entries,
|
||||||
|
`src/auth/plain.c:119–135`) to "membership in all configured groups" would
|
||||||
|
violate this requirement — on upgrade, any user with `*` in their passwd entry
|
||||||
|
would silently gain access to all groups without any administrator action.
|
||||||
|
|
||||||
|
Compliant alternative for the same feature: introduce a new per-vhost option
|
||||||
|
(e.g. `plain-wildcard-group = true`, defaulting to `false`) that the
|
||||||
|
administrator must explicitly enable; `plain_auth_group()` checks this flag
|
||||||
|
before treating `*` as a wildcard.
|
||||||
|
**Links:** REQ-AUTH-AUTH-012, REQ-AUTH-AUTH-016, REQ-CONFIG-SEC-001
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### REQ-GEN-SEC-002 — The three-process privilege boundary MUST NOT be collapsed by any patch
|
||||||
|
|
||||||
|
**Requirement:** The division of responsibilities across main (root, listeners
|
||||||
|
and IP allocation), sec-mod (root, authentication and private keys), and worker
|
||||||
|
(unprivileged + seccomp, per-client TLS/DTLS) is a security invariant. No
|
||||||
|
patch MAY:
|
||||||
|
|
||||||
|
(a) move credential handling, private-key operations, or session-state
|
||||||
|
storage into the worker process;
|
||||||
|
(b) grant a worker direct filesystem access to paths it resolves itself
|
||||||
|
(outside the root-created config snapshot, `REQ-CONFIG-SEC-001`);
|
||||||
|
(c) grant a worker a direct socket connection to sec-mod or main outside
|
||||||
|
the defined Unix-socket IPC channels (`src/ipc.proto`, `src/ctl.proto`);
|
||||||
|
(d) silently remove or bypass the seccomp filter that confines the worker.
|
||||||
|
|
||||||
|
If a proposed change requires crossing or relaxing a privilege boundary, it
|
||||||
|
MUST be flagged explicitly in the merge request for maintainer review and MUST
|
||||||
|
NOT be merged without explicit maintainer acknowledgment.
|
||||||
|
**Strength:** MUST NOT
|
||||||
|
**Status:** DERIVED
|
||||||
|
**Source:** AGENTS.md ("Hard rule"); `doc/design.md`; `src/main.c`,
|
||||||
|
`src/sec-mod.c`, `src/worker.c`
|
||||||
|
**Acceptance:** [SEC] code-review — confirm by call-graph inspection that no
|
||||||
|
function reachable from `src/worker-*.c` at runtime: (a) opens or reads a
|
||||||
|
credential file directly; (b) holds a private key in worker-local memory after
|
||||||
|
`AUTH_COOKIE_REP`; (c) connects a socket to sec-mod's Unix socket path directly
|
||||||
|
(all worker↔sec-mod traffic routes through main's forwarding). Any MR touching
|
||||||
|
the seccomp filter (`src/seccomp.c`) or adding a new IPC channel requires
|
||||||
|
maintainer sign-off citing this requirement.
|
||||||
|
**Links:** REQ-CONFIG-SEC-001, REQ-IPC-010, REQ-AUTH-AUTH-007,
|
||||||
|
REQ-GEN-TECH-003
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TECH — canonical technology stack constraints
|
||||||
|
|
||||||
|
### REQ-GEN-TECH-001 — talloc is the sole memory allocator; GnuTLS-owned memory is the only exception
|
||||||
|
|
||||||
|
**Requirement:** All heap allocations in ocserv source code MUST use talloc
|
||||||
|
functions (`talloc_zero`, `talloc_strdup`, `talloc_array`, etc.) with an
|
||||||
|
explicit talloc parent context. The plain C allocators `malloc`, `calloc`,
|
||||||
|
`realloc`, and `free` MUST NOT appear in new code under `src/` (outside vendored
|
||||||
|
subtrees: `src/ccan/`, `src/inih/`, `src/llhttp/`, `src/pcl/`,
|
||||||
|
`src/protobuf/`, `src/gnulib/`).
|
||||||
|
|
||||||
|
The sole exception: memory whose lifetime GnuTLS manages internally MUST be
|
||||||
|
allocated with `gnutls_malloc()` and freed with `gnutls_free()`. The two
|
||||||
|
allocator domains MUST NOT be mixed for the same logical allocation — a buffer
|
||||||
|
allocated with `talloc_*` MUST NOT be passed to `gnutls_free()`, and vice versa.
|
||||||
|
|
||||||
|
Error paths that must release multiple allocations MUST use a `goto cleanup`
|
||||||
|
pattern with a single freeing label rather than duplicating free calls on every
|
||||||
|
error branch.
|
||||||
|
**Strength:** MUST / MUST NOT
|
||||||
|
**Status:** DERIVED
|
||||||
|
**Source:** AGENTS.md (Canonical Technology Choices — Memory management);
|
||||||
|
pervasive in `src/*.c`
|
||||||
|
**Acceptance:** negative, code-review — `grep -rn '\b\(malloc\|calloc\|realloc\|free\)\s*(` src/` excluding vendored subtrees; any hit in non-vendored code is a violation. Confirm every new function that returns an allocated pointer documents its talloc parent in a comment or by convention (e.g. allocated on the passed-in `pool` argument).
|
||||||
|
**Links:** REQ-GEN-TECH-002
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### REQ-GEN-TECH-002 — GnuTLS is the sole cryptographic library; no OpenSSL; TLS calls go through `src/tlslib.c`
|
||||||
|
|
||||||
|
**Requirement:** All TLS, DTLS, certificate, and cryptographic operations MUST
|
||||||
|
use GnuTLS (or its companion library nettle for low-level primitives). OpenSSL
|
||||||
|
and any other TLS/crypto library MUST NOT be introduced as a dependency.
|
||||||
|
|
||||||
|
Code in `src/worker-*.c` and `src/main-*.c` MUST NOT call GnuTLS API functions
|
||||||
|
directly — all GnuTLS interaction from those files MUST go through the wrapper
|
||||||
|
functions in `src/tlslib.c` and `src/tlslib.h`. Direct GnuTLS calls are
|
||||||
|
permitted only within `src/tlslib.c` itself and in `src/sec-mod-*.c` for
|
||||||
|
private-key operations that are inherently sec-mod's responsibility.
|
||||||
|
**Strength:** MUST / MUST NOT
|
||||||
|
**Status:** DERIVED
|
||||||
|
**Source:** AGENTS.md (Canonical Technology Choices — Cryptography);
|
||||||
|
`src/tlslib.c`, `src/tlslib.h`
|
||||||
|
**Acceptance:** negative, code-review — `grep -rn 'SSL_\|EVP_\|openssl\|OPENSSL' src/` (excluding vendored subtrees); any hit is a violation. `grep -rn 'gnutls_' src/worker-*.c src/main-*.c`; any hit that is not a call to a `tlslib.h`-declared wrapper is a violation.
|
||||||
|
**Links:** REQ-GEN-TECH-001
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### REQ-GEN-TECH-003 — All cross-process messages MUST be defined in `.proto` files; generated bindings MUST NOT be hand-edited
|
||||||
|
|
||||||
|
**Requirement:** Every message that crosses a process boundary (worker ↔ main,
|
||||||
|
worker ↔ sec-mod, main ↔ sec-mod, `occtl` ↔ main) MUST be defined in
|
||||||
|
`src/ipc.proto` or `src/ctl.proto` using the protobuf-c schema language. No
|
||||||
|
ad-hoc binary format, fixed-layout struct, or text protocol MAY be introduced
|
||||||
|
for inter-process communication.
|
||||||
|
|
||||||
|
After any edit to a `.proto` file, the C bindings MUST be regenerated with:
|
||||||
|
|
||||||
|
```
|
||||||
|
protoc-c --c_out=src/ src/ipc.proto
|
||||||
|
protoc-c --c_out=src/ src/ctl.proto
|
||||||
|
```
|
||||||
|
|
||||||
|
The generated files `src/ipc.pb-c.c`, `src/ipc.pb-c.h`, `src/ctl.pb-c.c`,
|
||||||
|
and `src/ctl.pb-c.h` MUST NOT be edited by hand. Any manual edit will be
|
||||||
|
silently overwritten by the next `protoc-c` invocation and invalidates the
|
||||||
|
requirement that the `.proto` file is the authoritative source of the message
|
||||||
|
schema.
|
||||||
|
**Strength:** MUST / MUST NOT
|
||||||
|
**Status:** DERIVED
|
||||||
|
**Source:** AGENTS.md (Canonical Technology Choices — IPC serialization;
|
||||||
|
Modifying IPC); `src/ipc.proto`, `src/ctl.proto`
|
||||||
|
**Acceptance:** negative, code-review — confirm that `src/ipc.pb-c.*` and
|
||||||
|
`src/ctl.pb-c.*` are not modified in any MR that does not also modify the
|
||||||
|
corresponding `.proto` file. Positive — after a `.proto` edit, run `protoc-c`
|
||||||
|
and confirm `ninja -C build` succeeds and `git diff src/*.pb-c.*` matches the
|
||||||
|
`protoc-c` output exactly.
|
||||||
|
**Links:** REQ-GEN-SEC-002, REQ-IPC-010
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### REQ-GEN-TECH-004 — Configuration MUST use INI `key = value` format; no structured sub-formats
|
||||||
|
|
||||||
|
**Requirement:** All administrator-facing configuration options MUST be
|
||||||
|
expressible as flat INI `key = value` pairs or as the existing bracketed
|
||||||
|
sub-section syntax (`auth = module[key=value, ...]`) parsed by `src/inih/` and
|
||||||
|
`src/subconfig.c`. No new structured format — JSON objects, YAML documents,
|
||||||
|
TOML tables, XML, or any multi-line structured syntax — MAY be introduced for
|
||||||
|
options that are expressible as key=value pairs.
|
||||||
|
|
||||||
|
A new configuration file format (separate from `ocserv.conf`) MAY only be
|
||||||
|
introduced for data that is inherently structured and cannot be expressed as
|
||||||
|
key=value (e.g. the OIDC JSON configuration, which is defined by an external
|
||||||
|
standard). In that case the new format MUST be documented in `doc/ocserv.8.md`
|
||||||
|
and `doc/sample.config`, and its introduction requires a design-discussion
|
||||||
|
issue.
|
||||||
|
**Strength:** MUST / MUST NOT
|
||||||
|
**Status:** DERIVED
|
||||||
|
**Source:** AGENTS.md (Canonical Technology Choices — Configuration parsing;
|
||||||
|
Design Principles — Configuration stays in INI); `src/config.c`,
|
||||||
|
`src/subconfig.c`, `src/inih/`
|
||||||
|
**Acceptance:** code-review — any MR that adds a new `ini_parse()`-bypassing
|
||||||
|
config path or introduces a call to a JSON/YAML parser for a new option
|
||||||
|
(outside the existing OIDC path) is a violation. Positive — every new option
|
||||||
|
added in an MR appears in `doc/sample.config` as a `key = value` line.
|
||||||
|
**Links:** REQ-CONFIG-CFG-001, REQ-GEN-SEC-001
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### REQ-GEN-TECH-005 — A new external library dependency requires a design-discussion issue and explicit maintainer approval
|
||||||
|
|
||||||
|
**Requirement:** Before introducing a new external library (i.e. a new
|
||||||
|
`dependency()` call in `meson.build` that is not already present), the
|
||||||
|
contributor MUST:
|
||||||
|
|
||||||
|
(a) check `src/ccan/` — if the required functionality exists as a CCAN
|
||||||
|
module, copy it in and use it rather than adding an external dependency;
|
||||||
|
(b) consider whether a small inline implementation (≤ ~50 lines) would
|
||||||
|
suffice — if so, prefer it over a new dependency;
|
||||||
|
(c) if neither (a) nor (b) applies, open a design-discussion issue documenting
|
||||||
|
the proposed dependency, why existing options are insufficient, and the
|
||||||
|
build/packaging/security-audit impact; the MR MUST NOT be merged until a
|
||||||
|
maintainer explicitly approves the dependency in that issue.
|
||||||
|
|
||||||
|
Every external dependency is a permanent build, packaging, and security-audit
|
||||||
|
cost. The bar for approval is that no existing in-tree alternative exists and
|
||||||
|
the dependency is actively maintained and widely packaged.
|
||||||
|
**Strength:** MUST
|
||||||
|
**Status:** DERIVED
|
||||||
|
**Source:** AGENTS.md (Canonical Technology Choices — Utility constructs;
|
||||||
|
Design Principles — Resist dependency growth)
|
||||||
|
**Acceptance:** code-review — any MR that adds a `dependency()` call not
|
||||||
|
present in the base branch MUST cite a design-discussion issue number in the MR
|
||||||
|
description. The absence of such a citation is grounds for blocking the MR.
|
||||||
|
**Links:** REQ-GEN-TECH-001, REQ-GEN-TECH-002, REQ-GEN-TECH-003,
|
||||||
|
REQ-GEN-TECH-004
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STYLE — code style and structure rules
|
||||||
|
|
||||||
|
### REQ-GEN-STYLE-001 — Source files MUST conform to C99, Linux kernel style, and pass `clang-format`; header guards MUST use the canonical form
|
||||||
|
|
||||||
|
**Requirement:** All source files under `src/` and `tests/` (excluding vendored
|
||||||
|
subtrees: `src/ccan/`, `src/inih/`, `src/llhttp/`, `src/pcl/`, `src/protobuf/`,
|
||||||
|
`src/gnulib/`, and generated `*.pb-c.*` files) MUST:
|
||||||
|
|
||||||
|
(a) **C standard**: use C99 features only. C11 or later features (e.g.
|
||||||
|
`_Atomic`, `_Generic`, `_Static_assert` without a C99-compatible wrapper,
|
||||||
|
anonymous struct/union not as a GCC extension) MUST NOT be used.
|
||||||
|
(b) **Coding style**: follow Linux kernel style — tabs for indentation,
|
||||||
|
8-space tab width, 80-column line limit.
|
||||||
|
(c) **Format tool**: pass `clang-format --dry-run -Werror` without errors.
|
||||||
|
CI enforces this on every MR.
|
||||||
|
(d) **Header guards**: every `.h` file MUST use the form:
|
||||||
|
```c
|
||||||
|
#ifndef FILENAME_H
|
||||||
|
#define FILENAME_H
|
||||||
|
/* ... */
|
||||||
|
#endif /* FILENAME_H */
|
||||||
|
```
|
||||||
|
where `FILENAME_H` matches the file's base name uppercased with `.`
|
||||||
|
replaced by `_`.
|
||||||
|
**Strength:** MUST / MUST NOT
|
||||||
|
**Status:** DERIVED
|
||||||
|
**Source:** AGENTS.md (Code Style); `.gitlab-ci.yml` (clang-format CI job)
|
||||||
|
**Acceptance:** CI — the `clang-format` CI job runs `clang-format --dry-run
|
||||||
|
-Werror` on all non-vendored, non-generated files under `src/` and `tests/`
|
||||||
|
and fails the pipeline on any formatting violation. C standard and header-guard
|
||||||
|
conformance are verified by code review; no automated check currently exists
|
||||||
|
for C99-only compliance beyond what the compiler warns on with `-std=c99 -Wall`.
|
||||||
|
**Links:** REQ-GEN-STYLE-002
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### REQ-GEN-STYLE-002 — `#ifdef` blocks in function bodies MUST be shallow and short; optional features MUST use the stub pattern
|
||||||
|
|
||||||
|
**Requirement:** Preprocessor conditionals inside function bodies obscure
|
||||||
|
control flow and make review difficult. The following rules apply to all source
|
||||||
|
files under `src/` and `tests/` (excluding vendored subtrees):
|
||||||
|
|
||||||
|
(a) **Depth**: `#ifdef`/`#if` blocks inside a function body MUST NOT be
|
||||||
|
nested more than one level deep. A conditional that needs deeper nesting
|
||||||
|
MUST be extracted into a separately-named function.
|
||||||
|
(b) **Length**: each branch of an in-function `#ifdef` MUST contain at most
|
||||||
|
5 lines. Longer branches MUST be extracted into separately-named
|
||||||
|
functions.
|
||||||
|
(c) **Stub pattern**: when a feature is conditionally compiled, the header
|
||||||
|
MUST define a no-op (or safe error-returning) inline stub for the disabled
|
||||||
|
case, so call sites require no `#ifdef` at all. Example:
|
||||||
|
```c
|
||||||
|
#ifdef HAVE_FEATURE
|
||||||
|
int feature_init(worker_st *ws);
|
||||||
|
#else
|
||||||
|
static inline int feature_init(worker_st *ws) { return 0; }
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
(d) **Significant feature code**: any optional feature spanning more than one
|
||||||
|
function MUST live in its own `.c` file included or excluded by the build
|
||||||
|
system (`meson.build`), following the pattern of `src/auth/` and
|
||||||
|
`src/acct/`. It MUST NOT be implemented as a large inline `#ifdef` block
|
||||||
|
in an existing file.
|
||||||
|
(e) **`#endif` annotation**: every `#endif` MUST be annotated with the
|
||||||
|
condition it closes: `#endif /* HAVE_FEATURE */`.
|
||||||
|
**Strength:** MUST / MUST NOT
|
||||||
|
**Status:** DERIVED
|
||||||
|
**Source:** AGENTS.md (Preprocessor Conditionals); `src/auth/`,
|
||||||
|
`src/acct/` (model structure)
|
||||||
|
**Acceptance:** code-review — no automated tool currently enforces (a)–(e);
|
||||||
|
review is the gate. When an MR is submitted, the reviewer MUST check every
|
||||||
|
modified function for `#ifdef` depth and length violations and for missing
|
||||||
|
`#endif` annotations. The `src/auth/pam.c` / `src/auth/gssapi.c` files
|
||||||
|
illustrate compliant optional-feature structure.
|
||||||
|
**Links:** REQ-GEN-STYLE-001
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TEST — test quality requirements
|
||||||
|
|
||||||
|
### REQ-GEN-TEST-001 — Every feature or fix MUST have both a positive and a negative test; tests MUST be self-diagnosing and registered in `tests/meson.build`
|
||||||
|
|
||||||
|
**Requirement:** No feature addition or bug fix is complete without tests.
|
||||||
|
The following apply to all tests in `tests/`:
|
||||||
|
|
||||||
|
(a) **Coverage**: every new feature or changed behavior MUST have at least
|
||||||
|
one positive test (the correct behavior is exercised and confirmed) and
|
||||||
|
at least one negative test (invalid input or error conditions are
|
||||||
|
correctly rejected). For `SEC`, `AUTH`, and `IPC` changes, the negative
|
||||||
|
test is the more important of the two and MUST be written first.
|
||||||
|
(b) **Bug-fix test order**: for a bug fix, the reproducing test MUST be
|
||||||
|
written and confirmed to fail against the unmodified code before the fix
|
||||||
|
is applied. A test written after the fix cannot demonstrate it is
|
||||||
|
meaningful.
|
||||||
|
(c) **Self-diagnosing output**: a test failure MUST be explainable from the
|
||||||
|
test's own output without local reproduction. Shell tests MUST print what
|
||||||
|
they were testing and why it failed (e.g.
|
||||||
|
`echo "FAIL: expected exit 0, got $ret"`). C unit tests MUST print the
|
||||||
|
failing condition and relevant values before returning non-zero. Tests
|
||||||
|
that exit non-zero with no diagnostic output MUST NOT be accepted.
|
||||||
|
(d) **Registration**: every new test MUST be registered in
|
||||||
|
`tests/meson.build`. An unregistered test is not run by CI and provides
|
||||||
|
no coverage guarantee.
|
||||||
|
**Strength:** MUST / MUST NOT
|
||||||
|
**Status:** DERIVED
|
||||||
|
**Source:** AGENTS.md (Testing New Functionality); `tests/meson.build`;
|
||||||
|
`tests/common.sh`
|
||||||
|
**Acceptance:** code-review — confirm for every MR that: (1) `tests/meson.build`
|
||||||
|
contains an entry for each new test file; (2) at least one test is a negative
|
||||||
|
case; (3) running the negative test against the pre-fix code produces a
|
||||||
|
non-zero exit and a human-readable failure message. CI runs all registered
|
||||||
|
tests on every MR; a passing CI run with no newly registered test for a
|
||||||
|
behavior change is itself a review finding.
|
||||||
|
**Links:** REQ-GEN-SEC-001, REQ-GEN-SEC-002
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## COMPAT — platform portability policy
|
||||||
|
|
||||||
|
### REQ-GEN-COMPAT-001 — Linux is the primary target; BSD compatibility is best-effort; Linux-specific code MUST be guarded with `#ifdef __linux__`
|
||||||
|
|
||||||
|
**Requirement:** ocserv is a Linux service. Linux-specific features (epoll,
|
||||||
|
TUN device ioctls, procfs, namespaces, seccomp, `SO_REUSEPORT`, etc.) are
|
||||||
|
accepted without requiring a BSD equivalent.
|
||||||
|
|
||||||
|
BSD (FreeBSD, OpenBSD) compatibility is maintained on a best-effort basis:
|
||||||
|
|
||||||
|
(a) Patches MUST NOT gratuitously break BSD builds. A change that breaks BSD
|
||||||
|
solely because the author did not check is a defect; a change that is
|
||||||
|
inherently Linux-only is accepted.
|
||||||
|
(b) Any code path that invokes a Linux-specific system call, header, or
|
||||||
|
kernel interface MUST be guarded with `#ifdef __linux__` (or a more
|
||||||
|
specific feature-test macro) so that BSD builds continue to compile,
|
||||||
|
even if the BSD path is a no-op stub.
|
||||||
|
(c) Features that rely on procfs (e.g. config snapshots, REQ-CONFIG-SEC-001)
|
||||||
|
are Linux-only by nature; on BSD, the absence of procfs means the
|
||||||
|
corresponding behavior is unavailable and the administrator MUST restart
|
||||||
|
the server to apply configuration changes. This difference MUST be noted
|
||||||
|
in `doc/ocserv.8.md` for any option where it applies.
|
||||||
|
(d) A feature MUST NOT be rejected from merging solely because it does not
|
||||||
|
work on BSD, provided (a) and (b) are satisfied.
|
||||||
|
**Strength:** MUST / MUST NOT
|
||||||
|
**Status:** DERIVED
|
||||||
|
**Source:** AGENTS.md (Project Overview — BSD; Platform Portability);
|
||||||
|
`src/config.c` (`PROC_FS_SUPPORTED`); `.gitlab-ci.yml`
|
||||||
|
**Acceptance:** CI — the cross-compilation job (i386/Debian) catches most
|
||||||
|
Linux-specific implicit assumptions. BSD-specific: code-review confirms that
|
||||||
|
any new `#include <linux/...>` or Linux-specific `ioctl`/syscall is inside
|
||||||
|
`#ifdef __linux__`. REQ-CONFIG-SEC-001's `[Divergence]` note documents the
|
||||||
|
accepted procfs gap as a model for how to document platform differences.
|
||||||
|
**Links:** REQ-CONFIG-SEC-001, REQ-GEN-STYLE-002
|
||||||
Reference in New Issue
Block a user