Use PAM properly, which allows us to handle other auth types

Previously I was just presenting a static username/password box, and
then running PAM with pre-set credentials.  This works just fine when
PAM is expecting a username and password, but if it's expecting
something like a fingerprint scan or a hardware security token, this
wouldn't entirely work right.  Well, it would "work", but the
username/password dialog would be displayed, and then hitting "Unlock"
would start a different auth process with no visible feedback as to
what's supposed to happen.

This also means I need to switch PAM wrapper crates; the one I was using
before did not allow passing a fixed username to the underlying
pam_start() call, which meant that PAM would try to prompt the user for
it, which is not what we want.
This commit is contained in:
Brian Tarricone 2022-08-14 22:17:58 -07:00
parent 7d05bcb0d5
commit 49394b3e53
4 changed files with 351 additions and 175 deletions

160
Cargo.lock generated
View File

@ -248,7 +248,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2da379dbebc0b76ef63ca68d8fc6e71c0f13e59432e0987e508c1820e6ab5239"
dependencies = [
"bitflags",
"cexpr",
"cexpr 0.4.0",
"clang-sys",
"clap 2.34.0",
"env_logger 0.8.4",
@ -260,8 +260,31 @@ dependencies = [
"quote",
"regex",
"rustc-hash",
"shlex",
"which",
"shlex 0.1.1",
"which 3.1.1",
]
[[package]]
name = "bindgen"
version = "0.59.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8"
dependencies = [
"bitflags",
"cexpr 0.6.0",
"clang-sys",
"clap 2.34.0",
"env_logger 0.9.0",
"lazy_static",
"lazycell",
"log",
"peeking_take_while",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex 1.1.0",
"which 4.2.5",
]
[[package]]
@ -339,7 +362,7 @@ dependencies = [
"gtk",
"gtk-sys",
"log",
"pam",
"pam-client",
"shell-words",
]
@ -442,7 +465,16 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
dependencies = [
"nom",
"nom 5.1.2",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom 7.1.1",
]
[[package]]
@ -589,6 +621,23 @@ version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6907e25393cdcc1f4f3f513d9aac1e840eb1cc341a0fccb01171f7d14d10b946"
[[package]]
name = "either"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be"
[[package]]
name = "enum-repr"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bad30c9c0fa1aaf1ae5010dab11f1117b15d35faf62cda4bbbc53b9987950f18"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "enumflags2"
version = "0.7.5"
@ -1090,6 +1139,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "itoa"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
[[package]]
name = "js-sys"
version = "0.3.57"
@ -1182,6 +1237,12 @@ dependencies = [
"autocfg",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nix"
version = "0.23.1"
@ -1205,6 +1266,16 @@ dependencies = [
"version_check",
]
[[package]]
name = "nom"
version = "7.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "num-integer"
version = "0.1.45"
@ -1257,22 +1328,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "029d8d0b2f198229de29dca79676f2738ff952edf3fde542eb8bf94d8c21b435"
[[package]]
name = "pam"
version = "0.7.0"
name = "pam-client"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa2bdc959c201c047004a1420a92aaa1dd1a6b64d5ef333aa3a4ac764fb93097"
checksum = "51bd776116a7ada5ebbe31f54cdc5b1030ed7265686cf7c8a21c057a2f8dab9a"
dependencies = [
"bitflags",
"enum-repr",
"libc",
"pam-sys",
"users",
"rpassword",
"rustversion",
]
[[package]]
name = "pam-sys"
version = "0.5.6"
version = "1.0.0-alpha4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd4858311a097f01a0006ef7d0cd50bca81ec430c949d7bf95cbefd202282434"
checksum = "5e9dfd42858f6a6bb1081079fd9dc259ca3e2aaece6cb689fd36b1058046c969"
dependencies = [
"bindgen 0.59.2",
"libc",
]
@ -1558,6 +1633,18 @@ dependencies = [
"winapi",
]
[[package]]
name = "rpassword"
version = "6.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bf099a1888612545b683d2661a1940089f6c2e5a8e38979b2159da876bfd956"
dependencies = [
"libc",
"serde",
"serde_json",
"winapi",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
@ -1573,6 +1660,18 @@ dependencies = [
"semver",
]
[[package]]
name = "rustversion"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8"
[[package]]
name = "ryu"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
[[package]]
name = "scopeguard"
version = "1.1.0"
@ -1617,6 +1716,17 @@ dependencies = [
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_repr"
version = "0.1.8"
@ -1655,6 +1765,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
[[package]]
name = "shlex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]]
name = "slab"
version = "0.4.6"
@ -1820,15 +1936,6 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "users"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fed7d0912567d35f88010c23dbaf865e9da8b5227295e8dc0f2fdd109155ab7"
dependencies = [
"libc",
]
[[package]]
name = "value-bag"
version = "1.0.0-alpha.9"
@ -1963,6 +2070,17 @@ dependencies = [
"libc",
]
[[package]]
name = "which"
version = "4.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae"
dependencies = [
"either",
"lazy_static",
"libc",
]
[[package]]
name = "winapi"
version = "0.3.9"
@ -2048,7 +2166,7 @@ name = "xkbcommon-sys"
version = "0.7.5"
source = "git+https://github.com/kelnos/rust-xkbcommon-sys?branch=release-0.7.x#8579153d01ef87096abf0ef02608e5dde8e96a0c"
dependencies = [
"bindgen",
"bindgen 0.56.0",
"libc",
"pkg-config",
]

1
debian/control vendored
View File

@ -14,6 +14,7 @@ Build-Depends:
libpam0g-dev,
cargo (>= 0.57),
rustc (>= 1.58),
clang,
help2man
Package: bscreensaver

View File

@ -14,5 +14,5 @@ gtk-sys = "0.15"
gdk-sys = "0.15"
gdkx11 = "0.15"
log = "0.4"
pam = "0.7"
pam-client = "0.5"
shell-words = "1"

View File

@ -1,16 +1,78 @@
use chrono::prelude::*;
use gdkx11::X11Window;
use gethostname::gethostname;
use glib::{GString, clone, SourceId};
use glib::{GString, clone};
use gtk::{prelude::*, Button, Entry, Label, Plug, Window};
use log::{debug, error, warn};
use std::{io::{self, Write}, thread, time::Duration};
use std::{io::{self, Write}, sync::mpsc, thread, time::Duration, ffi::CString};
use bscreensaver_util::{init_logging, dialog::DialogExitStatus, settings::Configuration, desktop::NewLoginCommand};
const PAM_SERVICE_NAME: &str = "xscreensaver";
const DIALOG_UPDATE_INTERVAL: Duration = Duration::from_millis(100);
const DIALOG_TIMEOUT: Duration = Duration::from_secs(60);
#[derive(Debug)]
enum GtkConverseOp {
ShowInfo(String),
ShowError(String),
ShowPrompt(String, mpsc::Sender<String>),
ShowBlindPrompt(String, mpsc::Sender<String>),
}
struct Authenticator {
username: String,
sender: glib::Sender<GtkConverseOp>,
}
impl Authenticator {
pub fn new<S: AsRef<str>>(username: S, op_sender: glib::Sender<GtkConverseOp>) -> Self {
Self {
username: username.as_ref().to_string(),
sender: op_sender,
}
}
pub fn authenticate(mut self) -> anyhow::Result<()> {
let username = std::mem::take(&mut self.username);
let mut context = pam_client::Context::new(PAM_SERVICE_NAME, Some(username.as_str()), self)?;
context.authenticate(pam_client::Flag::NONE)?;
context.acct_mgmt(pam_client::Flag::NONE)?;
let _ = context.open_session(pam_client::Flag::NONE)?;
Ok(())
}
}
impl pam_client::ConversationHandler for Authenticator {
fn text_info(&mut self, msg: &std::ffi::CStr) {
let msg = msg.to_str().unwrap_or("(OS sent us a non-UTF-8 message)").trim();
let _ = self.sender.send(GtkConverseOp::ShowInfo(msg.to_string()));
thread::sleep(Duration::from_secs(2));
}
fn error_msg(&mut self, msg: &std::ffi::CStr) {
let msg = msg.to_str().unwrap_or("(OS sent us a non-UTF-8 message)").trim();
let _ = self.sender.send(GtkConverseOp::ShowError(msg.to_string()));
thread::sleep(Duration::from_secs(2));
}
fn prompt_echo_on(&mut self, msg: &std::ffi::CStr) -> Result<CString, pam_client::ErrorCode> {
let msg = msg.to_str().unwrap_or("Password:").trim();
let (tx, rx) = mpsc::channel();
self.sender.send(GtkConverseOp::ShowPrompt(msg.to_string(), tx)).map_err(|_| pam_client::ErrorCode::ABORT)?;
let result = rx.recv().map_err(|_| pam_client::ErrorCode::ABORT)?;
CString::new(result).map_err(|_| pam_client::ErrorCode::CRED_ERR)
}
fn prompt_echo_off(&mut self, msg: &std::ffi::CStr) -> Result<CString, pam_client::ErrorCode> {
let msg = msg.to_str().unwrap_or("Password:").trim();
let (tx, rx) = mpsc::channel();
self.sender.send(GtkConverseOp::ShowBlindPrompt(msg.to_string(), tx)).map_err(|_| pam_client::ErrorCode::ABORT)?;
let result = rx.recv().map_err(|_| pam_client::ErrorCode::ABORT)?;
CString::new(result).map_err(|_| pam_client::ErrorCode::CRED_ERR)
}
}
fn main() -> anyhow::Result<()> {
init_logging("BSCREENSAVER_DIALOG_GTK3_LOG");
glib::log_set_default_handler(glib::rust_log_handler);
@ -27,8 +89,6 @@ fn main() -> anyhow::Result<()> {
gtk::init()?;
let (tx, rx) = glib::MainContext::sync_channel(glib::PRIORITY_DEFAULT, 1);
let top_sg = gtk::SizeGroup::builder()
.mode(gtk::SizeGroupMode::Horizontal)
.build();
@ -150,32 +210,6 @@ fn main() -> anyhow::Result<()> {
glib::Continue(true)
});
let attrs = gtk::pango::AttrList::new();
attrs.insert(gtk::pango::AttrFloat::new_scale(gtk::pango::SCALE_XX_LARGE));
let mut bold_desc = gtk::pango::FontDescription::new();
bold_desc.set_weight(gtk::pango::Weight::Bold);
attrs.insert(gtk::pango::AttrFontDesc::new(&bold_desc));
let auth_status_label = gtk::Label::builder()
.label("")
.xalign(0.5)
.yalign(0.5)
.attributes(&attrs)
.opacity(0.0)
.build();
vbox.pack_start(&auth_status_label, false, false, 0);
rx.attach(None, clone!(@strong auth_status_label => move |(exit_status, auth_dots_id): (DialogExitStatus, SourceId)| {
auth_dots_id.remove();
if exit_status != DialogExitStatus::AuthSucceeded {
auth_status_label.set_label("Authentication Failed!");
auth_status_label.set_opacity(1.0);
glib::timeout_add_seconds_local_once(1, move || exit_status.exit());
} else {
exit_status.exit();
}
glib::Continue(true)
}));
let sep = gtk::Separator::builder()
.orientation(gtk::Orientation::Vertical)
.build();
@ -183,118 +217,46 @@ fn main() -> anyhow::Result<()> {
let vbox = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.spacing(4)
.spacing(0)
.margin(0)
.build();
top_sg.add_widget(&vbox);
top_hbox.pack_start(&vbox, true, true, 0);
let hbox = gtk::Box::builder()
let auth_vbox = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.spacing(4)
.build();
vbox.pack_start(&auth_vbox, true, true, 2);
let button_hbox = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal)
.spacing(8)
.build();
vbox.pack_start(&hbox, true, true, 2);
let label = Label::builder()
.label("Username:")
.xalign(0.0)
.build();
label_sg.add_widget(&label);
hbox.pack_start(&label, false, true, 8);
let username = bscreensaver_util::get_username()?;
let username_box = Entry::builder()
.text(&username)
.sensitive(false)
.build();
entry_sg.add_widget(&username_box);
hbox.pack_start(&username_box, true, true, 8);
let hbox = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal)
.spacing(8)
.build();
vbox.pack_start(&hbox, true, true, 0);
let label = Label::builder()
.label("Password:")
.xalign(0.0)
.build();
label_sg.add_widget(&label);
hbox.pack_start(&label, false, true, 8);
let password_box = Entry::builder()
.visibility(false)
.input_purpose(gtk::InputPurpose::Password)
.activates_default(true)
.width_chars(25)
.build();
entry_sg.add_widget(&password_box);
hbox.pack_start(&password_box, true, true, 8);
password_box.connect_key_press_event(|_, ev| {
if ev.keyval().name() == Some(GString::from("Escape")) {
DialogExitStatus::AuthCanceled.exit();
}
gtk::Inhibit(false)
});
password_box.grab_focus();
let hbox = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal)
.spacing(8)
.build();
vbox.pack_start(&hbox, true, true, 2);
vbox.pack_end(&button_hbox, false, false, 4);
if let Some(new_login_command) = new_login_command {
let new_login_button = Button::builder()
let button = Button::builder()
.label("New Login")
.build();
new_login_button.connect_clicked(move |_| {
let new_login_command = new_login_command.clone();
thread::spawn(move || {
button.connect_clicked(clone!(@strong new_login_command => move |_| {
thread::spawn(clone!(@strong new_login_command => move || {
if let Err(err) = new_login_command.run() {
warn!("Failed to run new login command: {}", err);
} else {
DialogExitStatus::OtherError.exit();
DialogExitStatus::AuthCanceled.exit();
}
});
});
hbox.pack_start(&new_login_button, false, true, 8);
}));
}));
button_hbox.pack_start(&button, false, true, 8);
}
let unlock_button = Button::builder()
.label("Unlock")
let accept_button_box = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal)
.spacing(0)
.margin(0)
.build();
unlock_button.connect_clicked(clone!(@strong password_box, @strong auth_status_label => move |unlock_button| {
unlock_button.set_sensitive(false);
password_box.set_sensitive(false);
let username = username.clone();
let password = password_box.text().to_string();
auth_status_label.set_label("Authenticating");
auth_status_label.set_opacity(1.0);
let auth_dots_id = glib::timeout_add_local(Duration::from_millis(500), clone!(@strong auth_status_label => move || {
let s = auth_status_label.label();
auth_status_label.set_label(&(s.as_str().to_string() + "."));
Continue(true)
}));
let tx = tx.clone();
thread::spawn(move || {
let status = if authenticate(&username, &password) {
DialogExitStatus::AuthSucceeded
} else {
DialogExitStatus::AuthFailed
};
if let Err(err) = tx.send((status, auth_dots_id)) {
error!("Failed to send exit status to main thread: {}", err);
DialogExitStatus::OtherError.exit();
}
});
}));
hbox.pack_end(&unlock_button, false, true, 8);
unlock_button.set_can_default(true);
unlock_button.set_has_default(true);
button_hbox.pack_end(&accept_button_box, false, true, 8);
let timer = gtk::ProgressBar::builder()
.orientation(gtk::Orientation::Horizontal)
@ -304,9 +266,8 @@ fn main() -> anyhow::Result<()> {
.margin(2)
.build();
top_vbox.pack_end(&timer, false, false, 0);
let delta = (DIALOG_UPDATE_INTERVAL.as_millis() as f64) / (DIALOG_TIMEOUT.as_millis() as f64);
glib::timeout_add_local(DIALOG_UPDATE_INTERVAL, clone!(@strong timer => move || {
glib::timeout_add_local(DIALOG_UPDATE_INTERVAL, clone!(@weak timer => @default-return Continue(false), move || {
let new_fraction = timer.fraction() - delta;
if new_fraction <= 0.0 {
DialogExitStatus::AuthTimedOut.exit();
@ -315,40 +276,136 @@ fn main() -> anyhow::Result<()> {
Continue(true)
}));
password_box.connect_key_press_event(clone!(@strong timer => move |_, _| {
let new_fraction = timer.fraction() + 0.05;
timer.set_fraction(if new_fraction >= 1.0 { 1.0 } else { new_fraction });
Inhibit(false)
let (tx, rx) = glib::MainContext::channel(glib::Priority::default());
let username = bscreensaver_util::get_username()?;
rx.attach(None, clone!(@weak dialog, @weak auth_vbox, @weak accept_button_box, @weak timer, @strong username, => @default-return glib::Continue(false), move |op| {
for child in auth_vbox.children() {
auth_vbox.remove(&child);
}
for child in accept_button_box.children() {
accept_button_box.remove(&child);
}
debug!("PAM saysL {:?}", op);
let input_purpose = match op {
GtkConverseOp::ShowBlindPrompt(_, _) => gtk::InputPurpose::Password,
_ => gtk::InputPurpose::FreeForm,
};
match op {
GtkConverseOp::ShowInfo(msg) | GtkConverseOp::ShowError(msg) => {
let attrs = gtk::pango::AttrList::new();
attrs.insert(gtk::pango::AttrFloat::new_scale(gtk::pango::SCALE_LARGE));
let label = gtk::Label::builder()
.label(&msg)
.xalign(0.0)
.width_chars(40)
.wrap(true)
.wrap_mode(gtk::pango::WrapMode::Word)
.attributes(&attrs)
.build();
auth_vbox.pack_start(&label, false, false, 0);
},
GtkConverseOp::ShowPrompt(msg, result_sender) | GtkConverseOp::ShowBlindPrompt(msg, result_sender) => {
let hbox = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal)
.spacing(8)
.build();
auth_vbox.pack_start(&hbox, true, true, 2);
let label = Label::builder()
.label("Username:")
.xalign(0.0)
.build();
label_sg.add_widget(&label);
hbox.pack_start(&label, false, true, 8);
let username_box = Entry::builder()
.text(&username)
.sensitive(false)
.build();
entry_sg.add_widget(&username_box);
hbox.pack_start(&username_box, true, true, 8);
let hbox = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal)
.spacing(8)
.build();
auth_vbox.pack_start(&hbox, true, true, 0);
let label = Label::builder()
.label(&msg)
.xalign(0.0)
.build();
label_sg.add_widget(&label);
hbox.pack_start(&label, false, true, 8);
let input_box = Entry::builder()
.visibility(false)
.input_purpose(input_purpose)
.activates_default(true)
.width_chars(25)
.build();
entry_sg.add_widget(&input_box);
hbox.pack_start(&input_box, true, true, 8);
input_box.connect_key_press_event(clone!(@strong timer => move |_, ev| {
let new_fraction = timer.fraction() + 0.05;
timer.set_fraction(if new_fraction >= 1.0 { 1.0 } else { new_fraction });
if ev.keyval().name() == Some(GString::from("Escape")) {
DialogExitStatus::AuthCanceled.exit();
}
gtk::Inhibit(false)
}));
input_box.grab_focus();
let accept_button = Button::builder()
.label("Unlock")
.build();
accept_button.connect_clicked(clone!(@strong input_box => move |accept_button| {
accept_button.set_sensitive(false);
input_box.set_sensitive(false);
if let Err(err) = result_sender.send(input_box.text().to_string()) {
warn!("Failed to send result to main thread: {}", err);
}
}));
accept_button_box.pack_start(&accept_button, true, true, 0);
accept_button.set_can_default(true);
accept_button.set_has_default(true);
}
};
dialog.show_all();
glib::Continue(true)
}));
dialog.show_all();
thread::spawn(move || {
let authenticator = Authenticator::new(&username, tx.clone());
if let Err(err) = authenticator.authenticate() {
error!("PAM: {}", err);
if tx.send(GtkConverseOp::ShowError("Authentication Failed".to_string())).is_ok() {
thread::sleep(Duration::from_secs(2));
}
DialogExitStatus::AuthFailed.exit();
} else {
debug!("PAM success");
DialogExitStatus::AuthSucceeded.exit();
}
});
gtk::main();
DialogExitStatus::OtherError.exit(); // We should never get here!
// We never call gtk::main_quit(), so we should never get here. If
// we do, it'd be a bad bug, because we'd exit with status 0, which
// would be interpreted as "auth success".
DialogExitStatus::OtherError.exit();
}
fn set_time_label(label: &gtk::Label) {
let now = Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
label.set_label(&now);
}
fn authenticate(username: &String, password: &String) -> bool {
let mut authenticator = match pam::Authenticator::with_password("xscreensaver") {
Err(err) => {
error!("[PAM] {}", err);
return false;
},
Ok(authenticator) => authenticator,
};
authenticator.get_handler().set_credentials(username, password);
if let Err(err) = authenticator.authenticate() {
error!("[PAM] {}", err);
return false;
}
if let Err(err) = authenticator.open_session() {
error!("[PAM] {}", err);
false
} else {
true
}
}