view testharness/src/bin/testharness.rs @ 183:4f46681b3f54 default tip

Catch a few stray cargo fmt things.
author Paul Fisher <paul@pfish.zone>
date Wed, 30 Jul 2025 18:43:07 -0400
parents a8c814843ccb
children
line wrap: on
line source

//! The actual program which runs the tests.

use nonstick::conv::Exchange;
use nonstick::items::Items;
use nonstick::libpam::TransactionBuilder;
use nonstick::EnvironMap;
use nonstick::{
    AuthnFlags, AuthtokFlags, Conversation, ErrorCode, LibPamTransaction, PamShared, Transaction,
};
use std::cell::Cell;
use std::collections::HashMap;
use std::ffi::OsString;
use std::os::unix::ffi::OsStrExt;

macro_rules! run {
    ($x:expr) => {
        eprintln!("START {}", stringify!($x));
        $x;
        eprintln!("..END {}", stringify!($x));
    };
}

fn main() {
    run!(test_wrong_user());
    run!(test_wrong_password());
    run!(test_correct());
}

#[derive(Debug, Default)]
struct TestHarness {
    username_requested: Cell<bool>,
    wrong_username: bool,
    wrong_password: bool,
    changing_password: Cell<bool>,
    change_prompt_count: Cell<usize>,
}

impl Conversation for &TestHarness {
    fn communicate(&self, messages: &[Exchange]) {
        if let [only_msg] = messages {
            match only_msg {
                Exchange::Prompt(p) => {
                    if self.username_requested.get() {
                        panic!("username already requested!")
                    }
                    if self.wrong_username {
                        p.set_answer(Ok(OsString::from("not-right")))
                    } else {
                        p.set_answer(Ok(OsString::from("initial")))
                    }
                    self.username_requested.set(true)
                }
                Exchange::MaskedPrompt(p) => {
                    let answer = if self.changing_password.get() {
                        let prompt_count = self.change_prompt_count.get();
                        self.change_prompt_count.set(prompt_count + 1);
                        // When changing passwords after logging in, Sun PAM
                        // uses the existing authtok that was just entered as
                        // the old_authtok. Other PAMs prompt the user to enter
                        // their existing password again.
                        let responses: &[&str] = if cfg!(pam_impl = "Sun") {
                            &["mistake", "mismatch", "acceptable", "acceptable"]
                        } else {
                            &[
                                "old token!",
                                "mistake",
                                "mismatch",
                                "old token!",
                                "acceptable",
                                "acceptable",
                            ]
                        };
                        responses[prompt_count]
                    } else if self.wrong_password {
                        "bogus"
                    } else {
                        "valid"
                    };
                    p.set_answer(Ok(OsString::from(answer)));
                }
                Exchange::Error(e) if self.changing_password.get() => e.set_answer(Ok(())),
                other => panic!("Unknown message {other:?}!"),
            }
        } else {
            for msg in messages {
                match msg {
                    Exchange::Info(i) => i.set_answer(Ok(())),
                    Exchange::Error(e) => e.set_answer(Ok(())),
                    Exchange::Prompt(p) => match p.question().as_bytes() {
                        b"How many?" => p.set_answer(Ok(OsString::from("123"))),
                        _ => p.set_answer(Err(ErrorCode::ConversationError)),
                    },
                    Exchange::MaskedPrompt(p) => match p.question().as_bytes() {
                        b"Where?" => p.set_answer(Ok(OsString::from("abc"))),
                        _ => p.set_answer(Err(ErrorCode::ConversationError)),
                    },
                    other => other.set_error(ErrorCode::Abort),
                }
            }
        }
    }
}

impl TestHarness {
    fn start(&self) -> LibPamTransaction<&Self> {
        TransactionBuilder::new_with_service("nonstick-testharness")
            .build(self)
            .expect("expected build success")
    }
}

fn test_wrong_user() {
    let harness = TestHarness {
        wrong_username: true,
        ..Default::default()
    };
    let mut tx = harness.start();
    let auth = tx.authenticate(AuthnFlags::empty());
    assert_eq!(auth, Err(ErrorCode::UserUnknown));
}

fn test_wrong_password() {
    let harness = TestHarness {
        wrong_password: true,
        ..Default::default()
    };
    let mut tx = harness.start();
    let auth = tx.authenticate(AuthnFlags::empty());
    assert_eq!(auth, Err(ErrorCode::AuthenticationError));
}

fn test_correct() {
    let harness = TestHarness::default();
    let mut tx = harness.start();
    tx.authenticate(AuthnFlags::empty()).unwrap();
    assert_eq!(tx.items().user().unwrap().unwrap(), "updated-in-process");
    let result = tx.account_management(AuthnFlags::empty());
    assert_eq!(result, Err(ErrorCode::NewAuthTokRequired));
    harness.changing_password.set(true);
    let change = tx.change_authtok(AuthtokFlags::CHANGE_EXPIRED_AUTHTOK);
    if cfg!(pam_impl = "Sun") {
        assert!(change.is_err())
    } else {
        assert_eq!(change, Err(ErrorCode::TryAgain));
    }
    tx.change_authtok(AuthtokFlags::CHANGE_EXPIRED_AUTHTOK)
        .unwrap();
    let environ: HashMap<_, _> = tx.environ().iter().collect();
    assert_eq!(
        environ,
        HashMap::from([("nin".into(), "nine inch nails".into())])
    );
}