use std::{collections::HashMap, env::temp_dir, ffi::CStr, fs::File};

use pam::{
    constants::{PamResultCode, PAM_PROMPT_ECHO_ON},
    conv::Conv,
    module::PamHooks,
    pam_try,
};

struct PamHoneypot;

impl PamHooks for PamHoneypot {
    fn sm_authenticate(
        pamh: &mut pam::module::PamHandle,
        args: Vec<&CStr>,
        _flags: pam::constants::PamFlag,
    ) -> pam::constants::PamResultCode {
        let args: Vec<_> = args.iter().map(|s| s.to_string_lossy()).collect();
        let args: HashMap<&str, &str> = args
            .iter()
            .map(|s| {
                let mut parts = s.splitn(2, '=');
                (parts.next().unwrap(), parts.next().unwrap_or(""))
            })
            .collect();

        let honey_user = match args.get("user") {
            Some(user) => user,
            _ => return PamResultCode::PAM_IGNORE,
        };
        let honey_password = match args.get("password") {
            Some(password) => password,
            _ => return PamResultCode::PAM_IGNORE,
        };

        let user = pam_try!(pamh.get_user(None));
        if user != *honey_user {
            return PamResultCode::PAM_IGNORE;
        }

        let conv = match pamh.get_item::<Conv>() {
            Ok(Some(conv)) => conv,
            Ok(_) => {
                unreachable!("No conv available");
            }
            Err(err) => {
                return err;
            }
        };
        let password = pam_try!(conv.send(PAM_PROMPT_ECHO_ON, "Password"));
        let password = match password {
            Some(password) => Some(pam_try!(password.to_str(), PamResultCode::PAM_AUTH_ERR)),
            _ => None,
        };

        if let Some(password) = password {
            if *honey_password == password {
                let mut tmp = temp_dir();
                tmp.push("honeypot");
                let _ = File::create(tmp);

                return PamResultCode::PAM_AUTH_ERR;
            }
        }

        PamResultCode::PAM_IGNORE
    }
}

pam::pam_hooks!(PamHoneypot);