This commit is contained in:
2025-07-26 10:10:52 +08:00
parent 9f83969584
commit 81c11d3f30
21 changed files with 1095 additions and 1 deletions

2
Cargo.toml Normal file
View File

@@ -0,0 +1,2 @@
[workspace]
members = ["pam", "pam-sober", "pam-http"]

22
LICENSE Normal file
View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 TOZNY
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,2 +1,25 @@
# pam-rs pam-rs
========
Rust 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.
## 🌐 [pam-http](pam-http)
An example of using pam-rs by performing HTTP basic access auth to authenticate users.
## 🍻 [pam-sober](pam-sober)
If you aren't sober enough for basic math, you can't login!
### Credits
The contents of this repo are heavily borrowed from:
- [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)

12
pam-http/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[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"] }

12
pam-http/Justfile Normal file
View File

@@ -0,0 +1,12 @@
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

35
pam-http/README.md Normal file
View File

@@ -0,0 +1,35 @@
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.

2
pam-http/conf/http-auth Normal file
View File

@@ -0,0 +1,2 @@
auth sufficient pam_http.so url=http://localhost:3000
account sufficient pam_http.so

88
pam-http/src/lib.rs Normal file
View File

@@ -0,0 +1,88 @@
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())
}

52
pam-http/test.c Normal file
View File

@@ -0,0 +1,52 @@
#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;
}

12
pam-sober/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[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"

12
pam-sober/Justfile Normal file
View File

@@ -0,0 +1,12 @@
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

View File

@@ -0,0 +1,2 @@
auth sufficient pam_sober.so
account sufficient pam_sober.so

73
pam-sober/src/lib.rs Normal file
View File

@@ -0,0 +1,73 @@
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
}
}

53
pam-sober/test.c Normal file
View File

@@ -0,0 +1,53 @@
#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;
}

16
pam/Cargo.toml Normal file
View File

@@ -0,0 +1,16 @@
[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"

66
pam/src/constants.rs Normal file
View File

@@ -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,
}

94
pam/src/conv.rs Normal file
View File

@@ -0,0 +1,94 @@
use libc::{c_char, c_int};
use std::ffi::{CStr, CString};
use std::ptr;
use constants::PamResultCode;
use constants::PamMessageStyle;
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<'a> Conv<'a> {
/// 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<'a> Item for Conv<'a> {
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 _
}
}

88
pam/src/items.rs Normal file
View File

@@ -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);

34
pam/src/lib.rs Executable file
View File

@@ -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]: 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;

141
pam/src/macros.rs Normal file
View File

@@ -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);
}

255
pam/src/module.rs Executable file
View File

@@ -0,0 +1,255 @@
//! Functions for use in pam modules.
use libc::c_char;
use std::ffi::{CStr, CString};
use constants::{PamFlag, PamResultCode};
/// 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: crate::items::ItemType,
item: &mut *const libc::c_void,
) -> PamResultCode;
fn pam_set_item(
pamh: *mut PamHandle,
item_type: crate::items::ItemType,
item: *const libc::c_void,
) -> PamResultCode;
fn pam_get_user(
pamh: *const PamHandle,
user: &*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 `pam_get_data` in
/// http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.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 funtion is undefined.
pub unsafe fn get_data<'a, T>(&'a self, key: &str) -> PamResult<&'a 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 `pam_set_data` in
/// http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.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>,
)
};
if PamResultCode::PAM_SUCCESS == res {
Ok(())
} else {
Err(res)
}
}
/// Retrieves a value that has been set, possibly by the pam client. This is
/// particularly useful for getting a `PamConv` reference.
///
/// See `pam_get_item` in
/// http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.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)
};
if PamResultCode::PAM_SUCCESS == res {
Ok(item)
} else {
Err(res)
}
}
/// 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 `pam_set_item` in
/// http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.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>())};
if PamResultCode::PAM_SUCCESS == res {
Ok(())
} else {
Err(res)
}
}
/// Retrieves the name of the user who is authenticating or logging in.
///
/// This is really a specialization of `get_item`.
///
/// See `pam_get_user` in
/// http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.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 ptr: *mut c_char = std::ptr::null_mut();
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 res = unsafe { pam_get_user(self, &ptr, c_prompt) };
if PamResultCode::PAM_SUCCESS == res && !ptr.is_null() {
let const_ptr = ptr as *const c_char;
let bytes = unsafe { CStr::from_ptr(const_ptr).to_bytes() };
String::from_utf8(bytes.to_vec()).map_err(|_| PamResultCode::PAM_CONV_ERR)
} else {
Err(res)
}
}
}
/// Provides functions that are invoked by the entrypoints generated by the
/// [`pam_hooks!` macro](../macro.pam_hooks.html).
///
/// All of 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
}
}