view testharness/src/lib.rs @ 163:a75a66cb4181

Add end-to-end tests; fix issues found by tests. - Create tests and installer/remover shell script - Fix Pointer/pointee problems - Add Debug formatting - Misc cleanup
author Paul Fisher <paul@pfish.zone>
date Mon, 14 Jul 2025 17:40:11 -0400
parents 634cd5f2ac8b
children 2f5913131295
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, ErrorCode, Flags, ModuleClient, PamModule};
use std::ffi::{CStr, OsString};
use std::os::unix::ffi::OsStrExt;

struct TestHarness;

impl<M: ModuleClient> PamModule<M> for TestHarness {
    fn authenticate(handle: &mut M, args: Vec<&CStr>, _: Flags) -> 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>, _: Flags) -> 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>, flags: Flags) -> nonstick::Result<()> {
        if flags.contains(Flags::PRELIMINARY_CHECK) {
            let password = handle.authtok(None)?;
            if password.as_bytes() != b"acceptable" {
                return Err(ErrorCode::PermissionDenied);
            }
            handle.set_module_data("checked_pass", password)
        } else if flags.contains(Flags::UPDATE_AUTHTOK) {
            let password = handle.authtok(None)?;
            let checked: &OsString = handle
                .get_module_data("checked_pass")
                .ok_or(ErrorCode::SystemError)?;
            if password != *checked {
                error!(handle, "password mismatch? {password:?} {checked:?}");
                return Err(ErrorCode::AuthenticationError);
            }
            Ok(())
        } else {
            error!(handle, "invalid flag state: {flags:?}");
            Err(ErrorCode::SystemError)
        }
    }
}

pam_hooks!(TestHarness);