75 lines
3.5 KiB
Markdown
75 lines
3.5 KiB
Markdown
# CLAUDE.md
|
|||
|
|
|
||
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||
|
|
|
||
|
|
## What this is
|
||
|
|
|
||
|
|
A minimal Rust reimplementation of the Cyrus `saslauthd` daemon. It listens on a Unix
|
||
|
|
socket, speaks the saslauthd wire protocol, and checks credentials against per-user
|
||
|
|
password files in a directory. In production it runs as PID 1 in an Alpine container
|
||
|
|
(`pobox_saslauthd`), and Cyrus IMAP / Exim authenticate through its socket.
|
||
|
|
|
||
|
|
## Commands
|
||
|
|
|
||
|
|
```sh
|
||
|
|
cargo build --workspace
|
||
|
|
cargo test --workspace # unit + integration tests
|
||
|
|
cargo test -p server --test shutdown_test # one integration test file
|
||
|
|
cargo test -p server it_checks_argon2 # one test by name
|
||
|
|
cargo clippy --workspace --all-targets # plain `cargo clippy` skips test code
|
||
|
|
cargo run -p server -- -f /tmp/saslauthd.sock -p ./test_passwords
|
||
|
|
RUST_LOG=info cargo run -p server # env_logger; info shows auth results + shutdown
|
||
|
|
cargo run -p client -- -f /tmp/saslauthd.sock bp some-rad-password # manual round-trip
|
||
|
|
docker build -t saslauthd . # musl static build, targets x86_64 only
|
||
|
|
```
|
||
|
|
|
||
|
|
Tests spawn the real `saslauthd` binary on a socket under the OS temp dir and signal it
|
||
|
|
with `kill`, so they are Unix-only and rely on `test_passwords/bp` at the repo root.
|
||
|
|
|
||
|
|
## Architecture
|
||
|
|
|
||
|
|
Cargo workspace (edition 2024) with three crates:
|
||
|
|
|
||
|
|
- `common` — the wire protocol shared by server and client. `io.rs` reads/writes
|
||
|
|
length-prefixed strings (u16 big-endian length + bytes). `request.rs` is the four-field
|
||
|
|
request (userid, password, service, realm) in that order.
|
||
|
|
- `server` — the `saslauthd` binary. `Server` owns the `UnixListener`, spawns one thread
|
||
|
|
per connection running `Handler`, which parses a `Request`, asks `PasswordDirectory`
|
||
|
|
whether it is valid, and writes back `OK` or `NO` as a length-prefixed string.
|
||
|
|
- `client` — `saslauthd_test_client`, a tiny CLI for exercising a running server.
|
||
|
|
|
||
|
|
Top-level `tests/` holds the `common` crate's protocol tests; `server/tests/` holds the
|
||
|
|
process-level shutdown tests.
|
||
|
|
|
||
|
|
### Authentication model (`server/src/repository.rs`)
|
||
|
|
|
||
|
|
The password directory contains one file per userid. Blank lines and `#` comments are
|
||
|
|
ignored. Each remaining line is either:
|
||
|
|
|
||
|
|
- a plain-text password, matched exactly, or
|
||
|
|
- `[key]<argon2 PHC hash>`, matched only when the client sends the password as
|
||
|
|
`[key]<plaintext>` and the plaintext verifies against the hash for that key.
|
||
|
|
|
||
|
|
The client's password prefix decides which path is taken. Userids containing `/` are
|
||
|
|
rejected before touching the filesystem. `service` and `realm` are read but ignored.
|
||
|
|
|
||
|
|
### Server lifecycle (`server/src/server.rs`)
|
||
|
|
|
||
|
|
Startup removes any stale file at the socket path, binds, and chmods the socket 0777.
|
||
|
|
The accept loop is non-blocking and polls a flag set by `signal-hook` for SIGTERM/SIGINT.
|
||
|
|
On a signal it stops accepting, waits up to 3 s for in-flight handler threads, then
|
||
|
|
`Drop` removes the socket file. Exit status is 0. This matters because as container
|
||
|
|
PID 1 the process gets no default signal handling; without it `docker stop` hangs for
|
||
|
|
10 s and host reboots stall.
|
||
|
|
|
||
|
|
CLI options live in `options.rs` as a `LazyLock<Opt>` (clap derive) and are read from
|
||
|
|
anywhere via `OPTIONS`.
|
||
|
|
|
||
|
|
## Constraints
|
||
|
|
|
||
|
|
- Do not change the wire protocol or the `--socket-name` / `--password-dir` flags;
|
||
|
|
Cyrus and Exim depend on both.
|
||
|
|
- The Dockerfile hardcodes `x86_64-unknown-linux-musl`. On an arm64 Mac the cross-build
|
||
|
|
segfaults under qemu; verify container behaviour by substituting
|
||
|
|
`aarch64-unknown-linux-musl` in a copy of the Dockerfile instead.
|