mirror of
https://github.com/hyprwm/hyprlock.git
synced 2024-12-21 21:09:49 +01:00
auth: Support parallel fingerprint auth (#514)
* auth: Support parallel fingerprint auth I chose to use Fprint's dbus interface directly rather than going through pam (which uses Fprint's dbus interface) due to poor handling of system sleep somewhere between fprintd and pam. When preparing for sleep, fprintd puts the device to sleep, which causes VerifyStatus to emit with verify-unknown-error, which normally should be responded to by calling both Device.StopVerify and Device.Release (and this is what pam does). Unfortunately, if you try to release the device when the system is preparing for sleep, you'll get an error that the device is busy and then you can't can't claim or release the device for 30 seconds. pam also has a max timeout for pam_fprintd.so of 99 seconds, and so if we used pam, we'd have to deal with the timeouts and keep restarting the auth conversation. gdm/gnome-session lock seems to get around these issues by having a shutter on top of the lock screen that you have to interact with first that gives gnome-session a trigger to start fingerprint auth. * nix/overlays: add sdbus overlay --------- Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
This commit is contained in:
parent
b808086286
commit
f48540fcd4
8 changed files with 347 additions and 9 deletions
|
@ -56,7 +56,8 @@ pkg_check_modules(
|
|||
pangocairo
|
||||
libdrm
|
||||
gbm
|
||||
hyprutils>=0.2.3)
|
||||
hyprutils>=0.2.3
|
||||
sdbus-c++>=2.0.0)
|
||||
|
||||
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")
|
||||
add_executable(hyprlock ${SRCFILES})
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
hyprutils,
|
||||
pam,
|
||||
pango,
|
||||
sdbus-cpp,
|
||||
systemdLibs,
|
||||
wayland,
|
||||
wayland-protocols,
|
||||
wayland-scanner,
|
||||
|
@ -45,6 +47,8 @@ stdenv.mkDerivation {
|
|||
hyprutils
|
||||
pam
|
||||
pango
|
||||
sdbus-cpp
|
||||
systemdLibs
|
||||
wayland
|
||||
wayland-protocols
|
||||
];
|
||||
|
|
|
@ -15,6 +15,7 @@ in {
|
|||
hyprlock = lib.composeManyExtensions [
|
||||
inputs.hyprlang.overlays.default
|
||||
inputs.hyprutils.overlays.default
|
||||
inputs.self.overlays.sdbuscpp
|
||||
(final: prev: {
|
||||
hyprlock = prev.callPackage ./default.nix {
|
||||
stdenv = prev.gcc13Stdenv;
|
||||
|
@ -23,4 +24,17 @@ in {
|
|||
};
|
||||
})
|
||||
];
|
||||
|
||||
sdbuscpp = final: prev: {
|
||||
sdbus-cpp = prev.sdbus-cpp.overrideAttrs (self: super: {
|
||||
version = "2.0.0";
|
||||
|
||||
src = final.fetchFromGitHub {
|
||||
owner = "Kistler-group";
|
||||
repo = "sdbus-cpp";
|
||||
rev = "refs/tags/v${self.version}";
|
||||
hash = "sha256-W8V5FRhV3jtERMFrZ4gf30OpIQLYoj2yYGpnYOmH2+g=";
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -50,6 +50,9 @@ void CConfigManager::init() {
|
|||
m_config.addConfigValue("general:immediate_render", Hyprlang::INT{0});
|
||||
m_config.addConfigValue("general:pam_module", Hyprlang::STRING{"hyprlock"});
|
||||
m_config.addConfigValue("general:fractional_scaling", Hyprlang::INT{2});
|
||||
m_config.addConfigValue("general:enable_fingerprint", Hyprlang::INT{0});
|
||||
m_config.addConfigValue("general:fingerprint_ready_message", Hyprlang::STRING{"(Scan fingerprint to unlock)"});
|
||||
m_config.addConfigValue("general:fingerprint_present_message", Hyprlang::STRING{"Scanning fingerprint"});
|
||||
|
||||
m_config.addSpecialCategory("background", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
|
||||
m_config.addSpecialConfigValue("background", "monitor", Hyprlang::STRING{""});
|
||||
|
|
240
src/core/Fingerprint.cpp
Normal file
240
src/core/Fingerprint.cpp
Normal file
|
@ -0,0 +1,240 @@
|
|||
#include "Fingerprint.hpp"
|
||||
#include "../helpers/Log.hpp"
|
||||
#include "../config/ConfigManager.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
|
||||
static const auto FPRINT = sdbus::ServiceName{"net.reactivated.Fprint"};
|
||||
static const auto DEVICE = sdbus::ServiceName{"net.reactivated.Fprint.Device"};
|
||||
static const auto MANAGER = sdbus::ServiceName{"net.reactivated.Fprint.Manager"};
|
||||
static const auto LOGIN_MANAGER = sdbus::ServiceName{"org.freedesktop.login1.Manager"};
|
||||
static const auto RETRY_MESSAGE = "Could not match fingerprint. Try again.";
|
||||
|
||||
enum MatchResult {
|
||||
MATCH_INVALID = 0,
|
||||
MATCH_NO_MATCH,
|
||||
MATCH_MATCHED,
|
||||
MATCH_RETRY,
|
||||
MATCH_SWIPE_TOO_SHORT,
|
||||
MATCH_FINGER_NOT_CENTERED,
|
||||
MATCH_REMOVE_AND_RETRY,
|
||||
MATCH_DISCONNECTED,
|
||||
MATCH_UNKNOWN_ERROR,
|
||||
};
|
||||
|
||||
static std::map<std::string, MatchResult> s_mapStringToTestType = {{"verify-no-match", MATCH_NO_MATCH},
|
||||
{"verify-match", MATCH_MATCHED},
|
||||
{"verify-retry-scan", MATCH_RETRY},
|
||||
{"verify-swipe-too-short", MATCH_SWIPE_TOO_SHORT},
|
||||
{"verify-finger-not-centered", MATCH_FINGER_NOT_CENTERED},
|
||||
{"verify-remove-and-retry", MATCH_REMOVE_AND_RETRY},
|
||||
{"verify-disconnected", MATCH_DISCONNECTED},
|
||||
{"verify-unknown-error", MATCH_UNKNOWN_ERROR}};
|
||||
|
||||
CFingerprint::CFingerprint() {
|
||||
static auto* const PFINGERPRINTREADY = (Hyprlang::STRING*)(g_pConfigManager->getValuePtr("general:fingerprint_ready_message"));
|
||||
m_sFingerprintReady = *PFINGERPRINTREADY;
|
||||
static auto* const PFINGERPRINTPRESENT = (Hyprlang::STRING*)(g_pConfigManager->getValuePtr("general:fingerprint_present_message"));
|
||||
m_sFingerprintPresent = *PFINGERPRINTPRESENT;
|
||||
static auto* const PENABLEFINGERPRINT = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:enable_fingerprint");
|
||||
m_bEnabled = **PENABLEFINGERPRINT;
|
||||
}
|
||||
|
||||
std::shared_ptr<sdbus::IConnection> CFingerprint::start() {
|
||||
if (!m_bEnabled)
|
||||
return {};
|
||||
m_sDBUSState.connection = sdbus::createSystemBusConnection();
|
||||
registerSleepHandler();
|
||||
|
||||
// When entering sleep, the wake signal will trigger startVerify().
|
||||
if (m_sDBUSState.sleeping)
|
||||
return m_sDBUSState.connection;
|
||||
|
||||
startVerify();
|
||||
|
||||
return m_sDBUSState.connection;
|
||||
}
|
||||
|
||||
bool CFingerprint::isAuthenticated() {
|
||||
return m_bAuthenticated;
|
||||
}
|
||||
|
||||
std::optional<std::string> CFingerprint::getLastMessage() {
|
||||
return m_sDBUSState.message.empty() ? std::nullopt : std::optional(m_sDBUSState.message);
|
||||
}
|
||||
|
||||
void CFingerprint::terminate() {
|
||||
if (!m_sDBUSState.abort)
|
||||
releaseDevice();
|
||||
}
|
||||
|
||||
void CFingerprint::registerSleepHandler() {
|
||||
m_sDBUSState.login = sdbus::createProxy(*m_sDBUSState.connection, sdbus::ServiceName{"org.freedesktop.login1"}, sdbus::ObjectPath{"/org/freedesktop/login1"});
|
||||
m_sDBUSState.sleeping = m_sDBUSState.login->getProperty("PreparingForSleep").onInterface(LOGIN_MANAGER).get<bool>();
|
||||
m_sDBUSState.login->uponSignal("PrepareForSleep").onInterface(LOGIN_MANAGER).call([this](bool start) {
|
||||
Debug::log(LOG, "fprint: PrepareForSleep (start: {})", start);
|
||||
if (start) {
|
||||
m_sDBUSState.sleeping = true;
|
||||
stopVerify();
|
||||
m_sDBUSState.inhibitLock.reset();
|
||||
} else {
|
||||
m_sDBUSState.sleeping = false;
|
||||
inhibitSleep();
|
||||
startVerify();
|
||||
}
|
||||
});
|
||||
if (!m_sDBUSState.sleeping)
|
||||
inhibitSleep();
|
||||
}
|
||||
|
||||
void CFingerprint::inhibitSleep() {
|
||||
m_sDBUSState.login->callMethod("Inhibit")
|
||||
.onInterface(LOGIN_MANAGER)
|
||||
.withArguments("sleep", "hyprlock", "Fingerprint verifcation must be stopped before sleep", "delay")
|
||||
.storeResultsTo(m_sDBUSState.inhibitLock);
|
||||
}
|
||||
|
||||
bool CFingerprint::createDeviceProxy() {
|
||||
auto proxy = sdbus::createProxy(*m_sDBUSState.connection, FPRINT, sdbus::ObjectPath{"/net/reactivated/Fprint/Manager"});
|
||||
|
||||
sdbus::ObjectPath path;
|
||||
try {
|
||||
proxy->callMethod("GetDefaultDevice").onInterface(MANAGER).storeResultsTo(path);
|
||||
} catch (sdbus::Error& e) {
|
||||
Debug::log(WARN, "fprint: couldn't connect to Fprint service ({})", e.what());
|
||||
return false;
|
||||
}
|
||||
Debug::log(LOG, "fprint: using device path {}", path.c_str());
|
||||
m_sDBUSState.device = sdbus::createProxy(*m_sDBUSState.connection, FPRINT, path);
|
||||
|
||||
m_sDBUSState.device->uponSignal("VerifyFingerSelected").onInterface(DEVICE).call([](const std::string& finger) { Debug::log(LOG, "fprint: finger selected: {}", finger); });
|
||||
m_sDBUSState.device->uponSignal("VerifyStatus").onInterface(DEVICE).call([this](const std::string& result, const bool done) { handleVerifyStatus(result, done); });
|
||||
|
||||
m_sDBUSState.device->uponSignal("PropertiesChanged")
|
||||
.onInterface("org.freedesktop.DBus.Properties")
|
||||
.call([this](const std::string& interface, const std::map<std::string, sdbus::Variant>& properties) {
|
||||
if (interface != DEVICE || m_sDBUSState.done)
|
||||
return;
|
||||
|
||||
try {
|
||||
const auto presentVariant = properties.at("finger-present");
|
||||
bool isPresent = presentVariant.get<bool>();
|
||||
if (!isPresent)
|
||||
return;
|
||||
m_sDBUSState.message = m_sFingerprintPresent;
|
||||
g_pHyprlock->enqueueForceUpdateTimers();
|
||||
} catch (std::out_of_range& e) {}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CFingerprint::handleVerifyStatus(const std::string& result, bool done) {
|
||||
Debug::log(LOG, "fprint: handling status {}", result);
|
||||
auto matchResult = s_mapStringToTestType[result];
|
||||
if (m_sDBUSState.sleeping && matchResult != MATCH_DISCONNECTED)
|
||||
return;
|
||||
switch (matchResult) {
|
||||
case MATCH_INVALID: Debug::log(WARN, "fprint: unknown status: {}", result); break;
|
||||
case MATCH_NO_MATCH:
|
||||
stopVerify();
|
||||
if (m_sDBUSState.retries >= 3) {
|
||||
m_sDBUSState.message = "Fingerprint auth disabled: too many failed attempts";
|
||||
} else if (startVerify(/* showMessage= */ false)) {
|
||||
done = false;
|
||||
m_sDBUSState.retries++;
|
||||
m_sDBUSState.message = RETRY_MESSAGE;
|
||||
} else {
|
||||
m_sDBUSState.message = "Fingerprint auth disabled: could not restart verification";
|
||||
}
|
||||
break;
|
||||
case MATCH_UNKNOWN_ERROR:
|
||||
stopVerify();
|
||||
m_sDBUSState.message = "Unknown fingerprint error, disabling fingerprint auth";
|
||||
break;
|
||||
case MATCH_MATCHED:
|
||||
stopVerify();
|
||||
m_bAuthenticated = true;
|
||||
m_sDBUSState.message = "";
|
||||
g_pHyprlock->unlock();
|
||||
break;
|
||||
case MATCH_RETRY: m_sDBUSState.message = "Please retry fingerprint scan"; break;
|
||||
case MATCH_SWIPE_TOO_SHORT: m_sDBUSState.message = "Swipe too short - try again"; break;
|
||||
case MATCH_FINGER_NOT_CENTERED: m_sDBUSState.message = "Finger not centered - try again"; break;
|
||||
case MATCH_REMOVE_AND_RETRY: m_sDBUSState.message = "Remove your finger and try again"; break;
|
||||
case MATCH_DISCONNECTED:
|
||||
m_sDBUSState.message = "Fingerprint device disconnected";
|
||||
m_sDBUSState.abort = true;
|
||||
break;
|
||||
}
|
||||
g_pHyprlock->enqueueForceUpdateTimers();
|
||||
if (done || m_sDBUSState.abort) {
|
||||
m_sDBUSState.done = true;
|
||||
m_sDBUSState.connection->leaveEventLoop();
|
||||
}
|
||||
}
|
||||
|
||||
bool CFingerprint::claimDevice() {
|
||||
try {
|
||||
const auto currentUser = ""; // Empty string means use the caller's id.
|
||||
m_sDBUSState.device->callMethod("Claim").onInterface(DEVICE).withArguments(currentUser);
|
||||
} catch (sdbus::Error& e) {
|
||||
Debug::log(WARN, "fprint: could not claim device, {}", e.what());
|
||||
return false;
|
||||
}
|
||||
Debug::log(LOG, "fprint: claimed device");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CFingerprint::startVerify(bool updateMessage) {
|
||||
if (!m_sDBUSState.device) {
|
||||
if (!createDeviceProxy())
|
||||
return false;
|
||||
|
||||
claimDevice();
|
||||
}
|
||||
try {
|
||||
auto finger = "any"; // Any finger.
|
||||
m_sDBUSState.device->callMethod("VerifyStart").onInterface(DEVICE).withArguments(finger);
|
||||
} catch (sdbus::Error& e) {
|
||||
Debug::log(WARN, "fprint: could not start verifying, {}", e.what());
|
||||
return false;
|
||||
}
|
||||
Debug::log(LOG, "fprint: started verifying");
|
||||
if (updateMessage) {
|
||||
m_sDBUSState.message = m_sFingerprintReady;
|
||||
g_pHyprlock->enqueueForceUpdateTimers();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CFingerprint::stopVerify() {
|
||||
if (!m_sDBUSState.device)
|
||||
return false;
|
||||
try {
|
||||
m_sDBUSState.device->callMethod("VerifyStop").onInterface(DEVICE);
|
||||
} catch (sdbus::Error& e) {
|
||||
Debug::log(WARN, "fprint: could not stop verifying, {}", e.what());
|
||||
return false;
|
||||
}
|
||||
Debug::log(LOG, "fprint: stopped verification");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CFingerprint::releaseDevice() {
|
||||
if (!m_sDBUSState.device)
|
||||
return false;
|
||||
try {
|
||||
m_sDBUSState.device->callMethod("Release").onInterface(DEVICE);
|
||||
} catch (sdbus::Error& e) {
|
||||
Debug::log(WARN, "fprint: could not release device, {}", e.what());
|
||||
return false;
|
||||
}
|
||||
Debug::log(LOG, "fprint: released device");
|
||||
return true;
|
||||
}
|
51
src/core/Fingerprint.hpp
Normal file
51
src/core/Fingerprint.hpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
#pragma once
|
||||
|
||||
#include "hyprlock.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <sdbus-c++/sdbus-c++.h>
|
||||
|
||||
class CFingerprint {
|
||||
public:
|
||||
CFingerprint();
|
||||
|
||||
std::shared_ptr<sdbus::IConnection> start();
|
||||
bool isAuthenticated();
|
||||
std::optional<std::string> getLastMessage();
|
||||
void terminate();
|
||||
|
||||
private:
|
||||
struct SDBUSState {
|
||||
std::string message = "";
|
||||
|
||||
std::shared_ptr<sdbus::IConnection> connection;
|
||||
std::unique_ptr<sdbus::IProxy> login;
|
||||
std::unique_ptr<sdbus::IProxy> device;
|
||||
sdbus::UnixFd inhibitLock;
|
||||
|
||||
bool abort = false;
|
||||
bool done = false;
|
||||
int retries = 0;
|
||||
bool sleeping = false;
|
||||
} m_sDBUSState;
|
||||
|
||||
std::string m_sFingerprintReady;
|
||||
std::string m_sFingerprintPresent;
|
||||
bool m_bAuthenticated = false;
|
||||
bool m_bEnabled = false;
|
||||
|
||||
void handleVerifyStatus(const std::string& result, const bool done);
|
||||
|
||||
void registerSleepHandler();
|
||||
void inhibitSleep();
|
||||
|
||||
bool createDeviceProxy();
|
||||
bool claimDevice();
|
||||
bool startVerify(bool updateMessage = true);
|
||||
bool stopVerify();
|
||||
bool releaseDevice();
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CFingerprint> g_pFingerprint;
|
|
@ -4,6 +4,7 @@
|
|||
#include "../renderer/Renderer.hpp"
|
||||
#include "Auth.hpp"
|
||||
#include "Egl.hpp"
|
||||
#include "Fingerprint.hpp"
|
||||
#include "linux-dmabuf-unstable-v1-protocol.h"
|
||||
#include <sys/wait.h>
|
||||
#include <sys/poll.h>
|
||||
|
@ -17,6 +18,7 @@
|
|||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <sdbus-c++/sdbus-c++.h>
|
||||
|
||||
CHyprlock::CHyprlock(const std::string& wlDisplay, const bool immediate, const bool immediateRender, const bool noFadeIn) {
|
||||
m_sWaylandState.display = wl_display_connect(wlDisplay.empty() ? nullptr : wlDisplay.c_str());
|
||||
|
@ -417,6 +419,9 @@ void CHyprlock::run() {
|
|||
g_pAuth = std::make_unique<CAuth>();
|
||||
g_pAuth->start();
|
||||
|
||||
g_pFingerprint = std::make_unique<CFingerprint>();
|
||||
std::shared_ptr<sdbus::IConnection> conn = g_pFingerprint->start();
|
||||
|
||||
registerSignalAction(SIGUSR1, handleUnlockSignal, SA_RESTART);
|
||||
registerSignalAction(SIGUSR2, handleForceUpdateSignal);
|
||||
registerSignalAction(SIGRTMIN, handlePollTerminate);
|
||||
|
@ -425,16 +430,22 @@ void CHyprlock::run() {
|
|||
|
||||
createSessionLockSurfaces();
|
||||
|
||||
pollfd pollfds[] = {
|
||||
{
|
||||
.fd = wl_display_get_fd(m_sWaylandState.display),
|
||||
.events = POLLIN,
|
||||
},
|
||||
pollfd pollfds[2];
|
||||
pollfds[0] = {
|
||||
.fd = wl_display_get_fd(m_sWaylandState.display),
|
||||
.events = POLLIN,
|
||||
};
|
||||
if (conn) {
|
||||
pollfds[1] = {
|
||||
.fd = conn->getEventLoopPollData().fd,
|
||||
.events = POLLIN,
|
||||
};
|
||||
}
|
||||
size_t fdcount = conn ? 2 : 1;
|
||||
|
||||
std::thread pollThr([this, &pollfds]() {
|
||||
std::thread pollThr([this, &pollfds, fdcount]() {
|
||||
while (!m_bTerminate) {
|
||||
int ret = poll(pollfds, 1, 5000 /* 5 seconds, reasonable. Just in case we need to terminate and the signal fails */);
|
||||
int ret = poll(pollfds, fdcount, 5000 /* 5 seconds, reasonable. Just in case we need to terminate and the signal fails */);
|
||||
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR)
|
||||
|
@ -446,7 +457,7 @@ void CHyprlock::run() {
|
|||
exit(1);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 1; ++i) {
|
||||
for (size_t i = 0; i < fdcount; ++i) {
|
||||
if (pollfds[i].revents & POLLHUP) {
|
||||
Debug::log(CRIT, "[core] Disconnected from pollfd id {}", i);
|
||||
attemptRestoreOnDeath();
|
||||
|
@ -509,6 +520,12 @@ void CHyprlock::run() {
|
|||
|
||||
m_sLoopState.event = false;
|
||||
|
||||
if (pollfds[1].revents & POLLIN /* dbus */) {
|
||||
while (conn && conn->processPendingEvent()) {
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
if (pollfds[0].revents & POLLIN /* wl */) {
|
||||
Debug::log(TRACE, "got wl event");
|
||||
wl_display_flush(m_sWaylandState.display);
|
||||
|
@ -572,6 +589,7 @@ void CHyprlock::run() {
|
|||
pthread_kill(pollThr.native_handle(), SIGRTMIN);
|
||||
|
||||
g_pAuth->terminate();
|
||||
g_pFingerprint->terminate();
|
||||
|
||||
// wait for threads to exit cleanly to avoid a coredump
|
||||
pollThr.join();
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "../../helpers/Log.hpp"
|
||||
#include "../../core/hyprlock.hpp"
|
||||
#include "../../core/Auth.hpp"
|
||||
#include "../../core/Fingerprint.hpp"
|
||||
#include <chrono>
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
|
@ -166,6 +167,12 @@ IWidget::SFormatResult IWidget::formatString(std::string in) {
|
|||
result.allowForceUpdate = true;
|
||||
}
|
||||
|
||||
if (in.contains("$FPRINTMESSAGE")) {
|
||||
const auto FPRINTMESSAGE = g_pFingerprint->getLastMessage();
|
||||
replaceInString(in, "$FPRINTMESSAGE", FPRINTMESSAGE.has_value() ? FPRINTMESSAGE.value() : "");
|
||||
result.allowForceUpdate = true;
|
||||
}
|
||||
|
||||
if (in.starts_with("cmd[") && in.contains("]")) {
|
||||
// this is a command
|
||||
CVarList vars(in.substr(4, in.find_first_of(']') - 4), 0, ',', true);
|
||||
|
|
Loading…
Reference in a new issue