view testharness/src/lib.rs @ 171:e27c5c667a5a

Create full new types for return code and flags, separate end to end. This plumbs the ReturnCode and RawFlags types through the places where we call into or are called from PAM. Also adds Sun documentation to the project.
author Paul Fisher <paul@pfish.zone>
date Fri, 25 Jul 2025 20:52:14 -0400
parents 77470e45e397
children 6727cbe56f4a
line wrap: on
line source

//! The nonstick library

use crate::nonstick::items::ItemsMut;
use std::cell::Cell;
extern crate nonstick;

use nonstick::conv::{ErrorMsg, InfoMsg, MaskedQAndA, QAndA};
use nonstick::{
    error, info, pam_hooks, AuthnFlags, AuthtokAction, AuthtokFlags, ErrorCode, ModuleClient,
    PamModule,
};
use std::ffi::CStr;
use std::os::unix::ffi::OsStrExt;

struct TestHarness;

impl<M: ModuleClient> PamModule<M> for TestHarness {
    fn authenticate(handle: &mut M, args: Vec<&CStr>, _: AuthnFlags) -> nonstick::Result<()> {
        let strings: Vec<_> = args.iter().map(|&a| Vec::from(a.to_bytes())).collect();
        if strings != vec![Vec::from(b"param"), Vec::from(b"param2")] {
            return Err(ErrorCode::SystemError);
        }
        let username = handle.username(None)?;
        if username != "initial" {
            return Err(ErrorCode::UserUnknown);
        }
        handle
            .items_mut()
            .set_user(Some("updated-in-process".as_ref()))?;
        handle.set_module_data("florgus", Cell::new(99))?;
        let authtok = handle.authtok(Some("custom".as_ref()))?;
        if authtok.as_bytes() != b"valid" {
            return Err(ErrorCode::AuthenticationError);
        }
        let info = InfoMsg::new("Watch out!".as_ref());
        let err = ErrorMsg::new("It's broken!".as_ref());
        let public = QAndA::new("How many?".as_ref());
        let private = MaskedQAndA::new("Where?".as_ref());
        let msgs = &[
            info.exchange(),
            err.exchange(),
            public.exchange(),
            private.exchange(),
        ];
        handle.communicate(msgs);
        let public = public.answer()?;
        info!(handle, "public question: {:?}", public);
        let private = private.answer()?;
        info!(handle, "private question: {:?}", private);
        if public.as_bytes() == b"123" && private.as_bytes() == b"abc" {
            Ok(())
        } else {
            Err(ErrorCode::Abort)
        }
    }

    fn account_management(handle: &mut M, _: Vec<&CStr>, _: AuthnFlags) -> nonstick::Result<()> {
        let value: &Cell<i32> = match handle.username(None)?.as_bytes() {
            b"initial" => return Err(ErrorCode::AccountExpired),
            b"updated-in-process" => handle.get_module_data("florgus"),
            _ => return Err(ErrorCode::UserUnknown),
        }
        .ok_or(ErrorCode::SessionError)?;
        let florgus_str: Option<&i32> = handle.get_module_data("florgus");
        if let Some(s) = florgus_str {
            error!(
                handle,
                "module_data type mismatch: florgus = <{s}> but should not be set"
            )
        }
        if value.get() != 99 {
            error!(handle, "wrong value! {}", value.get());
            return Err(ErrorCode::AuthTokError);
        }
        let password = handle.authtok(None)?;
        if password.as_bytes() == b"valid" {
            Err(ErrorCode::NewAuthTokRequired)
        } else {
            Ok(())
        }
    }

    fn change_authtok(
        handle: &mut M,
        _: Vec<&CStr>,
        action: AuthtokAction,
        _flags: AuthtokFlags,
    ) -> nonstick::Result<()> {
        match action {
            AuthtokAction::Validate => {
                if handle.old_authtok(None)?.as_bytes() != b"old token!" {
                    return Err(ErrorCode::AuthenticationError);
                }
                Ok(())
            }
            AuthtokAction::Update => {
                let password = handle.authtok(None)?;
                if password.as_bytes() != b"acceptable" {
                    return Err(ErrorCode::PermissionDenied);
                }
                Ok(())
            }
        }
    }
}

pam_hooks!(TestHarness);