From 1749d9c08e3fe4f9b5cd9e70d9df73b81136cd7d Mon Sep 17 00:00:00 2001 From: Barry Pederson Date: Sat, 12 Sep 2026 13:08:11 -0700 Subject: [PATCH] Add CLAUDE.md with build commands and architecture notes Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Kmrgkpp9YBJ6AMaWYPPZuD --- CLAUDE.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..fec772b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,74 @@ +# 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]`, matched only when the client sends the password as + `[key]` 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.