Mercurial > crates > nonstick
changeset 45:ce47901aab7a
Rename to “nonstick”, move to root, update docs and license.
- Renames the crate to “nonstick”.
- Moves the main library to the root of the repository.
- Removes the example PAM modules.
- Updates copyright information in LICENSE file.
- Updates the README.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Tue, 15 Apr 2025 00:50:23 -0400 |
parents | 50371046c61a |
children | 350579171e1f |
files | .github/workflows/build.yml .github/workflows/check.yml .gitignore Cargo.toml LICENSE README.md pam-http/Cargo.toml pam-http/Justfile pam-http/README.md pam-http/conf/http-auth pam-http/src/lib.rs pam-http/test.c pam-sober/Cargo.toml pam-sober/Justfile pam-sober/conf/sober-auth pam-sober/src/lib.rs pam-sober/test.c pam/Cargo.toml pam/src/constants.rs pam/src/conv.rs pam/src/items.rs pam/src/lib.rs pam/src/macros.rs pam/src/module.rs src/constants.rs src/conv.rs src/items.rs src/lib.rs src/macros.rs src/module.rs |
diffstat | 30 files changed, 763 insertions(+), 1242 deletions(-) [+] |
line wrap: on
line diff
--- a/.github/workflows/build.yml Sat Mar 08 19:29:46 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -on: - push: - branches: - - master - workflow_call: - -name: Build & publish - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - name: Install PAM dev files - run: sudo apt-get install -y libpam0g-dev - - name: Use cargo cache - uses: actions/cache@v2 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - name: Publish module - uses: katyo/publish-crates@v1 - with: - registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} - check-repo: ${{ github.event_name == 'push' }} - ignore-unpublished-changes: true - # Only publish pam module, as pam-http and pam-sober are example projects - path: pam - args: --package pam-bindings \ No newline at end of file
--- a/.github/workflows/check.yml Sat Mar 08 19:29:46 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -on: - pull_request: - branches: - - master - workflow_call: - -name: Check - -jobs: - check: - name: Check & Lint - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - - name: Install PAM dev files - run: sudo apt-get install -y libpam0g-dev - - - name: Use cargo cache - uses: actions/cache@v2 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - - name: Run cargo check - uses: actions-rs/cargo@v1 - with: - command: check - args: --manifest-path pam/Cargo.toml - - - name: Run cargo test - uses: actions-rs/cargo@v1 - with: - command: test - args: --manifest-path pam/Cargo.toml - - - name: Run cargo check on pam-http - uses: actions-rs/cargo@v1 - with: - command: check - args: --manifest-path pam-http/Cargo.toml - - - name: Run cargo check on pam-sober - uses: actions-rs/cargo@v1 - with: - command: check - args: --manifest-path pam-sober/Cargo.toml - - - name: Run cargo fmt - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --manifest-path pam/Cargo.toml --all --check - - - name: Run cargo clippy - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --manifest-path pam/Cargo.toml -- -D warnings \ No newline at end of file
--- a/.gitignore Sat Mar 08 19:29:46 2025 -0500 +++ b/.gitignore Tue Apr 15 00:50:23 2025 -0400 @@ -1,15 +1,3 @@ -# Compiled files -*.o -*.so -*.rlib -*.dll - -# Executables -*.exe - -# Generated by Cargo -target/ -Cargo.lock - -# Override top-level .gitignore in Misc repo -!src/tozny +/target +/.idea +/Cargo.lock \ No newline at end of file
--- a/Cargo.toml Sat Mar 08 19:29:46 2025 -0500 +++ b/Cargo.toml Tue Apr 15 00:50:23 2025 -0400 @@ -1,2 +1,15 @@ -[workspace] -members = ["pam", "pam-sober", "pam-http"] +[package] +name = "nonstick" +description = "PAM bindings for Rust" +version = "0.0.0-pre" +authors = ["Paul Fisher <paul@pfish.zone>", "Anthony Nowell <anowell@gmail.com>" ] +repository = "https://hg.pfish.zone/crates/nonstick/" +readme = "README.md" +keywords = ["pam", "ffi", "linux", "authentication"] +license = "MIT" + +[lib] +name = "pam" + +[dependencies] +libc = "0.2.97"
--- a/LICENSE Sat Mar 08 19:29:46 2025 -0500 +++ b/LICENSE Tue Apr 15 00:50:23 2025 -0400 @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 TOZNY +Copyright © 2015 TOZNY, 2015–2022 pam-rs contributors, 2025 Paul Fisher Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal
--- a/README.md Sat Mar 08 19:29:46 2025 -0500 +++ b/README.md Tue Apr 15 00:50:23 2025 -0400 @@ -1,25 +1,26 @@ -pam-rs -======== +# 🍳 nonstick -Rust interface to the pluggable authentication module framework (PAM). +Nonstick lets you use PAM (Pluggable Authentication Modules) from Rust without having to deal with icky unsafe code. + +## Status -The goal of this library is to provide a type-safe API that can be used to -interact with PAM. The library is incomplete - currently it supports a subset -of functions for use in a pam authentication module. A pam module is a shared -library that is invoked to authenticate a user, or to perform other functions. +It is currently very incomplete. +It only provides functionality for developing your own PAM authentication module (i.e., a backend that PAM calls to authenticate a user or do something similar). +At the moment, [Linux-PAM](https://github.com/linux-pam/linux-pam) is the only supported PAM implementation. -## 🌐 [pam-http](pam-http) +I will make an effort not to break APIs with development, but consider it alpha, pre-1.0 software. +While the code itself should be _secure_ and mostly safe, the API may not be completely stable. -An example of using pam-rs by performing HTTP basic access auth to authenticate users. +Goals include: -## 🍻 [pam-sober](pam-sober) +- Bindings for PAM clients. +- Support for non–Linux-PAM implementations. -If you aren't sober enough for basic math, you can't login! +## Credits -### Credits - -The contents of this repo are heavily borrowed from: +This is a direct fork of [Anthony Nowell](http://anowell.com/)’s [`pam-rs`/`pam-bindings` crate](https://crates.io/crates/pam-bindings). +`pam-rs` was in turn inspired by: -- [tozny/rust-pam](https://github.com/tozny/rust-pam) -- [ndenev/pam_groupmap](https://github.com/ndenev/pam_groupmap) -- [beatgammit/pam-http](https://github.com/beatgammit/pam-http) +- [`rust-pam` by tozny](https://github.com/tozny/rust-pam) +- [`pam_groupmap` by ndenev](https://github.com/ndenev/pam_groupmap) +- [`pam-http` by beatgammit](https://github.com/beatgammit/pam-http)
--- a/pam-http/Cargo.toml Sat Mar 08 19:29:46 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -[package] -name = "pam-http" -version = "0.1.0" -authors = ["Anthony Nowell <anowell@gmail.com>"] - -[lib] -name = "pam_http" -crate-type = ["cdylib"] - -[dependencies] -pam-bindings = { path = "../pam/" } -reqwest = { version = "0.11.3", features = ["blocking"] }
--- a/pam-http/Justfile Sat Mar 08 19:29:46 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ - -all: - cargo build - -install: - @cargo build --release - sudo cp conf/http-auth /etc/pam.d/ - sudo cp ../target/release/libpam_http.so /lib/security/pam_http.so - -test: - @just install - gcc -o ../target/pam_test test.c -lpam -lpam_misc
--- a/pam-http/README.md Sat Mar 08 19:29:46 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -pam-http -======== - -A PAM HTTP BasicAuth module built using pam-rs - -# Prerequisites - -You need some libraries before you build like libpam and libssl. - -If you're going to build on Ubuntu, just run this: - -``` -sudo apt-get install -y build-essential libpam0g-dev libpam0g libssl-dev -``` - -# Building - -Just use `cargo build`. - -# Usage - -You need to move the build product to a folder where PAM is looking for modules. - -If you're using Ubuntu you can move `libpam_http.so` to `/lib/security`. -After doing so you need to make sure it has proper permissions: `sudo chmod 755 /lib/security/libpam_http.so`. -Then you can place a configuration file in `/etc/pam.d/`. It can look something like this: - -``` -auth sufficient libpam_http.so url=https://theserver.example.com/someendpoint -account sufficient libpam_http.so -``` - -Make sure the endpoint you're specifying can receive GET requests and supports -[HTTP Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication#Client_side). -If the user is authenticated successfully it should return HTTP 200.
--- a/pam-http/conf/http-auth Sat Mar 08 19:29:46 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2 +0,0 @@ -auth sufficient pam_http.so url=http://localhost:3000 -account sufficient pam_http.so
--- a/pam-http/src/lib.rs Sat Mar 08 19:29:46 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +0,0 @@ -extern crate pam; -extern crate reqwest; - -use pam::constants::{PamFlag, PamResultCode, PAM_PROMPT_ECHO_OFF}; -use pam::conv::Conv; -use pam::module::{PamHandle, PamHooks}; -use reqwest::blocking::Client; -use reqwest::StatusCode; -use std::collections::HashMap; -use std::ffi::CStr; -use std::time::Duration; -use pam::pam_try; - -struct PamHttp; -pam::pam_hooks!(PamHttp); - -impl PamHooks for PamHttp { - // This function performs the task of authenticating the user. - fn sm_authenticate(pamh: &mut PamHandle, args: Vec<&CStr>, _flags: PamFlag) -> PamResultCode { - println!("Let's auth over HTTP"); - - let args: Vec<_> = args - .iter() - .map(|s| s.to_string_lossy()) - .collect(); - let args: HashMap<&str, &str> = args - .iter() - .map(|s| { - let mut parts = s.splitn(2, '='); - (parts.next().unwrap(), parts.next().unwrap_or("")) - }) - .collect(); - - let user = pam_try!(pamh.get_user(None)); - - let url: &str = match args.get("url") { - Some(url) => url, - None => return PamResultCode::PAM_AUTH_ERR, - }; - - let conv = match pamh.get_item::<Conv>() { - Ok(Some(conv)) => conv, - Ok(None) => { - unreachable!("No conv available"); - } - Err(err) => { - println!("Couldn't get pam_conv"); - return err; - } - }; - let password = pam_try!(conv.send(PAM_PROMPT_ECHO_OFF, "Word, yo: ")); - let password = match password { - Some(password) => Some(pam_try!(password.to_str(), PamResultCode::PAM_AUTH_ERR)), - None => None, - }; - println!("Got a password {:?}", password); - let status = pam_try!( - get_url(url, &user, password), - PamResultCode::PAM_AUTH_ERR - ); - - if !status.is_success() { - println!("HTTP Error: {}", status); - return PamResultCode::PAM_AUTH_ERR; - } - - PamResultCode::PAM_SUCCESS - } - - fn sm_setcred(_pamh: &mut PamHandle, _args: Vec<&CStr>, _flags: PamFlag) -> PamResultCode { - println!("set credentials"); - PamResultCode::PAM_SUCCESS - } - - fn acct_mgmt(_pamh: &mut PamHandle, _args: Vec<&CStr>, _flags: PamFlag) -> PamResultCode { - println!("account management"); - PamResultCode::PAM_SUCCESS - } -} - -fn get_url(url: &str, user: &str, password: Option<&str>) -> reqwest::Result<StatusCode> { - let client = Client::builder().timeout(Duration::from_secs(15)).build()?; - client - .get(url) - .basic_auth(user, password) - .send() - .map(|r| r.status()) -}
--- a/pam-http/test.c Sat Mar 08 19:29:46 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -#include <security/pam_appl.h> -#include <security/pam_misc.h> -#include <stdio.h> - -const struct pam_conv conv = { - misc_conv, - NULL -}; - -int main(int argc, char *argv[]) { - pam_handle_t* pamh = NULL; - int retval; - const char* user = "nobody"; - - if(argc != 2) { - printf("Usage: app [username]\n"); - exit(1); - } - - user = argv[1]; - - retval = pam_start("http-auth", user, &conv, &pamh); - - // Are the credentials correct? - if (retval == PAM_SUCCESS) { - printf("Credentials accepted.\n"); - retval = pam_authenticate(pamh, 0); - } - - // Can the accound be used at this time? - if (retval == PAM_SUCCESS) { - printf("Account is valid.\n"); - retval = pam_acct_mgmt(pamh, 0); - } - - // Did everything work? - if (retval == PAM_SUCCESS) { - printf("Authenticated\n"); - } else { - printf("Not Authenticated\n"); - } - - // close PAM (end session) - if (pam_end(pamh, retval) != PAM_SUCCESS) { - pamh = NULL; - printf("check_user: failed to release authenticator\n"); - exit(1); - } - - return retval == PAM_SUCCESS ? 0 : 1; -} -
--- a/pam-sober/Cargo.toml Sat Mar 08 19:29:46 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -[package] -name = "pam-sober" -version = "0.1.0" -authors = ["Anthony Nowell <anowell@gmail.com>"] - -[lib] -name = "pam_sober" -crate-type = ["cdylib"] - -[dependencies] -pam-bindings = { path = "../pam/" } -rand = "0.8.4"
--- a/pam-sober/Justfile Sat Mar 08 19:29:46 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ - -all: - cargo build - -install: - @cargo build --release - sudo cp conf/sober-auth /etc/pam.d/ - sudo cp ../target/release/libpam_sober.so /lib/security/pam_sober.so - -test: - @just install - gcc -o ../target/pam_test test.c -lpam -lpam_misc
--- a/pam-sober/conf/sober-auth Sat Mar 08 19:29:46 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2 +0,0 @@ -auth sufficient pam_sober.so -account sufficient pam_sober.so
--- a/pam-sober/src/lib.rs Sat Mar 08 19:29:46 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -extern crate pam; -extern crate rand; - -use pam::constants::{PamFlag, PamResultCode, PAM_PROMPT_ECHO_ON}; -use pam::conv::Conv; -use pam::module::{PamHandle, PamHooks}; -use rand::Rng; -use std::ffi::CStr; -use std::str::FromStr; -use pam::pam_try; - -struct PamSober; -pam::pam_hooks!(PamSober); - -impl PamHooks for PamSober { - // This function performs the task of authenticating the user. - fn sm_authenticate(pamh: &mut PamHandle, _args: Vec<&CStr>, _flags: PamFlag) -> PamResultCode { - println!("Let's make sure you're sober enough to perform basic addition"); - - /* TODO: use args to change difficulty ;-) - let args: HashMap<&str, &str> = args.iter().map(|s| { - let mut parts = s.splitn(2, "="); - (parts.next().unwrap(), parts.next().unwrap_or("")) - }).collect(); - */ - - // TODO: maybe we can change difficulty base on user? - // let user = pam_try!(pam.get_user(None)); - - let conv = match pamh.get_item::<Conv>() { - Ok(Some(conv)) => conv, - Ok(None) => todo!(), - Err(err) => { - println!("Couldn't get pam_conv"); - return err; - } - }; - - let mut rng = rand::thread_rng(); - let a = rng.gen::<u32>() % 100; - let b = rng.gen::<u32>() % 100; - let math = format!("{} + {} = ", a, b); - - // This println kinda helps debugging since the test script doesn't echo - eprintln!("[DEBUG]: {}{}", math, a + b); - - let password = pam_try!(conv.send(PAM_PROMPT_ECHO_ON, &math)); - - if let Some(password) = password { - let password = pam_try!(password.to_str(), PamResultCode::PAM_AUTH_ERR); - let answer = pam_try!(u32::from_str(password), PamResultCode::PAM_AUTH_ERR); - if answer == a + b { - PamResultCode::PAM_SUCCESS - } else { - println!("Wrong answer provided {} + {} != {}", a, b, answer); - PamResultCode::PAM_AUTH_ERR - } - } else { - println!("You failed the PAM sobriety test."); - PamResultCode::PAM_AUTH_ERR - } - } - - fn sm_setcred(_pamh: &mut PamHandle, _args: Vec<&CStr>, _flags: PamFlag) -> PamResultCode { - println!("set credentials"); - PamResultCode::PAM_SUCCESS - } - - fn acct_mgmt(_pamh: &mut PamHandle, _args: Vec<&CStr>, _flags: PamFlag) -> PamResultCode { - println!("account management"); - PamResultCode::PAM_SUCCESS - } -}
--- a/pam-sober/test.c Sat Mar 08 19:29:46 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -#include <security/pam_appl.h> -#include <security/pam_misc.h> -#include <stdio.h> - -const struct pam_conv conv = { - misc_conv, - NULL -}; - -int main(int argc, char *argv[]) { - pam_handle_t* pamh = NULL; - int retval; - const char* user = "nobody"; - - if(argc != 2) { - printf("Usage: app [username]\n"); - exit(1); - } - - user = argv[1]; - - retval = pam_start("sober-auth", user, &conv, &pamh); - - // Are the credentials correct? - if (retval == PAM_SUCCESS) { - printf("PAM module initialized\n"); - retval = pam_authenticate(pamh, 0); - } - - // Can the accound be used at this time? - if (retval == PAM_SUCCESS) { - printf("Credentials accepted.\n"); - retval = pam_acct_mgmt(pamh, 0); - } - - // Did everything work? - if (retval == PAM_SUCCESS) { - printf("Account is valid.\n"); - printf("Authenticated\n"); - } else { - printf("Not Authenticated\n"); - } - - // close PAM (end session) - if (pam_end(pamh, retval) != PAM_SUCCESS) { - pamh = NULL; - printf("check_user: failed to release authenticator\n"); - exit(1); - } - - return retval == PAM_SUCCESS ? 0 : 1; -} -
--- a/pam/Cargo.toml Sat Mar 08 19:29:46 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -[package] - -name = "pam-bindings" -description = "PAM bindings for Rust" -version = "0.1.1" -authors = [ "Anthony Nowell <anowell@gmail.com>" ] -repository = "https://github.com/anowell/pam-rs" -readme = "../README.md" -keywords = ["pam", "ffi", "linux", "authentication"] -license = "MIT" - -[lib] -name = "pam" - -[dependencies] -libc = "0.2.97"
--- a/pam/src/constants.rs Sat Mar 08 19:29:46 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,66 +0,0 @@ -use libc::{c_int, c_uint}; - -// TODO: Import constants from C header file at compile time. - -pub type PamFlag = c_uint; -pub type PamItemType = c_int; -pub type PamMessageStyle = c_int; - -// The Linux-PAM flags -// see /usr/include/security/_pam_types.h -pub const PAM_SILENT: PamFlag = 0x8000; -pub const PAM_DISALLOW_NULL_AUTHTOK: PamFlag = 0x0001; -pub const PAM_ESTABLISH_CRED: PamFlag = 0x0002; -pub const PAM_DELETE_CRED: PamFlag = 0x0004; -pub const PAM_REINITIALIZE_CRED: PamFlag = 0x0008; -pub const PAM_REFRESH_CRED: PamFlag = 0x0010; -pub const PAM_CHANGE_EXPIRED_AUTHTOK: PamFlag = 0x0020; - -// Message styles -pub const PAM_PROMPT_ECHO_OFF: PamMessageStyle = 1; -pub const PAM_PROMPT_ECHO_ON: PamMessageStyle = 2; -pub const PAM_ERROR_MSG: PamMessageStyle = 3; -pub const PAM_TEXT_INFO: PamMessageStyle = 4; -/// yes/no/maybe conditionals -pub const PAM_RADIO_TYPE: PamMessageStyle = 5; -pub const PAM_BINARY_PROMPT: PamMessageStyle = 7; - -// The Linux-PAM return values -// see /usr/include/security/_pam_types.h -#[allow(non_camel_case_types, dead_code)] -#[derive(Debug, PartialEq)] -#[repr(C)] -pub enum PamResultCode { - PAM_SUCCESS = 0, - PAM_OPEN_ERR = 1, - PAM_SYMBOL_ERR = 2, - PAM_SERVICE_ERR = 3, - PAM_SYSTEM_ERR = 4, - PAM_BUF_ERR = 5, - PAM_PERM_DENIED = 6, - PAM_AUTH_ERR = 7, - PAM_CRED_INSUFFICIENT = 8, - PAM_AUTHINFO_UNAVAIL = 9, - PAM_USER_UNKNOWN = 10, - PAM_MAXTRIES = 11, - PAM_NEW_AUTHTOK_REQD = 12, - PAM_ACCT_EXPIRED = 13, - PAM_SESSION_ERR = 14, - PAM_CRED_UNAVAIL = 15, - PAM_CRED_EXPIRED = 16, - PAM_CRED_ERR = 17, - PAM_NO_MODULE_DATA = 18, - PAM_CONV_ERR = 19, - PAM_AUTHTOK_ERR = 20, - PAM_AUTHTOK_RECOVERY_ERR = 21, - PAM_AUTHTOK_LOCK_BUSY = 22, - PAM_AUTHTOK_DISABLE_AGING = 23, - PAM_TRY_AGAIN = 24, - PAM_IGNORE = 25, - PAM_ABORT = 26, - PAM_AUTHTOK_EXPIRED = 27, - PAM_MODULE_UNKNOWN = 28, - PAM_BAD_ITEM = 29, - PAM_CONV_AGAIN = 30, - PAM_INCOMPLETE = 31, -}
--- a/pam/src/conv.rs Sat Mar 08 19:29:46 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ -use libc::{c_char, c_int}; -use std::ffi::{CStr, CString}; -use std::ptr; - -use constants::PamMessageStyle; -use constants::PamResultCode; -use items::Item; -use module::PamResult; - -#[repr(C)] -struct PamMessage { - msg_style: PamMessageStyle, - msg: *const c_char, -} - -#[repr(C)] -struct PamResponse { - resp: *const c_char, - resp_retcode: libc::c_int, // Unused - always zero -} - -/// `PamConv` acts as a channel for communicating with user. -/// -/// Communication is mediated by the pam client (the application that invoked -/// pam). Messages sent will be relayed to the user by the client, and response -/// will be relayed back. -#[repr(C)] -pub struct Inner { - conv: extern "C" fn( - num_msg: c_int, - pam_message: &&PamMessage, - pam_response: &mut *const PamResponse, - appdata_ptr: *const libc::c_void, - ) -> PamResultCode, - appdata_ptr: *const libc::c_void, -} - -pub struct Conv<'a>(&'a Inner); - -impl Conv<'_> { - /// Sends a message to the pam client. - /// - /// This will typically result in the user seeing a message or a prompt. - /// There are several message styles available: - /// - /// - PAM_PROMPT_ECHO_OFF - /// - PAM_PROMPT_ECHO_ON - /// - PAM_ERROR_MSG - /// - PAM_TEXT_INFO - /// - PAM_RADIO_TYPE - /// - PAM_BINARY_PROMPT - /// - /// Note that the user experience will depend on how the client implements - /// these message styles - and not all applications implement all message - /// styles. - pub fn send(&self, style: PamMessageStyle, msg: &str) -> PamResult<Option<&CStr>> { - let mut resp_ptr: *const PamResponse = ptr::null(); - let msg_cstr = CString::new(msg).unwrap(); - let msg = PamMessage { - msg_style: style, - msg: msg_cstr.as_ptr(), - }; - - let ret = (self.0.conv)(1, &&msg, &mut resp_ptr, self.0.appdata_ptr); - - if PamResultCode::PAM_SUCCESS == ret { - // PamResponse.resp is null for styles that don't return user input like PAM_TEXT_INFO - let response = unsafe { (*resp_ptr).resp }; - if response.is_null() { - Ok(None) - } else { - Ok(Some(unsafe { CStr::from_ptr(response) })) - } - } else { - Err(ret) - } - } -} - -impl Item for Conv<'_> { - type Raw = Inner; - - fn type_id() -> crate::items::ItemType { - crate::items::ItemType::Conv - } - - unsafe fn from_raw(raw: *const Self::Raw) -> Self { - Self(&*raw) - } - - fn into_raw(self) -> *const Self::Raw { - self.0 as _ - } -}
--- a/pam/src/items.rs Sat Mar 08 19:29:46 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +0,0 @@ -#[repr(u32)] -pub enum ItemType { - /// The service name - Service = 1, - /// The user name - User = 2, - /// The tty name - Tty = 3, - /// The remote host name - RHost = 4, - /// The pam_conv structure - Conv = 5, - /// The authentication token (password) - AuthTok = 6, - /// The old authentication token - OldAuthTok = 7, - /// The remote user name - RUser = 8, - /// the prompt for getting a username - UserPrompt = 9, - /// app supplied function to override failure delays - FailDelay = 10, - /// X :display name - XDisplay = 11, - /// X :server authentication data - XAuthData = 12, - /// The type for pam_get_authtok - AuthTokType = 13, -} - -// A type that can be requested by `pam::Handle::get_item`. -pub trait Item { - /// The `repr(C)` type that is returned (by pointer) by the underlying `pam_get_item` function. - type Raw; - - /// The `ItemType` for this type - fn type_id() -> ItemType; - - /// The function to convert from the pointer to the C-representation to this safer wrapper type - /// - /// # Safety - /// - /// This function can assume the pointer is a valid pointer to a `Self::Raw` instance. - unsafe fn from_raw(raw: *const Self::Raw) -> Self; - - /// The function to convert from this wrapper type to a C-compatible pointer. - fn into_raw(self) -> *const Self::Raw; -} - -macro_rules! cstr_item { - ($name:ident) => { - #[derive(Debug)] - pub struct $name<'s>(pub &'s std::ffi::CStr); - - impl<'s> std::ops::Deref for $name<'s> { - type Target = &'s std::ffi::CStr; - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - impl<'s> Item for $name<'s> { - type Raw = libc::c_char; - - fn type_id() -> ItemType { - ItemType::$name - } - - unsafe fn from_raw(raw: *const Self::Raw) -> Self { - Self(std::ffi::CStr::from_ptr(raw)) - } - - fn into_raw(self) -> *const Self::Raw { - self.0.as_ptr() - } - } - }; -} - -cstr_item!(Service); -cstr_item!(User); -cstr_item!(Tty); -cstr_item!(RHost); -// Conv -cstr_item!(AuthTok); -cstr_item!(OldAuthTok); -cstr_item!(RUser); -cstr_item!(UserPrompt);
--- a/pam/src/lib.rs Sat Mar 08 19:29:46 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -//! Interface to the pluggable authentication module framework (PAM). -//! -//! The goal of this library is to provide a type-safe API that can be used to -//! interact with PAM. The library is incomplete - currently it supports -//! a subset of functions for use in a pam authentication module. A pam module -//! is a shared library that is invoked to authenticate a user, or to perform -//! other functions. -//! -//! For general information on writing pam modules, see -//! [The Linux-PAM Module Writers' Guide][module-guide] -//! -//! [module-guide]: http://www.linux-pam.org/Linux-PAM-html/Linux-PAM_MWG.html -//! -//! A typical authentication module will define an external function called -//! `pam_sm_authenticate()`, which will use functions in this library to -//! interrogate the program that requested authentication for more information, -//! and to render a result. For a working example that uses this library, see -//! [toznyauth-pam][]. -//! -//! [toznyauth-pam]: https://github.com/tozny/toznyauth-pam -//! -//! Note that constants that are normally read from pam header files are -//! hard-coded in the `constants` module. The values there are taken from -//! a Linux system. That means that it might take some work to get this library -//! to work on other platforms. - -extern crate libc; - -pub mod constants; -pub mod conv; -pub mod items; -#[doc(hidden)] -pub mod macros; -pub mod module;
--- a/pam/src/macros.rs Sat Mar 08 19:29:46 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,141 +0,0 @@ -/// Macro to generate the `extern "C"` entrypoint bindings needed by PAM -/// -/// You can call `pam_hooks!(SomeType);` for any type that implements `PamHooks` -/// -/// ## Examples: -/// -/// Here is full example of a PAM module that would authenticate and authorize everybody: -/// -/// ``` -/// #[macro_use] extern crate pam; -/// -/// use pam::module::{PamHooks, PamHandle}; -/// use pam::constants::{PamResultCode, PamFlag}; -/// use std::ffi::CStr; -/// -/// # fn main() {} -/// struct MyPamModule; -/// pam_hooks!(MyPamModule); -/// -/// impl PamHooks for MyPamModule { -/// fn sm_authenticate(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { -/// println!("Everybody is authenticated!"); -/// PamResultCode::PAM_SUCCESS -/// } -/// -/// fn acct_mgmt(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { -/// println!("Everybody is authorized!"); -/// PamResultCode::PAM_SUCCESS -/// } -/// } -/// ``` -#[macro_export] -macro_rules! pam_hooks { - ($ident:ident) => { - pub use self::pam_hooks_scope::*; - mod pam_hooks_scope { - use std::ffi::CStr; - use std::os::raw::{c_char, c_int}; - use $crate::constants::{PamFlag, PamResultCode}; - use $crate::module::{PamHandle, PamHooks}; - - fn extract_argv<'a>(argc: c_int, argv: *const *const c_char) -> Vec<&'a CStr> { - (0..argc) - .map(|o| unsafe { CStr::from_ptr(*argv.offset(o as isize) as *const c_char) }) - .collect() - } - - #[no_mangle] - pub extern "C" fn pam_sm_acct_mgmt( - pamh: &mut PamHandle, - flags: PamFlag, - argc: c_int, - argv: *const *const c_char, - ) -> PamResultCode { - let args = extract_argv(argc, argv); - super::$ident::acct_mgmt(pamh, args, flags) - } - - #[no_mangle] - pub extern "C" fn pam_sm_authenticate( - pamh: &mut PamHandle, - flags: PamFlag, - argc: c_int, - argv: *const *const c_char, - ) -> PamResultCode { - let args = extract_argv(argc, argv); - super::$ident::sm_authenticate(pamh, args, flags) - } - - #[no_mangle] - pub extern "C" fn pam_sm_chauthtok( - pamh: &mut PamHandle, - flags: PamFlag, - argc: c_int, - argv: *const *const c_char, - ) -> PamResultCode { - let args = extract_argv(argc, argv); - super::$ident::sm_chauthtok(pamh, args, flags) - } - - #[no_mangle] - pub extern "C" fn pam_sm_close_session( - pamh: &mut PamHandle, - flags: PamFlag, - argc: c_int, - argv: *const *const c_char, - ) -> PamResultCode { - let args = extract_argv(argc, argv); - super::$ident::sm_close_session(pamh, args, flags) - } - - #[no_mangle] - pub extern "C" fn pam_sm_open_session( - pamh: &mut PamHandle, - flags: PamFlag, - argc: c_int, - argv: *const *const c_char, - ) -> PamResultCode { - let args = extract_argv(argc, argv); - super::$ident::sm_open_session(pamh, args, flags) - } - - #[no_mangle] - pub extern "C" fn pam_sm_setcred( - pamh: &mut PamHandle, - flags: PamFlag, - argc: c_int, - argv: *const *const c_char, - ) -> PamResultCode { - let args = extract_argv(argc, argv); - super::$ident::sm_setcred(pamh, args, flags) - } - } - }; -} - -#[macro_export] -macro_rules! pam_try { - ($r:expr) => { - match $r { - Ok(t) => t, - Err(e) => return e, - } - }; - ($r:expr, $e:expr) => { - match $r { - Ok(t) => t, - Err(_) => return $e, - } - }; -} - -#[cfg(test)] -pub mod test { - use module::PamHooks; - - struct Foo; - impl PamHooks for Foo {} - - pam_hooks!(Foo); -}
--- a/pam/src/module.rs Sat Mar 08 19:29:46 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,302 +0,0 @@ -//! Functions for use in pam modules. - -use libc::c_char; -use std::ffi::{CStr, CString}; - -use constants::{PamFlag, PamResultCode}; -use items::ItemType; - -/// Opaque type, used as a pointer when making pam API calls. -/// -/// A module is invoked via an external function such as `pam_sm_authenticate`. -/// Such a call provides a pam handle pointer. The same pointer should be given -/// as an argument when making API calls. -#[repr(C)] -pub struct PamHandle { - _data: [u8; 0], -} - -#[link(name = "pam")] -extern "C" { - fn pam_get_data( - pamh: *const PamHandle, - module_data_name: *const c_char, - data: &mut *const libc::c_void, - ) -> PamResultCode; - - fn pam_set_data( - pamh: *const PamHandle, - module_data_name: *const c_char, - data: *mut libc::c_void, - cleanup: extern "C" fn( - pamh: *const PamHandle, - data: *mut libc::c_void, - error_status: PamResultCode, - ), - ) -> PamResultCode; - - fn pam_get_item( - pamh: *const PamHandle, - item_type: ItemType, - item: &mut *const libc::c_void, - ) -> PamResultCode; - - fn pam_set_item( - pamh: *mut PamHandle, - item_type: ItemType, - item: *const libc::c_void, - ) -> PamResultCode; - - fn pam_get_user( - pamh: *const PamHandle, - user: &*mut c_char, - prompt: *const c_char, - ) -> PamResultCode; - - fn pam_get_authtok( - pamh: *const PamHandle, - item_type: ItemType, - data: &*mut c_char, - prompt: *const c_char, - ) -> PamResultCode; - -} - -pub extern "C" fn cleanup<T>(_: *const PamHandle, c_data: *mut libc::c_void, _: PamResultCode) { - unsafe { - let _data: Box<T> = Box::from_raw(c_data.cast::<T>()); - } -} - -pub type PamResult<T> = Result<T, PamResultCode>; - -impl PamHandle { - /// Gets some value, identified by `key`, that has been set by the module - /// previously. - /// - /// See the [`pam_get_data` manual page]( - /// https://www.man7.org/linux/man-pages/man3/pam_get_data.3.html). - /// - /// # Errors - /// - /// Returns an error if the underlying PAM function call fails. - /// - /// # Safety - /// - /// The data stored under the provided key must be of type `T` otherwise the - /// behaviour of this function is undefined. - pub unsafe fn get_data<T>(&self, key: &str) -> PamResult<&T> { - let c_key = CString::new(key).unwrap(); - let mut ptr: *const libc::c_void = std::ptr::null(); - let res = pam_get_data(self, c_key.as_ptr(), &mut ptr); - if PamResultCode::PAM_SUCCESS == res && !ptr.is_null() { - let typed_ptr = ptr.cast::<T>(); - let data: &T = &*typed_ptr; - Ok(data) - } else { - Err(res) - } - } - - /// Stores a value that can be retrieved later with `get_data`. The value lives - /// as long as the current pam cycle. - /// - /// See the [`pam_set_data` manual page]( - /// https://www.man7.org/linux/man-pages/man3/pam_set_data.3.html). - /// - /// # Errors - /// - /// Returns an error if the underlying PAM function call fails. - pub fn set_data<T>(&self, key: &str, data: Box<T>) -> PamResult<()> { - let c_key = CString::new(key).unwrap(); - let res = unsafe { - pam_set_data( - self, - c_key.as_ptr(), - Box::into_raw(data).cast::<libc::c_void>(), - cleanup::<T>, - ) - }; - to_result(res) - } - - /// Retrieves a value that has been set, possibly by the pam client. This is - /// particularly useful for getting a `PamConv` reference. - /// - /// See the [`pam_get_item` manual page]( - /// https://www.man7.org/linux/man-pages/man3/pam_get_item.3.html). - /// - /// # Errors - /// - /// Returns an error if the underlying PAM function call fails. - pub fn get_item<T: crate::items::Item>(&self) -> PamResult<Option<T>> { - let mut ptr: *const libc::c_void = std::ptr::null(); - let (res, item) = unsafe { - let r = pam_get_item(self, T::type_id(), &mut ptr); - let typed_ptr = ptr.cast::<T::Raw>(); - let t = if typed_ptr.is_null() { - None - } else { - Some(T::from_raw(typed_ptr)) - }; - (r, t) - }; - match res { - PamResultCode::PAM_SUCCESS => Ok(item), - other => Err(other), - } - } - - /// Sets a value in the pam context. The value can be retrieved using - /// `get_item`. - /// - /// Note that all items are strings, except `PAM_CONV` and `PAM_FAIL_DELAY`. - /// - /// See the [`pam_set_item` manual page]( - /// https://www.man7.org/linux/man-pages/man3/pam_set_item.3.html). - /// - /// # Errors - /// - /// Returns an error if the underlying PAM function call fails. - /// - /// # Panics - /// - /// Panics if the provided item key contains a nul byte. - pub fn set_item_str<T: crate::items::Item>(&mut self, item: T) -> PamResult<()> { - let res = - unsafe { pam_set_item(self, T::type_id(), item.into_raw().cast::<libc::c_void>()) }; - to_result(res) - } - - /// Retrieves the name of the user who is authenticating or logging in. - /// - /// This is really a specialization of `get_item`. - /// - /// See the [`pam_get_user` manual page]( - /// https://www.man7.org/linux/man-pages/man3/pam_get_user.3.html). - /// - /// # Errors - /// - /// Returns an error if the underlying PAM function call fails. - /// - /// # Panics - /// - /// Panics if the provided prompt string contains a nul byte. - pub fn get_user(&self, prompt: Option<&str>) -> PamResult<String> { - let prompt_string; - let c_prompt = match prompt { - Some(p) => { - prompt_string = CString::new(p).unwrap(); - prompt_string.as_ptr() - } - None => std::ptr::null(), - }; - let output: *mut c_char = std::ptr::null_mut(); - let res = unsafe { pam_get_user(self, &output, c_prompt) }; - match res { - PamResultCode::PAM_SUCCESS => copy_pam_string(output), - otherwise => Err(otherwise), - } - } - - /// Retrieves the authentication token from the user. - /// - /// This is really a specialization of `get_item`. - /// - /// See the [`pam_get_authtok` manual page]( - /// https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html). - /// - /// # Errors - /// - /// Returns an error if the underlying PAM function call fails. - /// - /// # Panics - /// - /// Panics if the provided prompt string contains a nul byte. - pub fn get_authtok(&self, prompt: Option<&str>) -> PamResult<String> { - let prompt_string; - let c_prompt = match prompt { - Some(p) => { - prompt_string = CString::new(p).unwrap(); - prompt_string.as_ptr() - } - None => std::ptr::null(), - }; - let output: *mut c_char = std::ptr::null_mut(); - let res = unsafe { pam_get_authtok(self, ItemType::AuthTok, &output, c_prompt) }; - match res { - PamResultCode::PAM_SUCCESS => copy_pam_string(output), - otherwise => Err(otherwise), - } - } -} - -/// Creates an owned copy of a string that is returned from a -/// <code>pam_get_<var>whatever</var></code> function. -fn copy_pam_string(result_ptr: *const c_char) -> PamResult<String> { - // We really shouldn't get a null pointer back here, but if we do, return nothing. - if result_ptr.is_null() { - return Ok(String::from("")); - } - let bytes = unsafe { CStr::from_ptr(result_ptr).to_bytes() }; - String::from_utf8(bytes.to_vec()).map_err(|_| PamResultCode::PAM_CONV_ERR) -} - -/// Convenience to transform a `PamResultCode` into a unit `PamResult`. -fn to_result(result: PamResultCode) -> PamResult<()> { - match result { - PamResultCode::PAM_SUCCESS => Ok(()), - otherwise => Err(otherwise), - } -} - -/// Provides functions that are invoked by the entrypoints generated by the -/// [`pam_hooks!` macro](../macro.pam_hooks.html). -/// -/// All hooks are ignored by PAM dispatch by default given the default return value of `PAM_IGNORE`. -/// Override any functions that you want to handle with your module. See `man pam(3)`. -#[allow(unused_variables)] -pub trait PamHooks { - /// This function performs the task of establishing whether the user is permitted to gain access at - /// this time. It should be understood that the user has previously been validated by an - /// authentication module. This function checks for other things. Such things might be: the time of - /// day or the date, the terminal line, remote hostname, etc. This function may also determine - /// things like the expiration on passwords, and respond that the user change it before continuing. - fn acct_mgmt(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { - PamResultCode::PAM_IGNORE - } - - /// This function performs the task of authenticating the user. - fn sm_authenticate(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { - PamResultCode::PAM_IGNORE - } - - /// This function is used to (re-)set the authentication token of the user. - /// - /// The PAM library calls this function twice in succession. The first time with - /// `PAM_PRELIM_CHECK` and then, if the module does not return `PAM_TRY_AGAIN`, subsequently with - /// `PAM_UPDATE_AUTHTOK`. It is only on the second call that the authorization token is - /// (possibly) changed. - fn sm_chauthtok(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { - PamResultCode::PAM_IGNORE - } - - /// This function is called to terminate a session. - fn sm_close_session(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { - PamResultCode::PAM_IGNORE - } - - /// This function is called to commence a session. - fn sm_open_session(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { - PamResultCode::PAM_IGNORE - } - - /// This function performs the task of altering the credentials of the user with respect to the - /// corresponding authorization scheme. Generally, an authentication module may have access to more - /// information about a user than their authentication token. This function is used to make such - /// information available to the application. It should only be called after the user has been - /// authenticated but before a session has been established. - fn sm_setcred(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { - PamResultCode::PAM_IGNORE - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/constants.rs Tue Apr 15 00:50:23 2025 -0400 @@ -0,0 +1,66 @@ +use libc::{c_int, c_uint}; + +// TODO: Import constants from C header file at compile time. + +pub type PamFlag = c_uint; +pub type PamItemType = c_int; +pub type PamMessageStyle = c_int; + +// The Linux-PAM flags +// see /usr/include/security/_pam_types.h +pub const PAM_SILENT: PamFlag = 0x8000; +pub const PAM_DISALLOW_NULL_AUTHTOK: PamFlag = 0x0001; +pub const PAM_ESTABLISH_CRED: PamFlag = 0x0002; +pub const PAM_DELETE_CRED: PamFlag = 0x0004; +pub const PAM_REINITIALIZE_CRED: PamFlag = 0x0008; +pub const PAM_REFRESH_CRED: PamFlag = 0x0010; +pub const PAM_CHANGE_EXPIRED_AUTHTOK: PamFlag = 0x0020; + +// Message styles +pub const PAM_PROMPT_ECHO_OFF: PamMessageStyle = 1; +pub const PAM_PROMPT_ECHO_ON: PamMessageStyle = 2; +pub const PAM_ERROR_MSG: PamMessageStyle = 3; +pub const PAM_TEXT_INFO: PamMessageStyle = 4; +/// yes/no/maybe conditionals +pub const PAM_RADIO_TYPE: PamMessageStyle = 5; +pub const PAM_BINARY_PROMPT: PamMessageStyle = 7; + +// The Linux-PAM return values +// see /usr/include/security/_pam_types.h +#[allow(non_camel_case_types, dead_code)] +#[derive(Debug, PartialEq)] +#[repr(C)] +pub enum PamResultCode { + PAM_SUCCESS = 0, + PAM_OPEN_ERR = 1, + PAM_SYMBOL_ERR = 2, + PAM_SERVICE_ERR = 3, + PAM_SYSTEM_ERR = 4, + PAM_BUF_ERR = 5, + PAM_PERM_DENIED = 6, + PAM_AUTH_ERR = 7, + PAM_CRED_INSUFFICIENT = 8, + PAM_AUTHINFO_UNAVAIL = 9, + PAM_USER_UNKNOWN = 10, + PAM_MAXTRIES = 11, + PAM_NEW_AUTHTOK_REQD = 12, + PAM_ACCT_EXPIRED = 13, + PAM_SESSION_ERR = 14, + PAM_CRED_UNAVAIL = 15, + PAM_CRED_EXPIRED = 16, + PAM_CRED_ERR = 17, + PAM_NO_MODULE_DATA = 18, + PAM_CONV_ERR = 19, + PAM_AUTHTOK_ERR = 20, + PAM_AUTHTOK_RECOVERY_ERR = 21, + PAM_AUTHTOK_LOCK_BUSY = 22, + PAM_AUTHTOK_DISABLE_AGING = 23, + PAM_TRY_AGAIN = 24, + PAM_IGNORE = 25, + PAM_ABORT = 26, + PAM_AUTHTOK_EXPIRED = 27, + PAM_MODULE_UNKNOWN = 28, + PAM_BAD_ITEM = 29, + PAM_CONV_AGAIN = 30, + PAM_INCOMPLETE = 31, +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/conv.rs Tue Apr 15 00:50:23 2025 -0400 @@ -0,0 +1,94 @@ +use libc::{c_char, c_int}; +use std::ffi::{CStr, CString}; +use std::ptr; + +use constants::PamMessageStyle; +use constants::PamResultCode; +use items::Item; +use module::PamResult; + +#[repr(C)] +struct PamMessage { + msg_style: PamMessageStyle, + msg: *const c_char, +} + +#[repr(C)] +struct PamResponse { + resp: *const c_char, + resp_retcode: libc::c_int, // Unused - always zero +} + +/// `PamConv` acts as a channel for communicating with user. +/// +/// Communication is mediated by the pam client (the application that invoked +/// pam). Messages sent will be relayed to the user by the client, and response +/// will be relayed back. +#[repr(C)] +pub struct Inner { + conv: extern "C" fn( + num_msg: c_int, + pam_message: &&PamMessage, + pam_response: &mut *const PamResponse, + appdata_ptr: *const libc::c_void, + ) -> PamResultCode, + appdata_ptr: *const libc::c_void, +} + +pub struct Conv<'a>(&'a Inner); + +impl Conv<'_> { + /// Sends a message to the pam client. + /// + /// This will typically result in the user seeing a message or a prompt. + /// There are several message styles available: + /// + /// - PAM_PROMPT_ECHO_OFF + /// - PAM_PROMPT_ECHO_ON + /// - PAM_ERROR_MSG + /// - PAM_TEXT_INFO + /// - PAM_RADIO_TYPE + /// - PAM_BINARY_PROMPT + /// + /// Note that the user experience will depend on how the client implements + /// these message styles - and not all applications implement all message + /// styles. + pub fn send(&self, style: PamMessageStyle, msg: &str) -> PamResult<Option<&CStr>> { + let mut resp_ptr: *const PamResponse = ptr::null(); + let msg_cstr = CString::new(msg).unwrap(); + let msg = PamMessage { + msg_style: style, + msg: msg_cstr.as_ptr(), + }; + + let ret = (self.0.conv)(1, &&msg, &mut resp_ptr, self.0.appdata_ptr); + + if PamResultCode::PAM_SUCCESS == ret { + // PamResponse.resp is null for styles that don't return user input like PAM_TEXT_INFO + let response = unsafe { (*resp_ptr).resp }; + if response.is_null() { + Ok(None) + } else { + Ok(Some(unsafe { CStr::from_ptr(response) })) + } + } else { + Err(ret) + } + } +} + +impl Item for Conv<'_> { + type Raw = Inner; + + fn type_id() -> crate::items::ItemType { + crate::items::ItemType::Conv + } + + unsafe fn from_raw(raw: *const Self::Raw) -> Self { + Self(&*raw) + } + + fn into_raw(self) -> *const Self::Raw { + self.0 as _ + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/items.rs Tue Apr 15 00:50:23 2025 -0400 @@ -0,0 +1,88 @@ +#[repr(u32)] +pub enum ItemType { + /// The service name + Service = 1, + /// The user name + User = 2, + /// The tty name + Tty = 3, + /// The remote host name + RHost = 4, + /// The pam_conv structure + Conv = 5, + /// The authentication token (password) + AuthTok = 6, + /// The old authentication token + OldAuthTok = 7, + /// The remote user name + RUser = 8, + /// the prompt for getting a username + UserPrompt = 9, + /// app supplied function to override failure delays + FailDelay = 10, + /// X :display name + XDisplay = 11, + /// X :server authentication data + XAuthData = 12, + /// The type for pam_get_authtok + AuthTokType = 13, +} + +// A type that can be requested by `pam::Handle::get_item`. +pub trait Item { + /// The `repr(C)` type that is returned (by pointer) by the underlying `pam_get_item` function. + type Raw; + + /// The `ItemType` for this type + fn type_id() -> ItemType; + + /// The function to convert from the pointer to the C-representation to this safer wrapper type + /// + /// # Safety + /// + /// This function can assume the pointer is a valid pointer to a `Self::Raw` instance. + unsafe fn from_raw(raw: *const Self::Raw) -> Self; + + /// The function to convert from this wrapper type to a C-compatible pointer. + fn into_raw(self) -> *const Self::Raw; +} + +macro_rules! cstr_item { + ($name:ident) => { + #[derive(Debug)] + pub struct $name<'s>(pub &'s std::ffi::CStr); + + impl<'s> std::ops::Deref for $name<'s> { + type Target = &'s std::ffi::CStr; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl<'s> Item for $name<'s> { + type Raw = libc::c_char; + + fn type_id() -> ItemType { + ItemType::$name + } + + unsafe fn from_raw(raw: *const Self::Raw) -> Self { + Self(std::ffi::CStr::from_ptr(raw)) + } + + fn into_raw(self) -> *const Self::Raw { + self.0.as_ptr() + } + } + }; +} + +cstr_item!(Service); +cstr_item!(User); +cstr_item!(Tty); +cstr_item!(RHost); +// Conv +cstr_item!(AuthTok); +cstr_item!(OldAuthTok); +cstr_item!(RUser); +cstr_item!(UserPrompt);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib.rs Tue Apr 15 00:50:23 2025 -0400 @@ -0,0 +1,34 @@ +//! Interface to the pluggable authentication module framework (PAM). +//! +//! The goal of this library is to provide a type-safe API that can be used to +//! interact with PAM. The library is incomplete - currently it supports +//! a subset of functions for use in a pam authentication module. A pam module +//! is a shared library that is invoked to authenticate a user, or to perform +//! other functions. +//! +//! For general information on writing pam modules, see +//! [The Linux-PAM Module Writers' Guide][module-guide] +//! +//! [module-guide]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/Linux-PAM_MWG.html +//! +//! A typical authentication module will define an external function called +//! `pam_sm_authenticate()`, which will use functions in this library to +//! interrogate the program that requested authentication for more information, +//! and to render a result. For a working example that uses this library, see +//! [toznyauth-pam][]. +//! +//! [toznyauth-pam]: https://github.com/tozny/toznyauth-pam +//! +//! Note that constants that are normally read from pam header files are +//! hard-coded in the `constants` module. The values there are taken from +//! a Linux system. That means that it might take some work to get this library +//! to work on other platforms. + +extern crate libc; + +pub mod constants; +pub mod conv; +pub mod items; +#[doc(hidden)] +pub mod macros; +pub mod module;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/macros.rs Tue Apr 15 00:50:23 2025 -0400 @@ -0,0 +1,141 @@ +/// Macro to generate the `extern "C"` entrypoint bindings needed by PAM +/// +/// You can call `pam_hooks!(SomeType);` for any type that implements `PamHooks` +/// +/// ## Examples: +/// +/// Here is full example of a PAM module that would authenticate and authorize everybody: +/// +/// ``` +/// #[macro_use] extern crate pam; +/// +/// use pam::module::{PamHooks, PamHandle}; +/// use pam::constants::{PamResultCode, PamFlag}; +/// use std::ffi::CStr; +/// +/// # fn main() {} +/// struct MyPamModule; +/// pam_hooks!(MyPamModule); +/// +/// impl PamHooks for MyPamModule { +/// fn sm_authenticate(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { +/// println!("Everybody is authenticated!"); +/// PamResultCode::PAM_SUCCESS +/// } +/// +/// fn acct_mgmt(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { +/// println!("Everybody is authorized!"); +/// PamResultCode::PAM_SUCCESS +/// } +/// } +/// ``` +#[macro_export] +macro_rules! pam_hooks { + ($ident:ident) => { + pub use self::pam_hooks_scope::*; + mod pam_hooks_scope { + use std::ffi::CStr; + use std::os::raw::{c_char, c_int}; + use $crate::constants::{PamFlag, PamResultCode}; + use $crate::module::{PamHandle, PamHooks}; + + fn extract_argv<'a>(argc: c_int, argv: *const *const c_char) -> Vec<&'a CStr> { + (0..argc) + .map(|o| unsafe { CStr::from_ptr(*argv.offset(o as isize) as *const c_char) }) + .collect() + } + + #[no_mangle] + pub extern "C" fn pam_sm_acct_mgmt( + pamh: &mut PamHandle, + flags: PamFlag, + argc: c_int, + argv: *const *const c_char, + ) -> PamResultCode { + let args = extract_argv(argc, argv); + super::$ident::acct_mgmt(pamh, args, flags) + } + + #[no_mangle] + pub extern "C" fn pam_sm_authenticate( + pamh: &mut PamHandle, + flags: PamFlag, + argc: c_int, + argv: *const *const c_char, + ) -> PamResultCode { + let args = extract_argv(argc, argv); + super::$ident::sm_authenticate(pamh, args, flags) + } + + #[no_mangle] + pub extern "C" fn pam_sm_chauthtok( + pamh: &mut PamHandle, + flags: PamFlag, + argc: c_int, + argv: *const *const c_char, + ) -> PamResultCode { + let args = extract_argv(argc, argv); + super::$ident::sm_chauthtok(pamh, args, flags) + } + + #[no_mangle] + pub extern "C" fn pam_sm_close_session( + pamh: &mut PamHandle, + flags: PamFlag, + argc: c_int, + argv: *const *const c_char, + ) -> PamResultCode { + let args = extract_argv(argc, argv); + super::$ident::sm_close_session(pamh, args, flags) + } + + #[no_mangle] + pub extern "C" fn pam_sm_open_session( + pamh: &mut PamHandle, + flags: PamFlag, + argc: c_int, + argv: *const *const c_char, + ) -> PamResultCode { + let args = extract_argv(argc, argv); + super::$ident::sm_open_session(pamh, args, flags) + } + + #[no_mangle] + pub extern "C" fn pam_sm_setcred( + pamh: &mut PamHandle, + flags: PamFlag, + argc: c_int, + argv: *const *const c_char, + ) -> PamResultCode { + let args = extract_argv(argc, argv); + super::$ident::sm_setcred(pamh, args, flags) + } + } + }; +} + +#[macro_export] +macro_rules! pam_try { + ($r:expr) => { + match $r { + Ok(t) => t, + Err(e) => return e, + } + }; + ($r:expr, $e:expr) => { + match $r { + Ok(t) => t, + Err(_) => return $e, + } + }; +} + +#[cfg(test)] +pub mod test { + use module::PamHooks; + + struct Foo; + impl PamHooks for Foo {} + + pam_hooks!(Foo); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/module.rs Tue Apr 15 00:50:23 2025 -0400 @@ -0,0 +1,303 @@ +//! Functions for use in pam modules. + +use libc::c_char; +use std::ffi::{CStr, CString}; + +use constants::{PamFlag, PamResultCode}; +use items::ItemType; + +/// Opaque type, used as a pointer when making pam API calls. +/// +/// A module is invoked via an external function such as `pam_sm_authenticate`. +/// Such a call provides a pam handle pointer. The same pointer should be given +/// as an argument when making API calls. +#[repr(C)] +pub struct PamHandle { + _data: [u8; 0], +} + +#[link(name = "pam")] +extern "C" { + fn pam_get_data( + pamh: *const PamHandle, + module_data_name: *const c_char, + data: &mut *const libc::c_void, + ) -> PamResultCode; + + fn pam_set_data( + pamh: *const PamHandle, + module_data_name: *const c_char, + data: *mut libc::c_void, + cleanup: extern "C" fn( + pamh: *const PamHandle, + data: *mut libc::c_void, + error_status: PamResultCode, + ), + ) -> PamResultCode; + + fn pam_get_item( + pamh: *const PamHandle, + item_type: ItemType, + item: &mut *const libc::c_void, + ) -> PamResultCode; + + fn pam_set_item( + pamh: *mut PamHandle, + item_type: ItemType, + item: *const libc::c_void, + ) -> PamResultCode; + + fn pam_get_user( + pamh: *const PamHandle, + user: &*mut c_char, + prompt: *const c_char, + ) -> PamResultCode; + + fn pam_get_authtok( + pamh: *const PamHandle, + item_type: ItemType, + data: &*mut c_char, + prompt: *const c_char, + ) -> PamResultCode; + +} + +pub extern "C" fn cleanup<T>(_: *const PamHandle, c_data: *mut libc::c_void, _: PamResultCode) { + unsafe { + let _data: Box<T> = Box::from_raw(c_data.cast::<T>()); + } +} + +pub type PamResult<T> = Result<T, PamResultCode>; + +impl PamHandle { + /// Gets some value, identified by `key`, that has been set by the module + /// previously. + /// + /// See the [`pam_get_data` manual page]( + /// https://www.man7.org/linux/man-pages/man3/pam_get_data.3.html). + /// + /// # Errors + /// + /// Returns an error if the underlying PAM function call fails. + /// + /// # Safety + /// + /// The data stored under the provided key must be of type `T` otherwise the + /// behaviour of this function is undefined. + pub unsafe fn get_data<T>(&self, key: &str) -> PamResult<&T> { + let c_key = CString::new(key).unwrap(); + let mut ptr: *const libc::c_void = std::ptr::null(); + let res = pam_get_data(self, c_key.as_ptr(), &mut ptr); + if PamResultCode::PAM_SUCCESS == res && !ptr.is_null() { + let typed_ptr = ptr.cast::<T>(); + let data: &T = &*typed_ptr; + Ok(data) + } else { + Err(res) + } + } + + /// Stores a value that can be retrieved later with `get_data`. The value lives + /// as long as the current pam cycle. + /// + /// See the [`pam_set_data` manual page]( + /// https://www.man7.org/linux/man-pages/man3/pam_set_data.3.html). + /// + /// # Errors + /// + /// Returns an error if the underlying PAM function call fails. + pub fn set_data<T>(&self, key: &str, data: Box<T>) -> PamResult<()> { + let c_key = CString::new(key).unwrap(); + let res = unsafe { + pam_set_data( + self, + c_key.as_ptr(), + Box::into_raw(data).cast::<libc::c_void>(), + cleanup::<T>, + ) + }; + to_result(res) + } + + /// Retrieves a value that has been set, possibly by the pam client. This is + /// particularly useful for getting a `PamConv` reference. + /// + /// See the [`pam_get_item` manual page]( + /// https://www.man7.org/linux/man-pages/man3/pam_get_item.3.html). + /// + /// # Errors + /// + /// Returns an error if the underlying PAM function call fails. + pub fn get_item<T: crate::items::Item>(&self) -> PamResult<Option<T>> { + let mut ptr: *const libc::c_void = std::ptr::null(); + let (res, item) = unsafe { + let r = pam_get_item(self, T::type_id(), &mut ptr); + let typed_ptr = ptr.cast::<T::Raw>(); + let t = if typed_ptr.is_null() { + None + } else { + Some(T::from_raw(typed_ptr)) + }; + (r, t) + }; + match res { + PamResultCode::PAM_SUCCESS => Ok(item), + other => Err(other), + } + } + + /// Sets a value in the pam context. The value can be retrieved using + /// `get_item`. + /// + /// Note that all items are strings, except `PAM_CONV` and `PAM_FAIL_DELAY`. + /// + /// See the [`pam_set_item` manual page]( + /// https://www.man7.org/linux/man-pages/man3/pam_set_item.3.html). + /// + /// # Errors + /// + /// Returns an error if the underlying PAM function call fails. + /// + /// # Panics + /// + /// Panics if the provided item key contains a nul byte. + pub fn set_item_str<T: crate::items::Item>(&mut self, item: T) -> PamResult<()> { + let res = + unsafe { pam_set_item(self, T::type_id(), item.into_raw().cast::<libc::c_void>()) }; + to_result(res) + } + + /// Retrieves the name of the user who is authenticating or logging in. + /// + /// This is really a specialization of `get_item`. + /// + /// See the [`pam_get_user` manual page]( + /// https://www.man7.org/linux/man-pages/man3/pam_get_user.3.html). + /// + /// # Errors + /// + /// Returns an error if the underlying PAM function call fails. + /// + /// # Panics + /// + /// Panics if the provided prompt string contains a nul byte. + pub fn get_user(&self, prompt: Option<&str>) -> PamResult<String> { + let prompt_string; + let c_prompt = match prompt { + Some(p) => { + prompt_string = CString::new(p).unwrap(); + prompt_string.as_ptr() + } + None => std::ptr::null(), + }; + let output: *mut c_char = std::ptr::null_mut(); + let res = unsafe { pam_get_user(self, &output, c_prompt) }; + match res { + PamResultCode::PAM_SUCCESS => copy_pam_string(output), + otherwise => Err(otherwise), + } + } + + /// Retrieves the authentication token from the user. + /// + /// This is really a specialization of `get_item`. + /// + /// See the [`pam_get_authtok` manual page]( + /// https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html). + /// + /// # Errors + /// + /// Returns an error if the underlying PAM function call fails. + /// + /// # Panics + /// + /// Panics if the provided prompt string contains a nul byte. + pub fn get_authtok(&self, prompt: Option<&str>) -> PamResult<String> { + let prompt_string; + let c_prompt = match prompt { + Some(p) => { + prompt_string = CString::new(p).unwrap(); + prompt_string.as_ptr() + } + None => std::ptr::null(), + }; + let output: *mut c_char = std::ptr::null_mut(); + let res = unsafe { pam_get_authtok(self, ItemType::AuthTok, &output, c_prompt) }; + match res { + PamResultCode::PAM_SUCCESS => copy_pam_string(output), + otherwise => Err(otherwise), + } + } +} + +/// Creates an owned copy of a string that is returned from a +/// <code>pam_get_<var>whatever</var></code> function. +fn copy_pam_string(result_ptr: *const c_char) -> PamResult<String> { + // We really shouldn't get a null pointer back here, but if we do, return nothing. + if result_ptr.is_null() { + return Ok(String::from("")); + } + let bytes = unsafe { CStr::from_ptr(result_ptr).to_bytes() }; + String::from_utf8(bytes.to_vec()).map_err(|_| PamResultCode::PAM_CONV_ERR) +} + +/// Convenience to transform a `PamResultCode` into a unit `PamResult`. +fn to_result(result: PamResultCode) -> PamResult<()> { + match result { + PamResultCode::PAM_SUCCESS => Ok(()), + otherwise => Err(otherwise), + } +} + +/// Provides functions that are invoked by the entrypoints generated by the +/// [`pam_hooks!` macro](../macro.pam_hooks.html). +/// +/// All hooks are ignored by PAM dispatch by default given the default return value of `PAM_IGNORE`. +/// Override any functions that you want to handle with your module. See [PAM’s root manual page]( +/// https://www.man7.org/linux/man-pages/man3/pam.3.html). +#[allow(unused_variables)] +pub trait PamHooks { + /// This function performs the task of establishing whether the user is permitted to gain access at + /// this time. It should be understood that the user has previously been validated by an + /// authentication module. This function checks for other things. Such things might be: the time of + /// day or the date, the terminal line, remote hostname, etc. This function may also determine + /// things like the expiration on passwords, and respond that the user change it before continuing. + fn acct_mgmt(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { + PamResultCode::PAM_IGNORE + } + + /// This function performs the task of authenticating the user. + fn sm_authenticate(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { + PamResultCode::PAM_IGNORE + } + + /// This function is used to (re-)set the authentication token of the user. + /// + /// The PAM library calls this function twice in succession. The first time with + /// `PAM_PRELIM_CHECK` and then, if the module does not return `PAM_TRY_AGAIN`, subsequently with + /// `PAM_UPDATE_AUTHTOK`. It is only on the second call that the authorization token is + /// (possibly) changed. + fn sm_chauthtok(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { + PamResultCode::PAM_IGNORE + } + + /// This function is called to terminate a session. + fn sm_close_session(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { + PamResultCode::PAM_IGNORE + } + + /// This function is called to commence a session. + fn sm_open_session(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { + PamResultCode::PAM_IGNORE + } + + /// This function performs the task of altering the credentials of the user with respect to the + /// corresponding authorization scheme. Generally, an authentication module may have access to more + /// information about a user than their authentication token. This function is used to make such + /// information available to the application. It should only be called after the user has been + /// authenticated but before a session has been established. + fn sm_setcred(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { + PamResultCode::PAM_IGNORE + } +}