changeset 1:b195a14058bb

initial commit
author Jesse Hallett <jesse@galois.com>
date Thu, 05 Mar 2015 16:25:10 -0800
parents 8ed8b2cfd909
children e69e2966ae0d
files .gitignore Cargo.toml README.markdown src/constants.rs src/conv.rs src/lib.rs src/module.rs
diffstat 7 files changed, 394 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.gitignore	Thu Mar 05 16:25:10 2015 -0800
@@ -0,0 +1,15 @@
+# Compiled files
+*.o
+*.so
+*.rlib
+*.dll
+
+# Executables
+*.exe
+
+# Generated by Cargo
+/target/
+Cargo.lock
+
+# Override top-level .gitignore in Misc repo
+!src/tozny
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Cargo.toml	Thu Mar 05 16:25:10 2015 -0800
@@ -0,0 +1,9 @@
+[package]
+
+name = "pam"
+version = "0.0.1"
+authors = [ "Jesse Hallett <jesse@galois.com" ]
+
+[lib]
+
+name = "pam"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README.markdown	Thu Mar 05 16:25:10 2015 -0800
@@ -0,0 +1,18 @@
+rust-pam
+========
+
+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.
+
+For more information, see the package documentation.  To build the
+documentation:
+
+    $ cargo doc
+
+Then open
+
+	target/doc/pam/index.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/constants.rs	Thu Mar 05 16:25:10 2015 -0800
@@ -0,0 +1,79 @@
+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;
+pub type PamResultCode   = c_int;
+pub type AlwaysZero      = 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;
+
+// The Linux-PAM item types
+// see /usr/include/security/_pam_types.h
+pub const PAM_SERVICE:      PamItemType =  1;   /* The service name */
+pub const PAM_USER:         PamItemType =  2;   /* The user name */
+pub const PAM_TTY:          PamItemType =  3;   /* The tty name */
+pub const PAM_RHOST:        PamItemType =  4;   /* The remote host name */
+pub const PAM_CONV:         PamItemType =  5;   /* The pam_conv structure */
+pub const PAM_AUTHTOK:      PamItemType =  6;   /* The authentication token (password) */
+pub const PAM_OLDAUTHTOK:   PamItemType =  7;   /* The old authentication token */
+pub const PAM_RUSER:        PamItemType =  8;   /* The remote user name */
+pub const PAM_USER_PROMPT:  PamItemType =  9;   /* the prompt for getting a username */
+/* Linux-PAM :extensionsPamItemType = */
+pub const PAM_FAIL_DELAY:   PamItemType = 10;   /* app supplied function to override failure delays */
+pub const PAM_XDISPLAY:     PamItemType = 11;   /* X :display name */
+pub const PAM_XAUTHDATA:    PamItemType = 12;   /* X :server authentication data */
+pub const PAM_AUTHTOK_TYPE: PamItemType = 13;   /* The type for pam_get_authtok */
+
+// 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;
+pub const PAM_RADIO_TYPE:      PamMessageStyle = 5;        /* yes/no/maybe conditionals */
+pub const PAM_BINARY_PROMPT:   PamMessageStyle = 7;
+
+// The Linux-PAM return values
+// see /usr/include/security/_pam_types.h
+pub const PAM_SUCCESS:               PamResultCode =  0;
+pub const PAM_OPEN_ERR:              PamResultCode =  1;
+pub const PAM_SYMBOL_ERR:            PamResultCode =  2;
+pub const PAM_SERVICE_ERR:           PamResultCode =  3;
+pub const PAM_SYSTEM_ERR:            PamResultCode =  4;
+pub const PAM_BUF_ERR:               PamResultCode =  5;
+pub const PAM_PERM_DENIED:           PamResultCode =  6;
+pub const PAM_AUTH_ERR:              PamResultCode =  7;
+pub const PAM_CRED_INSUFFICIENT:     PamResultCode =  8;
+pub const PAM_AUTHINFO_UNAVAIL:      PamResultCode =  9;
+pub const PAM_USER_UNKNOWN:          PamResultCode = 10;
+pub const PAM_MAXTRIES:              PamResultCode = 11;
+pub const PAM_NEW_AUTHTOK_REQD:      PamResultCode = 12;
+pub const PAM_ACCT_EXPIRED:          PamResultCode = 13;
+pub const PAM_SESSION_ERR:           PamResultCode = 14;
+pub const PAM_CRED_UNAVAIL:          PamResultCode = 15;
+pub const PAM_CRED_EXPIRED:          PamResultCode = 16;
+pub const PAM_CRED_ERR:              PamResultCode = 17;
+pub const PAM_NO_MODULE_DATA:        PamResultCode = 18;
+pub const PAM_CONV_ERR:              PamResultCode = 19;
+pub const PAM_AUTHTOK_ERR:           PamResultCode = 20;
+pub const PAM_AUTHTOK_RECOVERY_ERR:  PamResultCode = 21;
+pub const PAM_AUTHTOK_LOCK_BUSY:     PamResultCode = 22;
+pub const PAM_AUTHTOK_DISABLE_AGING: PamResultCode = 23;
+pub const PAM_TRY_AGAIN:             PamResultCode = 24;
+pub const PAM_IGNORE:                PamResultCode = 25;
+pub const PAM_ABORT:                 PamResultCode = 26;
+pub const PAM_AUTHTOK_EXPIRED:       PamResultCode = 27;
+pub const PAM_MODULE_UNKNOWN:        PamResultCode = 28;
+pub const PAM_BAD_ITEM:              PamResultCode = 29;
+pub const PAM_CONV_AGAIN:            PamResultCode = 30;
+pub const PAM_INCOMPLETE:            PamResultCode = 31;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/conv.rs	Thu Mar 05 16:25:10 2015 -0800
@@ -0,0 +1,84 @@
+use libc::{c_char, c_int};
+use std::{ptr};
+use std::ffi::{CStr, CString};
+
+use constants;
+use constants::*;
+use module::{PamItem, PamResult};
+
+#[allow(missing_copy_implementations)]
+pub enum AppDataPtr {}
+
+#[repr(C)]
+struct PamMessage {
+    msg_style: PamMessageStyle,
+    msg:       *const c_char,
+}
+
+#[repr(C)]
+struct PamResponse {
+    resp: *const c_char,
+    resp_retcode: AlwaysZero,
+}
+
+/// `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 PamConv {
+    conv: extern fn(num_msg: c_int,
+                    pam_message: &&PamMessage,
+                    pam_response: &*mut PamResponse,
+                    appdata_ptr: *const AppDataPtr
+                   ) -> PamResultCode,
+    appdata_ptr: *const AppDataPtr,
+}
+
+impl PamConv {
+    /// 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<String>> {
+        let resp_ptr: *mut PamResponse = ptr::null_mut();
+        let msg = PamMessage {
+            msg_style: style,
+            msg: CString::new(msg).unwrap().as_ptr(),
+        };
+
+        let ret = (self.conv)(1, &&msg, &resp_ptr, self.appdata_ptr);
+
+        if constants::PAM_SUCCESS == ret {
+            let s = unsafe { resp_ptr.as_ref() }
+            .and_then(|r| {
+                if r.resp.is_null() {
+                    None
+                }
+                else {
+                    let bytes = unsafe { CStr::from_ptr(r.resp).to_bytes() };
+                    String::from_utf8(bytes.to_vec()).ok()
+                }
+            });
+            Ok(s)
+        } else {
+            Err(ret)
+        }
+    }
+}
+
+impl PamItem for PamConv {
+    fn item_type(_: Option<Self>) -> PamItemType { PAM_CONV }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib.rs	Thu Mar 05 16:25:10 2015 -0800
@@ -0,0 +1,35 @@
+#![feature(core)]
+#![feature(libc)]
+#![feature(std_misc)]
+
+//! 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
+//! [tozny-pam][].
+//!
+//! [tozny-pam]: https://github.com/tozny/tozny-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 conv;
+pub mod constants;
+pub mod module;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/module.rs	Thu Mar 05 16:25:10 2015 -0800
@@ -0,0 +1,154 @@
+//! Functions for use in pam modules.
+
+use libc::{c_char};
+use std::{mem, ptr};
+use std::ffi::{CStr, CString};
+
+use constants;
+use constants::*;
+
+/// 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.
+#[allow(missing_copy_implementations)]
+pub enum PamHandleT {}
+
+#[allow(missing_copy_implementations)]
+enum PamItemT {}
+
+#[allow(missing_copy_implementations)]
+pub enum PamDataT {}
+
+#[link(name = "pam")]
+extern {
+    fn pam_get_data(pamh: *const PamHandleT,
+                    module_data_name: *const c_char,
+                    data: & *mut PamDataT,
+                    ) -> PamResultCode;
+
+    fn pam_set_data(pamh: *const PamHandleT,
+                    module_data_name: *const c_char,
+                    data: Box<PamDataT>,
+                    cleanup: extern fn (pamh: *const PamHandleT,
+                                        data: Box<PamDataT>,
+                                        error_status: PamResultCode
+                                        ),
+                    ) -> PamResultCode;
+
+    fn pam_get_item(pamh: *const PamHandleT,
+                    item_type: PamItemType,
+                    item: & *mut PamItemT,
+                    ) -> PamResultCode;
+
+    fn pam_set_item(pamh: *mut PamHandleT,
+                    item_type: PamItemType,
+                    item: &PamItemT,
+                    ) -> PamResultCode;
+
+    fn pam_get_user(pamh: *const PamHandleT,
+                    user: & *mut c_char,
+                    prompt: *const c_char,
+                    ) -> PamResultCode;
+}
+
+pub type PamResult<T> = Result<T, PamResultCode>;
+
+/// Type-level mapping for safely retrieving values with `get_item`.
+///
+/// See `pam_get_item` in
+/// http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html
+pub trait PamItem {
+    /// Maps a Rust type to a pam constant.
+    ///
+    /// For example, the type PamConv maps to the constant PAM_CONV.  The pam
+    /// API contract specifies that when the API function `pam_get_item` is
+    /// called with the constant PAM_CONV, it will return a value of type
+    /// `PamConv`.
+    ///
+    /// The argument will always be `None`.  Its purpose is to provide a type
+    /// label - the value is not important.
+    fn item_type(_: Option<Self>) -> PamItemType;
+}
+
+/// 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
+pub unsafe fn get_data<'a, T>(pamh: &'a PamHandleT, key: &str) -> PamResult<&'a T> {
+    let c_key = CString::new(key).unwrap().as_ptr();
+    let mut ptr: *mut PamDataT = ptr::null_mut();
+    let res = pam_get_data(pamh, c_key, &mut ptr);
+    if constants::PAM_SUCCESS == res && !ptr.is_null() {
+        let raw_data: &PamDataT = ptr.as_ref().unwrap();
+        let data: &T = mem::transmute(raw_data);
+        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
+pub fn set_data<T>(pamh: &PamHandleT, key: &str, data: Box<T>) -> PamResult<()> {
+    let c_key = CString::new(key).unwrap().as_ptr();
+    let res = unsafe {
+        let c_data: Box<PamDataT> = mem::transmute(data);
+        pam_set_data(pamh, c_key, c_data, cleanup::<T>)
+    };
+    if constants::PAM_SUCCESS == res { Ok(()) } else { Err(res) }
+}
+
+#[no_mangle]
+pub extern fn cleanup<T>(_: *const PamHandleT, c_data: Box<PamDataT>, _: PamResultCode) {
+    unsafe {
+        let data: Box<T> = mem::transmute(c_data);
+        mem::drop(data);
+    }
+}
+
+/// 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
+pub fn get_item<'a, T: PamItem>(pamh: &'a PamHandleT) -> PamResult<&'a T> {
+    let ptr: *mut PamItemT = ptr::null_mut();
+    let (res, item) = unsafe {
+        let r = pam_get_item(pamh, PamItem::item_type(None::<T>), &ptr);
+        let raw_item: &PamItemT = ptr.as_ref().unwrap();
+        let t: &T = mem::transmute(raw_item);
+        (r, t)
+    };
+    if constants::PAM_SUCCESS == res { Ok(item) } 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
+pub fn get_user<'a>(pamh: &'a PamHandleT, prompt: Option<&str>) -> PamResult<String> {
+    let ptr: *mut c_char = ptr::null_mut();
+    let c_prompt = match prompt {
+        Some(p) => CString::new(p).unwrap().as_ptr(),
+        None    => ptr::null(),
+    };
+    let res = unsafe { pam_get_user(pamh, &ptr, c_prompt) };
+    if constants::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(|_| PAM_CONV_ERR)
+    }
+    else {
+        Err(res)
+    }
+}