use std::{fmt::Display, str::FromStr, sync::{Arc, Mutex}, time::{self, Duration}};
use dbus::{arg::ReadAll, blocking::{Connection, Proxy}, message::SignalArgs, Message, Path};
use crate::privileges::with_uid_as_euid;
use super::PresenceVerifier;
pub struct FprintdPresenceVerifier {
use_system_bus: bool,
timeout: Duration,
}
const FPRINTD_BUS_NAME: &str = "net.reactivated.Fprint";
const FPRINTD_MANAGER_PATH: &str = "/net/reactivated/Fprint/Manager";
const FPRINTD_MANAGER_IFACE: &str = "net.reactivated.Fprint.Manager";
const FPRINTD_DEVICE_IFACE: &str = "net.reactivated.Fprint.Device";
struct VerifyStatus {
status: Status,
#[allow(dead_code)]
done: bool,
}
#[derive(Clone, Copy, Debug)]
enum Status {
Match,
NoMatch,
RetryScan,
SwipeTooShort,
FingerNotCentered,
RemoveAndRetry,
Disconnected,
UnknownError,
}
impl FromStr for Status {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"verify-match" => Ok(Status::Match),
"verify-no-match" => Ok(Status::NoMatch),
"verify-retry-scan" => Ok(Status::RetryScan),
"verify-swipe-too-short" => Ok(Status::SwipeTooShort),
"verify-finger-not-entered" => Ok(Status::FingerNotCentered),
"verify-remove-and-retry" => Ok(Status::RemoveAndRetry),
"verify-disconnected" => Ok(Status::Disconnected),
"verify-unknown-error" => Ok(Status::UnknownError),
_ => Err(format!("unknown status: {}", s))
}
}
}
impl Display for Status {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Status::Match => f.write_str("verify-match"),
Status::NoMatch => f.write_str("verify-no-match"),
Status::RetryScan => f.write_str("verify-retry-scan"),
Status::SwipeTooShort => f.write_str("verify-swipe-too-short"),
Status::FingerNotCentered => f.write_str("verify-finger-not-entered"),
Status::RemoveAndRetry => f.write_str("verify-remove-and-retry"),
Status::Disconnected => f.write_str("verify-disconnected"),
Status::UnknownError => f.write_str("verify-unknown-error"),
}
}
}
impl ReadAll for VerifyStatus {
fn read(i: &mut dbus::arg::Iter) -> Result<Self, dbus::arg::TypeMismatchError> {
Ok(VerifyStatus {
status: Status::from_str(i.read::<&str>()?).unwrap(),
done: i.read()?,
})
}
}
impl SignalArgs for VerifyStatus {
const NAME: &'static str = "VerifyStatus";
const INTERFACE: &'static str = FPRINTD_DEVICE_IFACE;
}
struct FprintDevice<'a> {
proxy: Proxy<'a, &'a Connection>,
connection: &'a Connection,
}
impl <'a> Drop for FprintDevice<'a> {
fn drop(&mut self) {
match self.proxy.method_call(FPRINTD_DEVICE_IFACE, "Release", ()) {
Ok(()) => (),
Err(e) => log::warn!("failed to release fprintd device: {:#?}", e),
}
}
}
fn fail<T>(reason: &str) -> super::Result<T> {
Err(super::Error::ImplementationSpecificError(reason.to_owned()))
}
impl <'a> FprintDevice<'a> {
fn verify(&self, timeout: &Duration) -> super::Result<bool> {
let scan_status = Arc::new(Mutex::new(None));
let scan_status_clone = scan_status.clone();
self.proxy.match_signal(move |status: VerifyStatus, _: &Connection, _: &Message| {
*scan_status.lock().unwrap() = Some(status.status);
true
}).or(fail("fprintd: unable to listen for signal"))?;
self.proxy.method_call::<(), _, _, _>(FPRINTD_DEVICE_IFACE, "VerifyStart", ("any",))
.or(fail("fprintd: unable to start fingerprint verification"))?;
eprintln!("place your finger on the fingerprint reader");
let mut time_left = timeout.as_millis() as i64;
while time_left > 0 {
let t0 = time::Instant::now();
self.connection.process(Duration::from_millis(time_left as u64))
.or(fail("fprintd: unable to process incoming signals"))?;
let t1 = time::Instant::now();
time_left -= (t1 - t0).as_millis() as i64;
if let Some(status) = *scan_status_clone.lock().unwrap() {
match status {
Status::Match => {
self.proxy.method_call::<(), _, _, _>(FPRINTD_DEVICE_IFACE, "VerifyStop", ())
.or(fail("fprintd: unable to stop fingerprint verification"))?;
return Ok(true)
},
Status::NoMatch => {
eprintln!("fingerprint not recognized, try again");
self.proxy.method_call::<(), _, _, _>(FPRINTD_DEVICE_IFACE, "VerifyStop", ())
.or(fail("fprintd: unable to stop fingerprint verification"))?;
self.proxy.method_call::<(), _, _, _>(FPRINTD_DEVICE_IFACE, "VerifyStart", ("any",))
.or(fail("fprintd: unable to restart fingerprint verification"))?;
},
Status::RetryScan | Status::SwipeTooShort | Status::FingerNotCentered | Status::RemoveAndRetry => {
eprintln!("fingerprint not recognized, try again")
},
Status::Disconnected => {
return fail("fprintd: fingerprint reader disconnected")
},
Status::UnknownError => {
self.proxy.method_call::<(), _, _, _>(FPRINTD_DEVICE_IFACE, "VerifyStop", ())
.or(fail("fprintd: unable to stop fingerprint verification"))?;
return fail("fprintd: fingerprint scan failed with unknown error")
},
}
}
}
self.proxy.method_call::<(), _, _, _>(FPRINTD_DEVICE_IFACE, "VerifyStop", ())
.or(fail("fprintd: unable to stop fingerprint verification"))?;
Ok(false)
}
fn claim_default_device(conn: &'a Connection) -> super::Result<Self> {
let mgr_proxy = conn.with_proxy(
FPRINTD_BUS_NAME,
FPRINTD_MANAGER_PATH,
Duration::from_secs(10),
);
let (device_path,): (Path,) = mgr_proxy.method_call(FPRINTD_MANAGER_IFACE, "GetDefaultDevice", ())
.or(Err(super::Error::ImplementationSpecificError("fprintd: couldn't get default device".to_owned())))?;
let proxy = conn.with_proxy(
FPRINTD_BUS_NAME,
device_path,
Duration::from_secs(10),
);
proxy.method_call::<(), _, _, _>(FPRINTD_DEVICE_IFACE, "Claim", ("",))
.or(Err(super::Error::ImplementationSpecificError("fprintd: unable to claim device".to_owned())))?;
Ok(FprintDevice { proxy, connection: conn })
}
}
impl PresenceVerifier for FprintdPresenceVerifier {
fn owner_present(&mut self) -> super::Result<bool> {
with_uid_as_euid(|| {
let conn = if self.use_system_bus {
Connection::new_system()
} else {
Connection::new_session()
}.or(Err(super::Error::ImplementationSpecificError("fprintd: couldn't connect to bus".to_owned())))?;
let dev = FprintDevice::claim_default_device(&conn)?;
dev.verify(&self.timeout)
})
}
}
impl FprintdPresenceVerifier {
pub fn new(timeout_secs: u8) -> Self {
FprintdPresenceVerifier { use_system_bus: true, timeout: Duration::from_secs(timeout_secs as u64) }
}
}
#[cfg(test)]
#[cfg(feature = "dbus-tests")]
mod tests {
use dbus::MethodErr;
use serial_test::serial;
use testutil::fprintd::{FprintdMethod, FprintdMockBuilder, DEVICE_PATH};
use crate::presence_verification;
use super::*;
fn new_session_verifier() -> FprintdPresenceVerifier {
FprintdPresenceVerifier {
use_system_bus: false,
timeout: Duration::from_secs(1),
}
}
#[test]
#[serial]
fn failed_getdefaultdevice_fails_presence_verification() {
let _mock = FprintdMockBuilder::<Status>::new()
.expect_method(FprintdMethod::GetDefaultDevice(Err(MethodErr::no_arg())))
.build();
let mut pv = new_session_verifier();
let error = pv.owner_present().unwrap_err();
assert_eq!(error, presence_verification::Error::ImplementationSpecificError("fprintd: couldn't get default device".to_owned()))
}
#[test]
#[serial]
fn failed_claim_fails_presence_verification() {
let _mock = FprintdMockBuilder::<Status>::new()
.expect_method(FprintdMethod::GetDefaultDevice(Ok(DEVICE_PATH.to_owned())))
.expect_method(FprintdMethod::Claim("".to_owned(), Err(MethodErr::no_arg())))
.build();
let mut pv = new_session_verifier();
let error = pv.owner_present().unwrap_err();
assert_eq!(error, presence_verification::Error::ImplementationSpecificError("fprintd: unable to claim device".to_owned()))
}
#[test]
#[serial]
fn failed_verifystart_fails_presence_verification() {
let _mock = FprintdMockBuilder::<Status>::new()
.expect_method(FprintdMethod::GetDefaultDevice(Ok(DEVICE_PATH.to_owned())))
.expect_method(FprintdMethod::Claim("".to_owned(), Ok(())))
.expect_method(FprintdMethod::VerifyStart("any".to_owned(), Err(MethodErr::no_arg())))
.expect_method(FprintdMethod::Release(Ok(())))
.build();
let mut pv = new_session_verifier();
let error = pv.owner_present().unwrap_err();
assert_eq!(error, presence_verification::Error::ImplementationSpecificError("fprintd: unable to start fingerprint verification".to_owned()))
}
#[test]
#[serial]
fn timeout_makes_presence_verification_succeed_with_result_false() {
let _mock = FprintdMockBuilder::<Status>::new()
.expect_method(FprintdMethod::GetDefaultDevice(Ok(DEVICE_PATH.to_owned())))
.expect_method(FprintdMethod::Claim("".to_owned(), Ok(())))
.expect_method(FprintdMethod::VerifyStart("any".to_owned(), Ok(())))
.expect_method(FprintdMethod::VerifyStop(Ok(())))
.expect_method(FprintdMethod::Release(Ok(())))
.build();
let mut pv = new_session_verifier();
assert_eq!(pv.owner_present().unwrap(), false);
}
#[test]
#[serial]
fn successful_scan_makes_presence_verification_succeed_with_result_true() {
let _mock = FprintdMockBuilder::new()
.expect_method(FprintdMethod::GetDefaultDevice(Ok(DEVICE_PATH.to_owned())))
.expect_method(FprintdMethod::Claim("".to_owned(), Ok(())))
.expect_method(FprintdMethod::VerifyStart("any".to_owned(), Ok(())))
.wait(Duration::from_millis(100))
.send_status(Status::Match, true)
.expect_method(FprintdMethod::VerifyStop(Ok(())))
.expect_method(FprintdMethod::Release(Ok(())))
.build();
let mut pv = new_session_verifier();
assert_eq!(pv.owner_present().unwrap(), true);
}
#[test]
#[serial]
fn no_match_followed_by_match_makes_presence_verification_succeed() {
let _mock = FprintdMockBuilder::new()
.expect_method(FprintdMethod::GetDefaultDevice(Ok(DEVICE_PATH.to_owned())))
.expect_method(FprintdMethod::Claim("".to_owned(), Ok(())))
.expect_method(FprintdMethod::VerifyStart("any".to_owned(), Ok(())))
.wait(Duration::from_millis(100))
.send_status(Status::NoMatch, true)
.expect_method(FprintdMethod::VerifyStop(Ok(())))
.expect_method(FprintdMethod::VerifyStart("any".to_owned(), Ok(())))
.wait(Duration::from_millis(100))
.send_status(Status::Match, true)
.expect_method(FprintdMethod::VerifyStop(Ok(())))
.expect_method(FprintdMethod::Release(Ok(())))
.build();
let mut pv = new_session_verifier();
assert_eq!(pv.owner_present().unwrap(), true);
}
#[test]
#[serial]
fn swipe_too_short_followed_by_match_makes_presence_verification_succeed() {
let _mock = FprintdMockBuilder::new()
.expect_method(FprintdMethod::GetDefaultDevice(Ok(DEVICE_PATH.to_owned())))
.expect_method(FprintdMethod::Claim("".to_owned(), Ok(())))
.expect_method(FprintdMethod::VerifyStart("any".to_owned(), Ok(())))
.wait(Duration::from_millis(100))
.send_status(Status::SwipeTooShort, false)
.wait(Duration::from_millis(100))
.send_status(Status::Match, true)
.expect_method(FprintdMethod::VerifyStop(Ok(())))
.expect_method(FprintdMethod::Release(Ok(())))
.build();
let mut pv = new_session_verifier();
assert_eq!(pv.owner_present().unwrap(), true);
}
#[test]
#[serial]
fn disconnected_makes_presence_verification_fail() {
let _mock = FprintdMockBuilder::new()
.expect_method(FprintdMethod::GetDefaultDevice(Ok(DEVICE_PATH.to_owned())))
.expect_method(FprintdMethod::Claim("".to_owned(), Ok(())))
.expect_method(FprintdMethod::VerifyStart("any".to_owned(), Ok(())))
.wait(Duration::from_millis(100))
.send_status(Status::Disconnected, false)
.expect_method(FprintdMethod::Release(Ok(())))
.build();
let mut pv = new_session_verifier();
assert_eq!(
pv.owner_present().unwrap_err(),
presence_verification::Error::ImplementationSpecificError("fprintd: fingerprint reader disconnected".to_owned())
);
}
#[test]
#[serial]
fn unknown_error_makes_presence_verification_fail() {
let _mock = FprintdMockBuilder::new()
.expect_method(FprintdMethod::GetDefaultDevice(Ok(DEVICE_PATH.to_owned())))
.expect_method(FprintdMethod::Claim("".to_owned(), Ok(())))
.expect_method(FprintdMethod::VerifyStart("any".to_owned(), Ok(())))
.wait(Duration::from_millis(100))
.send_status(Status::UnknownError, false)
.expect_method(FprintdMethod::VerifyStop(Ok(())))
.expect_method(FprintdMethod::Release(Ok(())))
.build();
let mut pv = new_session_verifier();
assert_eq!(
pv.owner_present().unwrap_err(),
presence_verification::Error::ImplementationSpecificError("fprintd: fingerprint scan failed with unknown error".to_owned())
);
}
}