# pam_unix — Safe Rust Bindings for Linux-PAM / OpenPAM

[![License](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue)](#license)

**pam_unix** is a memory-safe Rust wrapper around the [Linux-PAM](http://www.linux-pam.org/)
and [OpenPAM](https://www.openpam.org/) authentication libraries. It provides a
type-safe, `unsafe`-audited API for both PAM **clients** (applications that
authenticate users) and PAM **modules** (shared objects loaded by the PAM stack).

> This project is a security-hardened fork of [1wilkens/pam](https://github.com/1wilkens/pam/)
> (originally by [Florian Wilkens](https://github.com/1wilkens)). All credit for the
> foundational design and initial implementation goes to the original author.

---

## Features

- **Client mode** (`client` feature, enabled by default) — authenticate users,
  open sessions, set credentials, and manage the PAM environment from
  application code.
- **Module mode** (`module` feature) — implement custom PAM modules in pure Rust
  via the `PamModule` trait and `export_pam_module!` macro.
- **Password-based conversation** — ready-to-use `PasswordConv` handler with
  automatic credential zeroization (`Zeroizing<String>`).
- **Custom conversations** — implement the `Conversation` trait for interactive
  prompts, MFA (smartcard / FIDO2 / U2F via `PAM_BINARY_PROMPT`), or silent
  authentication.
- **Typed convenience wrappers** — `set_rhost()`, `set_tty()`, `set_ruser()`,
  `set_fail_delay()` for security-critical PAM items that modules like
  `pam_access`, `pam_faillock`, and audit stacks depend on.
- **Bitwise-flag support** — `PamFlags` struct with `BitOr` for combining
  PAM flags (`SILENT | DISALLOW_NULL_AUTHTOK`).
- **OpenPAM compatibility** — tested on **Linux** (glibc), **FreeBSD**,
  **NetBSD**, **DragonFly BSD**, and **macOS**.
- **Panic safety** — all handler calls in the FFI callback are wrapped in
  `catch_unwind` to prevent process abort.
- **Thread‑safe environment** — global `ENV_LOCK` mutex serializes
  `std::env::set_var` calls from multiple `Client` instances.
- **Input sanitization** — Unicode control, formatting, line-separator, and
  bidi-override characters (Trojan Source, CVE-2021-42574) are stripped from
  environment values injected from NSS / LDAP / passwd.

---

## Quick Start

Add to `Cargo.toml`:

```toml
[dependencies]
pam_unix = "0.2"
```

### Password Authentication

```rust
use pam_unix::Client;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut client = Client::with_password("login")?;

    // Optional: set remote host and TTY for audit modules
    client.set_rhost("192.168.1.100")?;
    client.set_tty("/dev/pts/3")?;

    // Provide credentials
    client.conversation_mut().set_credentials("alice", "s3cret");

    // Authenticate and open a session
    client.authenticate()?;
    client.open_session()?;

    println!("Session opened for {}", client.get_user()?);
    Ok(())
}
```

### Custom Conversation (Interactive Prompt)

```rust
use pam_unix::{Client, Conversation};
use std::ffi::{CStr, CString};

struct InteractivePrompt;

impl Conversation for InteractivePrompt {
    fn prompt_echo(&mut self, msg: &CStr) -> Result<CString, ()> {
        print!("{} ", msg.to_str().unwrap());
        // read from stdin, etc.
        Ok(CString::new("alice").unwrap())
    }
    fn prompt_blind(&mut self, msg: &CStr) -> Result<CString, ()> {
        // read password without echo
        Ok(CString::new("s3cret").unwrap())
    }
    fn info(&mut self, msg: &CStr) {
        eprintln!("[INFO] {}", msg.to_string_lossy());
    }
    fn error(&mut self, _msg: &CStr) {
        eprintln!("[ERROR] authentication failed");
    }
}

let mut client = Client::with_conversation("login", InteractivePrompt)?;
client.authenticate()?;
```

### Brute-Force Mitigation

```rust
client.set_fail_delay(2_000_000); // 2-second random delay on failure
client.authenticate()?;
```

### Alternative PAM Configuration Directory (Linux only)

```rust
let client = Client::with_password_confdir("test_service", "/tmp/test_pam.d")?;
```

---

## Platform Support

| Platform | PAM Implementation | Status |
|----------|-------------------|--------|
| Linux (glibc) | Linux-PAM | Full support |
| FreeBSD | OpenPAM | Full support |
| NetBSD | OpenPAM | Full support |
| DragonFly BSD | OpenPAM | Full support |
| macOS | OpenPAM | Full support |
| Other Unix | Generic PAM | Fallback env cleanup |

---

## Security

This crate has undergone an extensive security audit (OWASP Top 10, CWE, NIST
checklist) covering memory safety, credential handling, input sanitization, race
conditions, and denial-of-service vectors. Key mitigations include:

| Area | Mitigation |
|------|-----------|
| Credential storage | `Zeroizing<String>` — all heap buffers zeroized on drop / overwrite |
| Intermediate copies | `as_bytes()` instead of `.clone()` in prompt handlers |
| FFI panic safety | `catch_unwind` wraps all handler calls in `converse()` |
| Dangling pointers | `PamContext` owned wrapper replaces unconstrained `&'a mut` reference |
| Environment races | Global `ENV_LOCK` mutex serializes `set_var` calls |
| Input injection | `sanitize_env_value()` strips Cc, Cf, Zl, Zp Unicode categories |
| Session leaks | Explicit `close_session()` on `Reinitialize_Cred` failure path |
| Memory leaks | All `strdup`'d strings freed on error path in `converse()` |
| OOB reads | Bounds checking on `num_msg`, `argc`, binary prompt sizes |
| Unknown msg styles | Rejected as `Conv_Err` instead of silently mapping to `Prompt_Echo_On` |

See the [audit report](https://github.com/DeltaForksLabs/pam_unix) for the full
list of findings and resolutions.

---

## Supported Rust Versions

The crate is built and tested against **Rust stable**, **beta**, and **nightly**
on Linux. It uses edition 2021 and requires Rust ≥ 1.60.

---

## Minimum Supported PAM Version

- **Linux-PAM** ≥ 1.3.0 (for `PAM_BINARY_PROMPT` and `pam_fail_delay`)
- **OpenPAM** ≥ 20170430 (FreeBSD 11.1+, macOS 10.13+)

---

## License

Licensed under either of

- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)

at your option.

### Credits

This project is a fork of **[1wilkens/pam](https://github.com/1wilkens/pam/)**
created by [Florian Wilkens](https://github.com/1wilkens). The original crate
established the foundational design for safe Rust PAM bindings. All credit for
the initial architecture, the `pam_enum` proc-macro, and the original `Client` /
`PamModule` APIs belongs to Florian.

The fork builds on that work with:
- Comprehensive security hardening (zeroization, input sanitization, panic safety)
- `PAM_BINARY_PROMPT` support for MFA / FIDO2 / smartcard modules
- Typed wrappers for `PAM_RHOST`, `PAM_TTY`, `PAM_RUSER`
- `pam_start_confdir()` for isolated testing
- `pam_fail_delay()` for brute-force mitigation
- OpenPAM multi-platform support (FreeBSD, NetBSD, DragonFly BSD, macOS)
- `PamFlags` bitwise-OR support for combined flags
- Extensive unit (65) and integration (25) test suites

### Contribution

Unless you explicitly state otherwise, any contribution intentionally
submitted for inclusion in the work by you, as defined in the Apache-2.0
license, shall be dual licensed as above, without any additional terms or
conditions.
