├── .clippy.toml ├── .gitignore ├── .travis.yml ├── Cargo.toml ├── License ├── readme.md ├── src ├── lib.rs ├── libpam.rs ├── pam.rs └── pam_types.rs └── test-module ├── Cargo.toml └── src └── lib.rs /.clippy.toml: -------------------------------------------------------------------------------- 1 | msrv = "1.32.0" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | rust: 4 | - 1.32.0 # minimum supported toolchain 5 | - stable 6 | - beta 7 | - nightly 8 | 9 | matrix: 10 | allow_failures: 11 | - rust: nightly 12 | 13 | before_script: 14 | - bash -c 'if [[ "$TRAVIS_RUST_VERSION" == "$CLIPPY_RUST_VERSION" ]]; then 15 | rustup component add clippy-preview; 16 | fi' 17 | 18 | script: 19 | - cargo test --all-features 20 | - bash -c 'if [[ "$TRAVIS_RUST_VERSION" == "$CLIPPY_RUST_VERSION" ]]; then 21 | cargo clippy --all-features -- -D warnings; 22 | fi' 23 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pamsm" 3 | version = "0.5.5" 4 | readme = "readme.md" 5 | description = "Rust wrappers around PAM Service Modules functions" 6 | authors = ["Raphael Catolino "] 7 | license = "GPL-3.0" 8 | homepage = "https://github.com/rcatolino/pam_sm_rust" 9 | keywords = ["pam", "service", "module", "wrapper", "ffi"] 10 | categories = ["os::unix-apis", "authentication", "api-bindings"] 11 | 12 | [dev-dependencies] 13 | time = "^0.2" 14 | 15 | [dependencies] 16 | bitflags = "1.0" 17 | 18 | [features] 19 | libpam = [] 20 | 21 | [package.metadata.release] 22 | sign-commit = true 23 | upload-doc = false 24 | disable-publish = true 25 | disable-push = true 26 | pre-release-commit-message = "cargo: pamsm release {{version}}" 27 | pro-release-commit-message = "cargo: version bump to {{version}}" 28 | tag-message = "pams {{version}}" 29 | 30 | [package.metadata.docs.rs] 31 | features = ["libpam"] 32 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 raphael.catolino@gmail.com 2 | 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 3 of the License, or 6 | (at your option) any later version. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | You should have received a copy of the GNU General Public License 14 | along with this program. If not, see . 15 | 16 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # PAM SM 2 | 3 | [![Crates.io version shield](https://img.shields.io/crates/v/pamsm.svg)](https://crates.io/crates/pamsm) 4 | [![Crates.io license shield](https://img.shields.io/crates/l/pamsm.svg)](https://crates.io/crates/pamsm) 5 | 6 | Rust FFI wrapper to implement PAM service modules for Linux. 7 | 8 | **[Documentation](https://docs.rs/pamsm/) -** 9 | **[Cargo](https://crates.io/crates/pamsm) -** 10 | **[Repository](https://github.com/rcatolino/pam_sm_rust)** 11 | 12 | ## Features 13 | 14 | This crate supports the following optional features: 15 | * `libpam`: this enables the extension trait `PamLibExt` and linking against `libpam.so` for its native implementation. 16 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016 Raphael Catolino 2 | 3 | //! PAM Service Module wrappers 4 | //! # Usage 5 | //! For example, here is a time based authentication module : 6 | //! 7 | //! ```rust,no_run 8 | //! #[macro_use] extern crate pamsm; 9 | //! extern crate time; 10 | //! 11 | //! use pamsm::{PamServiceModule, Pam, PamFlags, PamError}; 12 | //! 13 | //! struct PamTime; 14 | //! 15 | //! impl PamServiceModule for PamTime { 16 | //! fn authenticate(pamh: Pam, _: PamFlags, args: Vec) -> PamError { 17 | //! let hour = time::OffsetDateTime::now_utc().hour(); 18 | //! if hour != 4 { 19 | //! // Only allow authentication when it's 4 AM 20 | //! PamError::SUCCESS 21 | //! } else { 22 | //! PamError::AUTH_ERR 23 | //! } 24 | //! } 25 | //! } 26 | //! 27 | //! pam_module!(PamTime); 28 | //! ``` 29 | #[macro_use] 30 | extern crate bitflags; 31 | 32 | #[cfg(feature = "libpam")] 33 | mod libpam; 34 | mod pam; 35 | mod pam_types; 36 | 37 | pub use pam::{Pam, PamError, PamFlags, PamSendRef, PamServiceModule}; 38 | 39 | #[cfg(feature = "libpam")] 40 | pub use libpam::{PamCleanupCb, PamData, PamLibExt, PamResult}; 41 | #[cfg(feature = "libpam")] 42 | pub use pam_types::{LogLvl, PamMsgStyle}; 43 | -------------------------------------------------------------------------------- /src/libpam.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use pam::{Pam, PamError, PamFlags}; 4 | use pam_types::{LogLvl, PamConv, PamHandle, PamItemType, PamMessage, PamMsgStyle, PamResponse}; 5 | use std::ffi::{CStr, CString, NulError}; 6 | use std::ops::Deref; 7 | use std::option::Option; 8 | use std::os::raw::{c_char, c_int, c_void}; 9 | use std::ptr; 10 | 11 | pub type PamResult = Result; 12 | /// Prototype of the callback used with [`PamLibExt::send_bytes`] 13 | pub type PamCleanupCb = fn(&Vec, Pam, PamFlags, PamError); 14 | 15 | #[derive(Clone)] 16 | struct PamByteData { 17 | cb: Option, 18 | data: Vec, 19 | } 20 | 21 | /// Trait to implement for data stored with pam using [`PamLibExt::send_data`] 22 | /// in order to provide a cleanup callback. 23 | /// # Example 24 | /// ``` 25 | /// extern crate pamsm; 26 | /// use pamsm::{Pam, PamData, PamError, PamFlags}; 27 | /// use std::fs::write; 28 | /// 29 | /// struct Token([u8; 32]); 30 | /// 31 | /// impl PamData for Token { 32 | /// fn cleanup(&self, _pam: Pam, flags: PamFlags, status: PamError) { 33 | /// if !flags.contains(PamFlags::DATA_REPLACE) && status == PamError::SUCCESS { 34 | /// match write(".token.bin", self.0) { 35 | /// Ok(_) => (), 36 | /// Err(err) => { 37 | /// if !flags.contains(PamFlags::SILENT) { 38 | /// println!("Error persisting token : {:?}", err); 39 | /// } 40 | /// } 41 | /// }; 42 | /// } 43 | /// } 44 | /// } 45 | /// ``` 46 | pub trait PamData { 47 | /// The cleanup method will be called before the data is dropped by pam. 48 | /// See `pam_set_data (3)` 49 | fn cleanup(&self, _pam: Pam, _flags: PamFlags, _status: PamError) {} 50 | } 51 | 52 | impl PamData for PamByteData { 53 | fn cleanup(&self, pam: Pam, flags: PamFlags, status: PamError) { 54 | if let Some(cb) = self.cb { 55 | (cb)(&self.data, pam, flags, status); 56 | } 57 | } 58 | } 59 | 60 | /// Blanket implementation for types that implement `Deref` when `T` implements `PamData`. 61 | impl PamData for U 62 | where 63 | U: Deref, 64 | { 65 | fn cleanup(&self, pam: Pam, flags: PamFlags, status: PamError) { 66 | T::cleanup(self, pam, flags, status) 67 | } 68 | } 69 | 70 | impl PamError { 71 | fn to_result(self, ok: T) -> PamResult { 72 | if self == PamError::SUCCESS { 73 | Ok(ok) 74 | } else { 75 | Err(self) 76 | } 77 | } 78 | } 79 | 80 | /// This contains a private marker trait, used to seal private traits. 81 | mod private { 82 | pub trait Sealed {} 83 | impl Sealed for super::Pam {} 84 | } 85 | 86 | impl Pam { 87 | // End users should call the item specific methods 88 | fn get_cstr_item(&self, item_type: PamItemType) -> PamResult> { 89 | match item_type { 90 | PamItemType::CONV | PamItemType::FAIL_DELAY | PamItemType::XAUTHDATA => { 91 | panic!("Error, get_cstr_item can only be used with pam item returning c-strings") 92 | } 93 | _ => (), 94 | } 95 | let mut raw_item: *const c_void = ptr::null(); 96 | let r = unsafe { PamError::new(pam_get_item(self.0, item_type as c_int, &mut raw_item)) }; 97 | if raw_item.is_null() { 98 | r.to_result(None) 99 | } else { 100 | // pam should keep the underlying token allocated during the lifetime of the module 101 | r.to_result(Some(unsafe { CStr::from_ptr(raw_item as *const c_char) })) 102 | } 103 | } 104 | } 105 | 106 | /// Extension trait over `Pam`, usually provided by the `libpam` shared library. 107 | pub trait PamLibExt: private::Sealed { 108 | /// Get the username. If the PAM_USER item is not set, this function 109 | /// prompts for a username (like get_authtok). 110 | /// Returns PamError::SERVICE_ERR if the prompt contains any null byte 111 | fn get_user(&self, prompt: Option<&str>) -> PamResult>; 112 | 113 | /// Get the username, i.e. the PAM_USER item. If it's not set return None. 114 | fn get_cached_user(&self) -> PamResult>; 115 | 116 | /// Get the cached authentication token. 117 | fn get_cached_authtok(&self) -> PamResult>; 118 | 119 | /// Get the cached old authentication token. 120 | fn get_cached_oldauthtok(&self) -> PamResult>; 121 | 122 | /// Get the cached authentication token or prompt the user for one if there isn't any. 123 | /// Returns PamError::SERVICE_ERR if the prompt contains any null byte 124 | fn get_authtok(&self, prompt: Option<&str>) -> PamResult>; 125 | 126 | fn set_authtok(&self, authtok: &CString) -> PamResult<()>; 127 | 128 | /// Get the remote hostname. 129 | fn get_rhost(&self) -> PamResult>; 130 | 131 | /// Get the remote username. 132 | fn get_ruser(&self) -> PamResult>; 133 | 134 | /// Get the service name. 135 | fn get_service(&self) -> PamResult>; 136 | 137 | /// Prompt the user for custom input. 138 | /// Returns PamError::SERVICE_ERR if the prompt contains any null byte 139 | fn conv(&self, prompt: Option<&str>, style: PamMsgStyle) -> PamResult>; 140 | 141 | /// Get a variable from the pam environment list. 142 | fn getenv(&self, name: &str) -> PamResult>; 143 | 144 | /// Put a variable in the pam environment list. 145 | /// `name_value` takes for form documented in pam_putent(3) : 146 | /// 147 | /// - `NAME=value` will set variable `NAME` to value `value` 148 | /// - `NAME=` will set variable `NAME` to an empty value 149 | /// - `NAME` will unset the variable `NAME` 150 | fn putenv(&self, name_value: &str) -> PamResult<()>; 151 | 152 | /// Send data to be stored by the pam library under the name `module_name`. 153 | /// The data can then be retrieved from a different 154 | /// callback in this module, or even by a different module 155 | /// using [`retrieve_data`][Self::retrieve_data]. 156 | /// 157 | /// When this method is called a second time with the same `module_name`, the method 158 | /// [`PamData::cleanup`] is called on the data previously stored. 159 | /// The same happens when the application calls `pam_end (3)` 160 | /// 161 | /// If your data can be converted into / from [`Vec`][std::vec::Vec] 162 | /// you should consider using the [`send_bytes`][Self::send_bytes] method instead. 163 | /// 164 | /// # Safety 165 | /// This method should not be used if the [`send_bytes`][Self::send_bytes] method is also used 166 | /// with the same `module_name`. 167 | unsafe fn send_data( 168 | &self, 169 | module_name: &str, 170 | data: T, 171 | ) -> PamResult<()>; 172 | 173 | /// Retrieve data previously stored with [`send_data`][Self::send_data]. 174 | /// 175 | /// Note that the result is a _copy_ of the data and not a shared reference, 176 | /// which differs from the behavior of the underlying `pam_get_data (3)` function. 177 | /// 178 | /// If you want to share the data instead you can wrap it in [`Arc`][std::sync::Arc]. 179 | /// # Safety 180 | /// The type parameter `T` must be the same as the one used in 181 | /// [`send_data`][Self::send_data] with the name `module_name`. 182 | /// 183 | /// If the data was stored with [`send_bytes`][Self::send_bytes] you must use 184 | /// [`retrieve_bytes`][Self::retrieve_bytes] instead. 185 | unsafe fn retrieve_data(&self, module_name: &str) -> PamResult; 186 | 187 | /// Similar to [`send_data`][Self::send_data], but only works with [`Vec`][std::vec::Vec]. 188 | /// The PamData trait doesn't have to be implemented on the data, a callback can be passed 189 | /// as an argument instead. 190 | fn send_bytes( 191 | &self, 192 | module_name: &str, 193 | data: Vec, 194 | cb: Option, 195 | ) -> PamResult<()>; 196 | 197 | /// Retrieve bytes previously stored with [`send_bytes`][Self::send_bytes]. 198 | /// The result is a clone of the data. 199 | fn retrieve_bytes(&self, module_name: &str) -> PamResult>; 200 | 201 | /// Send a message to syslog. 202 | fn syslog(&self, lvl: LogLvl, msg: &str) -> PamResult<()>; 203 | } 204 | 205 | impl From for PamError { 206 | fn from(_: NulError) -> PamError { 207 | PamError::SERVICE_ERR 208 | } 209 | } 210 | 211 | impl PamLibExt for Pam { 212 | fn get_user(&self, prompt: Option<&str>) -> PamResult> { 213 | let cprompt = match prompt { 214 | None => None, 215 | Some(p) => Some(CString::new(p)?), 216 | }; 217 | let mut raw_user: *const c_char = ptr::null(); 218 | let r = unsafe { 219 | PamError::new(pam_get_user( 220 | self.0, 221 | &mut raw_user, 222 | cprompt.as_ref().map_or(ptr::null(), |p| p.as_ptr()), 223 | )) 224 | }; 225 | 226 | if raw_user.is_null() { 227 | r.to_result(None) 228 | } else { 229 | r.to_result(Some(unsafe { CStr::from_ptr(raw_user) })) 230 | } 231 | } 232 | 233 | fn get_cached_user(&self) -> PamResult> { 234 | self.get_cstr_item(PamItemType::USER) 235 | } 236 | 237 | fn get_cached_authtok(&self) -> PamResult> { 238 | self.get_cstr_item(PamItemType::AUTHTOK) 239 | } 240 | 241 | fn get_cached_oldauthtok(&self) -> PamResult> { 242 | self.get_cstr_item(PamItemType::OLDAUTHTOK) 243 | } 244 | 245 | fn get_authtok(&self, prompt: Option<&str>) -> PamResult> { 246 | let cprompt = match prompt { 247 | None => None, 248 | Some(p) => Some(CString::new(p)?), 249 | }; 250 | let mut raw_at: *const c_char = ptr::null(); 251 | let r = unsafe { 252 | PamError::new(pam_get_authtok( 253 | self.0, 254 | PamItemType::AUTHTOK as i32, 255 | &mut raw_at, 256 | cprompt.as_ref().map_or(ptr::null(), |p| p.as_ptr()), 257 | )) 258 | }; 259 | 260 | if raw_at.is_null() { 261 | r.to_result(None) 262 | } else { 263 | r.to_result(unsafe { Some(CStr::from_ptr(raw_at)) }) 264 | } 265 | } 266 | 267 | fn set_authtok(&self, authtok: &CString) -> PamResult<()> { 268 | unsafe { 269 | set_item( 270 | self.0, 271 | PamItemType::AUTHTOK, 272 | authtok.as_ptr() as *const c_void, 273 | ) 274 | } 275 | } 276 | 277 | fn get_rhost(&self) -> PamResult> { 278 | self.get_cstr_item(PamItemType::RHOST) 279 | } 280 | 281 | fn get_ruser(&self) -> PamResult> { 282 | self.get_cstr_item(PamItemType::RUSER) 283 | } 284 | 285 | fn get_service(&self) -> PamResult> { 286 | self.get_cstr_item(PamItemType::SERVICE) 287 | } 288 | 289 | fn conv(&self, prompt: Option<&str>, style: PamMsgStyle) -> PamResult> { 290 | let mut conv_pointer: *const c_void = ptr::null(); 291 | let r = unsafe { 292 | PamError::new(pam_get_item( 293 | self.0, 294 | PamItemType::CONV as c_int, 295 | &mut conv_pointer, 296 | )) 297 | }; 298 | 299 | if r != PamError::SUCCESS { 300 | return Err(r); 301 | } 302 | 303 | if conv_pointer.is_null() { 304 | return Ok(None); 305 | } 306 | 307 | let conv = unsafe { &*(conv_pointer as *const PamConv) }; 308 | let mut resp_ptr: *mut PamResponse = ptr::null_mut(); 309 | let msg_cstr = CString::new(prompt.unwrap_or(""))?; 310 | let msg = PamMessage { 311 | msg_style: style, 312 | msg: msg_cstr.as_ptr(), 313 | }; 314 | 315 | match conv.cb.map(|cb| { 316 | PamError::new(cb( 317 | 1, 318 | &mut (&msg as *const PamMessage), 319 | &mut resp_ptr, 320 | conv.appdata_ptr, 321 | )) 322 | }) { 323 | Some(PamError::SUCCESS) => { 324 | Ok(unsafe { (*resp_ptr).resp }.map(|r| unsafe { CStr::from_ptr(r.as_ptr()) })) 325 | } 326 | Some(ret) => Err(ret), 327 | None => Ok(None), 328 | } 329 | } 330 | 331 | fn getenv(&self, name: &str) -> PamResult> { 332 | let cname = CString::new(name)?; 333 | let cenv = unsafe { pam_getenv(self.0, cname.as_ptr()) }; 334 | 335 | if cenv.is_null() { 336 | Ok(None) 337 | } else { 338 | unsafe { Ok(Some(CStr::from_ptr(cenv))) } 339 | } 340 | } 341 | 342 | fn putenv(&self, name_value: &str) -> PamResult<()> { 343 | let cenv = CString::new(name_value)?; 344 | unsafe { PamError::new(pam_putenv(self.0, cenv.as_ptr())).to_result(()) } 345 | } 346 | 347 | unsafe fn send_data( 348 | &self, 349 | module_name: &str, 350 | data: T, 351 | ) -> PamResult<()> { 352 | // The data has to be allocated on the heap because it will outlive the call stack. 353 | let data_copy = Box::new(data); 354 | PamError::new(pam_set_data( 355 | self.0, 356 | CString::new(module_name)?.as_ptr(), 357 | Box::into_raw(data_copy) as *mut c_void, 358 | Some(pam_data_cleanup::), 359 | )) 360 | .to_result(()) 361 | } 362 | 363 | unsafe fn retrieve_data(&self, module_name: &str) -> PamResult { 364 | let mut data_ptr: *const c_void = ptr::null(); 365 | // pam_get_data should be safe as long as T is the type that what used in send_data. 366 | PamError::new(pam_get_data( 367 | self.0, 368 | CString::new(module_name)?.as_ptr(), 369 | &mut data_ptr, 370 | )) 371 | .to_result(data_ptr as *const T) 372 | .map(|ptr| (*ptr).clone()) // pam guaranties the data is valid when SUCCESS is returned. 373 | } 374 | 375 | fn send_bytes( 376 | &self, 377 | module_name: &str, 378 | data: Vec, 379 | cb: Option, 380 | ) -> PamResult<()> { 381 | let data_cb = PamByteData { cb, data }; 382 | unsafe { self.send_data(module_name, data_cb) } 383 | } 384 | 385 | fn retrieve_bytes(&self, module_name: &str) -> PamResult> { 386 | unsafe { self.retrieve_data::(module_name) }.map(|data_cb| data_cb.data) 387 | } 388 | 389 | fn syslog(&self, lvl: LogLvl, msg: &str) -> PamResult<()> { 390 | let fmt = b"%s\0".as_ptr() as *const c_char; 391 | let cmsg = CString::new(msg)?; 392 | unsafe { 393 | pam_syslog(self.0, lvl as c_int, fmt, cmsg.as_ptr()); 394 | } 395 | Ok(()) 396 | } 397 | } 398 | 399 | unsafe extern "C" fn pam_data_cleanup( 400 | handle: PamHandle, 401 | data: *mut c_void, 402 | error_status: c_int, 403 | ) { 404 | Box::from_raw(data as *mut T).cleanup( 405 | Pam(handle), 406 | PamFlags::from_bits_truncate(error_status), 407 | PamError::new(error_status & 0xff), 408 | ); 409 | } 410 | 411 | unsafe fn set_item(pamh: PamHandle, item_type: PamItemType, item: *const c_void) -> PamResult<()> { 412 | PamError::new(pam_set_item(pamh, item_type as c_int, item)).to_result(()) 413 | } 414 | 415 | // Raw functions 416 | #[link(name = "pam")] 417 | extern "C" { 418 | pub fn pam_set_item(pamh: PamHandle, item_type: c_int, item: *const c_void) -> c_int; 419 | pub fn pam_get_item(pamh: PamHandle, item_type: c_int, item: *mut *const c_void) -> c_int; 420 | pub fn pam_strerror(pamh: PamHandle, errnum: c_int) -> *const c_char; 421 | pub fn pam_putenv(pamh: PamHandle, name_value: *const c_char) -> c_int; 422 | pub fn pam_getenv(pamh: PamHandle, name: *const c_char) -> *const c_char; 423 | pub fn pam_getenvlist(pamh: PamHandle) -> *mut *mut c_char; 424 | 425 | pub fn pam_set_data( 426 | pamh: PamHandle, 427 | module_data_name: *const c_char, 428 | data: *mut c_void, 429 | cleanup: Option, 430 | ) -> c_int; 431 | pub fn pam_get_data( 432 | pamh: PamHandle, 433 | module_data_name: *const c_char, 434 | data: *mut *const c_void, 435 | ) -> c_int; 436 | pub fn pam_get_user(pamh: PamHandle, user: *mut *const c_char, prompt: *const c_char) -> c_int; 437 | pub fn pam_get_authtok( 438 | pamh: PamHandle, 439 | item: c_int, 440 | authok_ptr: *mut *const c_char, 441 | prompt: *const c_char, 442 | ) -> c_int; 443 | 444 | pub fn pam_syslog(pamh: PamHandle, priority: c_int, fmt: *const c_char, ...) -> c_void; 445 | } 446 | -------------------------------------------------------------------------------- /src/pam.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 raphael.catolino@gmail.com 2 | 3 | #![allow(non_camel_case_types)] 4 | #![allow(clippy::upper_case_acronyms)] 5 | 6 | use pam_types::PamHandle; 7 | use std::fmt; 8 | use std::os::raw::c_int; 9 | 10 | /// Opaque PAM handle, with additional native methods available via `PamLibExt`. 11 | #[repr(transparent)] 12 | pub struct Pam(pub(crate) PamHandle); 13 | 14 | impl Pam { 15 | /// This allows sending the `Pam` handle to another thread. 16 | /// ```rust 17 | /// # use pamsm::Pam; 18 | /// # fn wrapper(pamh: &mut Pam) { 19 | /// std::thread::scope(|s| { 20 | /// let borrowed = pamh.as_send_ref(); 21 | /// s.spawn(move || { 22 | /// let pamh: &Pam = borrowed.into(); 23 | /// }); 24 | /// }); 25 | /// # } 26 | /// ``` 27 | /// Synchronized across multiple threads: 28 | /// ```rust 29 | /// # use pamsm::Pam; 30 | /// # fn wrapper(pamh: &mut Pam) { 31 | /// std::thread::scope(|s| { 32 | /// let shared_1 = std::sync::Arc::new(std::sync::Mutex::new(pamh.as_send_ref())); 33 | /// let shared_2 = shared_1.clone(); 34 | /// s.spawn(move || { 35 | /// let pamh: &Pam = &*shared_1.lock().unwrap(); 36 | /// }); 37 | /// s.spawn(move || { 38 | /// let pamh: &Pam = &*shared_2.lock().unwrap(); 39 | /// }); 40 | /// }); 41 | /// # } 42 | /// ``` 43 | pub fn as_send_ref(&mut self) -> PamSendRef<'_> { 44 | PamSendRef(self) 45 | } 46 | } 47 | 48 | impl<'a> From<&'a mut Pam> for PamSendRef<'a> { 49 | fn from(value: &'a mut Pam) -> Self { 50 | Self(value) 51 | } 52 | } 53 | 54 | /// This sendable reference to [`Pam`] can be created via [`Pam::as_send_ref`] or `From`/`Into`. 55 | pub struct PamSendRef<'a>(&'a mut Pam); 56 | 57 | unsafe impl<'a> Send for PamSendRef<'a> {} 58 | 59 | impl std::ops::Deref for PamSendRef<'_> { 60 | type Target = Pam; 61 | 62 | fn deref(&self) -> &Self::Target { 63 | self.0 64 | } 65 | } 66 | 67 | impl<'a> From> for &'a mut Pam { 68 | fn from(value: PamSendRef<'a>) -> Self { 69 | value.0 70 | } 71 | } 72 | 73 | impl<'a> From> for &'a Pam { 74 | fn from(value: PamSendRef<'a>) -> Self { 75 | value.0 76 | } 77 | } 78 | 79 | bitflags! { 80 | pub struct PamFlags : c_int { 81 | const DATA_REPLACE = 0x2000_0000; 82 | const SILENT = 0x8000; 83 | const DISALLOW_NULL_AUTHTOK = 0x0001; 84 | const ESTABLISH_CRED = 0x0002; 85 | const DELETE_CRED = 0x0004; 86 | const REINITIALIZE_CRED = 0x0008; 87 | const REFRESH_CRED = 0x0010; 88 | const CHANGE_EXPIRED_AUTHTOK = 0x0020; 89 | } 90 | } 91 | 92 | impl fmt::Display for PamError { 93 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 94 | write!(f, "{:?}", self) 95 | } 96 | } 97 | 98 | macro_rules! int_enum { 99 | ( $name:ident ($ukey:ident = $uvalue:expr) { 100 | $( $key:ident = $value:expr ),* 101 | }) => { 102 | #[derive(Clone, Copy, Debug, PartialEq)] 103 | pub enum $name { 104 | $( $key = $value, )* 105 | $ukey = $uvalue, 106 | } 107 | impl $name { 108 | #[cfg(feature = "libpam")] 109 | pub(crate) fn new(r: c_int) -> $name { 110 | match r { 111 | $( $value => $name::$key, )* 112 | _ => $name::$ukey, 113 | } 114 | } 115 | } 116 | } 117 | } 118 | 119 | int_enum! { 120 | PamError (UNKNOWN_RESULT = -1) { 121 | SUCCESS = 0, /* Successful function return */ 122 | OPEN_ERR = 1, /* dlopen() failure when dynamically */ 123 | SYMBOL_ERR = 2, /* Symbol not found */ 124 | SERVICE_ERR = 3, /* Error in service module */ 125 | SYSTEM_ERR = 4, /* System error */ 126 | BUF_ERR = 5, /* Memory buffer error */ 127 | PERM_DENIED = 6, /* Permission denied */ 128 | AUTH_ERR = 7, /* Authentication failure */ 129 | CRED_INSUFFICIENT = 8, /* Can not access authentication data */ 130 | AUTHINFO_UNAVAIL = 9, /* Underlying authentication service can not retrieve authentication information */ 131 | USER_UNKNOWN = 10, /* User not known to the underlying authenticaiton module */ 132 | MAXTRIES = 11, /* An authentication service has maintained a retry count which has been reached. No further retries should be attempted */ 133 | NEW_AUTHTOK_REQD = 12, /* New authentication token required. */ 134 | ACCT_EXPIRED = 13, /* User account has expired */ 135 | SESSION_ERR = 14, /* Can not make/remove an entry for the specified session */ 136 | CRED_UNAVAIL = 15, /* Underlying authentication service can not retrieve user credentials */ 137 | CRED_EXPIRED = 16, /* User credentials expired */ 138 | CRED_ERR = 17, /* Failure setting user credentials */ 139 | NO_MODULE_DATA = 18, /* No module specific data is present */ 140 | CONV_ERR = 19, /* Conversation error */ 141 | AUTHTOK_ERR = 20, /* Authentication token manipulation error */ 142 | AUTHTOK_RECOVERY_ERR = 21, /* Authentication information cannot be recovered */ 143 | AUTHTOK_LOCK_BUSY = 22, /* Authentication token lock busy */ 144 | AUTHTOK_DISABLE_AGING = 23, /* Authentication token aging disabled */ 145 | TRY_AGAIN = 24, /* Preliminary check by password service */ 146 | IGNORE = 25, /* Ignore underlying account module regardless of whether the control flag is required, optional, or sufficient */ 147 | ABORT = 26, /* Critical error (?module fail now request) */ 148 | AUTHTOK_EXPIRED = 27, /* user's authentication token has expired */ 149 | MODULE_UNKNOWN = 28, /* module is not known */ 150 | BAD_ITEM = 29, /* Bad item passed to *_item() */ 151 | CONV_AGAIN = 30, /* conversation function is event driven and data is not available yet */ 152 | INCOMPLETE = 31 /* please call this function again to complete authentication stack. Before calling again, verify that conversation is completed */ 153 | } 154 | } 155 | 156 | /// Default service module implementation. 157 | /// All default functions return SERVICE_ERR. 158 | /// You can override functions depending on what kind of module you implement. 159 | /// See the respective pam_sm_* man pages for documentation. 160 | pub trait PamServiceModule { 161 | fn open_session(_: Pam, _: PamFlags, _: Vec) -> PamError { 162 | PamError::SERVICE_ERR 163 | } 164 | 165 | fn close_session(_: Pam, _: PamFlags, _: Vec) -> PamError { 166 | PamError::SERVICE_ERR 167 | } 168 | 169 | fn authenticate(_: Pam, _: PamFlags, _: Vec) -> PamError { 170 | PamError::SERVICE_ERR 171 | } 172 | 173 | fn setcred(_: Pam, _: PamFlags, _: Vec) -> PamError { 174 | PamError::SERVICE_ERR 175 | } 176 | 177 | fn acct_mgmt(_: Pam, _: PamFlags, _: Vec) -> PamError { 178 | PamError::SERVICE_ERR 179 | } 180 | 181 | fn chauthtok(_: Pam, _: PamFlags, _: Vec) -> PamError { 182 | PamError::SERVICE_ERR 183 | } 184 | } 185 | 186 | /// Define entrypoints for the PAM module. 187 | /// 188 | /// This macro must be called exactly once in a PAM module. 189 | /// It then exports all the pam_sm_* symbols. 190 | /// 191 | /// The argument to the macro is a type implementing the 192 | /// `PamServiceModule` trait. 193 | /// 194 | /// # Example 195 | /// 196 | /// ```ignore 197 | /// // lib.rs 198 | /// #[macro_use] extern crate pamsm; 199 | /// 200 | /// pam_module!(MyPamService); 201 | /// ``` 202 | #[macro_export] 203 | macro_rules! pam_module { 204 | ($pamsm_ty:ty) => { 205 | // Check trait bound on input type. 206 | fn _check_pamsm_trait() {} 207 | fn _t() { 208 | _check_pamsm_trait::<$pamsm_ty>() 209 | } 210 | 211 | // Callback entry definition. 212 | macro_rules! pam_callback { 213 | ($pam_cb:ident, $rust_cb:ident) => { 214 | #[no_mangle] 215 | #[doc(hidden)] 216 | pub unsafe extern "C" fn $pam_cb( 217 | pamh: pamsm::Pam, 218 | flags: std::os::raw::c_int, 219 | argc: std::os::raw::c_int, 220 | argv: *const *const std::os::raw::c_char, 221 | ) -> std::os::raw::c_int { 222 | use std::os::raw::c_int; 223 | if argc < 0 { 224 | return pamsm::PamError::SERVICE_ERR as std::os::raw::c_int; 225 | } 226 | 227 | let mut args = Vec::::with_capacity(argc as usize); 228 | for count in 0..(argc as isize) { 229 | match { 230 | std::ffi::CStr::from_ptr( 231 | *argv.offset(count) as *const std::os::raw::c_char 232 | ) 233 | .to_str() 234 | } { 235 | Ok(s) => args.push(s.to_owned()), 236 | Err(_) => return pamsm::PamError::SERVICE_ERR as c_int, 237 | }; 238 | } 239 | <$pamsm_ty>::$rust_cb(pamh, pamsm::PamFlags::from_bits_unchecked(flags), args) as c_int 240 | } 241 | }; 242 | } 243 | 244 | pam_callback!(pam_sm_open_session, open_session); 245 | pam_callback!(pam_sm_close_session, close_session); 246 | pam_callback!(pam_sm_authenticate, authenticate); 247 | pam_callback!(pam_sm_setcred, setcred); 248 | pam_callback!(pam_sm_acct_mgmt, acct_mgmt); 249 | pam_callback!(pam_sm_chauthtok, chauthtok); 250 | }; 251 | } 252 | -------------------------------------------------------------------------------- /src/pam_types.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(clippy::upper_case_acronyms)] 4 | 5 | use pam::PamError; 6 | use std::option::Option; 7 | use std::os::raw::{c_char, c_int, c_void}; 8 | use std::ptr::NonNull; 9 | 10 | pub type PamHandle = *const c_void; 11 | 12 | #[repr(C)] 13 | pub enum PamMsgStyle { 14 | PROMPT_ECHO_OFF = 1, /* Ask for password without echo */ 15 | PROMPT_ECHO_ON = 2, /* Ask for password with echo */ 16 | ERROR_MSG = 3, /* Display an error message */ 17 | TEXT_INFO = 4, /* Display arbitrary text */ 18 | // Linux extensions 19 | PAM_MAX_NUM_MSG = 32, 20 | PAM_RADIO_TYPE = 5, /* yes/no/maybe conditionals */ 21 | PAM_BINARY_PROMPT = 7, 22 | } 23 | 24 | #[repr(C)] 25 | pub struct PamMessage { 26 | pub msg_style: PamMsgStyle, 27 | pub msg: *const c_char, 28 | } 29 | 30 | #[repr(C)] 31 | pub struct PamResponse { 32 | pub resp: Option>, 33 | pub resp_retcode: PamError, 34 | } 35 | 36 | pub(crate) type PamConvCallback = extern "C" fn( 37 | num_msg: c_int, 38 | msg: *mut *const PamMessage, 39 | resp: *mut *mut PamResponse, 40 | appdata_ptr: *mut c_void, 41 | ) -> c_int; 42 | 43 | #[repr(C)] 44 | pub(crate) struct PamConv { 45 | pub(crate) cb: Option, 46 | pub(crate) appdata_ptr: *mut c_void, 47 | } 48 | 49 | #[repr(C)] 50 | pub enum LogLvl { 51 | EMERG = 0, /* system is unusable */ 52 | ALERT = 1, /* action must be taken immediately */ 53 | CRIT = 2, /* critical conditions */ 54 | ERR = 3, /* error conditions */ 55 | WARNING = 4, /* warning conditions */ 56 | NOTICE = 5, /* normal but significant condition */ 57 | INFO = 6, /* informational */ 58 | DEBUG = 7, /* debug-level messages */ 59 | } 60 | 61 | #[repr(C)] 62 | pub enum PamItemType { 63 | SERVICE = 1, /* The service name */ 64 | USER = 2, /* The user name */ 65 | TTY = 3, /* The tty name */ 66 | RHOST = 4, /* The remote host name */ 67 | CONV = 5, /* The pam_conv structure */ 68 | AUTHTOK = 6, /* The authentication token (password) */ 69 | OLDAUTHTOK = 7, /* The old authentication token */ 70 | RUSER = 8, /* The remote user name */ 71 | USER_PROMPT = 9, /* the prompt for getting a username */ 72 | FAIL_DELAY = 10, /* app supplied function to override failure delays */ 73 | XDISPLAY = 11, /* X display name */ 74 | XAUTHDATA = 12, /* X server authentication data */ 75 | AUTHTOK_TYPE = 13, /* The type for pam_get_authtok */ 76 | } 77 | -------------------------------------------------------------------------------- /test-module/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-module" 3 | version = "0.1.0" 4 | authors = ["rca "] 5 | 6 | [dependencies] 7 | pamsm = { path = "../", features = ["libpam"] } 8 | rand = "0.8" 9 | 10 | [lib] 11 | crate-type = ["cdylib"] 12 | -------------------------------------------------------------------------------- /test-module/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate pamsm; 3 | extern crate rand; 4 | 5 | use pamsm::{LogLvl, Pam, PamData, PamError, PamFlags, PamLibExt, PamServiceModule}; 6 | use rand::RngCore; 7 | use std::fs::write; 8 | use std::time::Instant; 9 | 10 | struct PamTime; 11 | 12 | #[derive(Debug, Clone)] 13 | struct SessionStart(Instant); 14 | 15 | impl PamData for SessionStart { 16 | fn cleanup(&self, _pam: Pam, flags: PamFlags, status: PamError) { 17 | if !flags.contains(PamFlags::SILENT) { 18 | println!( 19 | "PamTime cleanup. Session opened for {:?}, result {}, flags {:?}", 20 | self.0.elapsed(), 21 | status, 22 | flags 23 | ); 24 | if flags.contains(PamFlags::DATA_REPLACE) { 25 | println!("Pam data is being replaced"); 26 | } 27 | } 28 | } 29 | } 30 | 31 | impl PamServiceModule for PamTime { 32 | fn open_session(pamh: Pam, _flags: PamFlags, _args: Vec) -> PamError { 33 | pamh.syslog(LogLvl::WARNING, "hehe coucou %s %s").expect("Failed to send syslog"); 34 | let now = SessionStart(Instant::now()); 35 | if let Err(e) = unsafe { pamh.send_data("pamtime", now) } { 36 | return e; 37 | } 38 | 39 | let mut token = vec![0u8; 32]; 40 | rand::thread_rng().fill_bytes(&mut token); 41 | let res = pamh.send_bytes( 42 | "pamtime_token", 43 | token, 44 | Some(|token, _, _, _| { 45 | if let Err(e) = write(".token.bin", token) { 46 | println!("Error persisting token : {:?}", e); 47 | } 48 | }), 49 | ); 50 | 51 | if let Err(e) = res { 52 | return e; 53 | } 54 | 55 | PamError::SUCCESS 56 | } 57 | 58 | fn close_session(_pamh: Pam, _flags: PamFlags, _args: Vec) -> PamError { 59 | PamError::SUCCESS 60 | } 61 | 62 | fn authenticate(pamh: Pam, _flags: PamFlags, _args: Vec) -> PamError { 63 | // If you need password here, that works like this: 64 | // 65 | // let pass = match pamh.get_authtok(None) { 66 | // Ok(Some(p)) => p, 67 | // Ok(None) => return PamError::AUTH_ERR, 68 | // Err(e) => return e, 69 | // }; 70 | 71 | // Only allow authentication when user name is root and the session is less than a minute 72 | // old 73 | let user = match pamh.get_user(None) { 74 | Ok(Some(u)) => u, 75 | Ok(None) => return PamError::USER_UNKNOWN, 76 | Err(e) => return e, 77 | }; 78 | 79 | let s: SessionStart = match unsafe { pamh.retrieve_data("pamtime") } { 80 | Err(e) => return e, 81 | Ok(tref) => tref, 82 | }; 83 | 84 | if user.to_str().unwrap_or("") == "root" && s.0.elapsed().as_secs() < 60 { 85 | PamError::SUCCESS 86 | } else { 87 | PamError::AUTH_ERR 88 | } 89 | } 90 | } 91 | 92 | pam_module!(PamTime); 93 | --------------------------------------------------------------------------------