feat: take the face in the lock screens of hyprland and niri
This commit is contained in:
1 parent
2a9b0d3f91
commit
c693ca216c
27 files changed
+1850
-1219
No files matched your search
@@ -130,7 +130,9 @@ void LockController::warmUp()
|
||||
|
||||
void LockController::startScan(const QString &why)
|
||||
{
|
||||
if (!m_locked || m_scan || m_stopped) {
|
||||
// A lock screen this cannot open (gtklock, waylock, a shell's own) asks
|
||||
// for the face itself, through PAM, when Enter is pressed.
|
||||
if (!m_locked || m_scan || m_stopped || !m_lock.canUnlock()) {
|
||||
return;
|
||||
}
|
||||
qInfo("scanning (%s)", qPrintable(why));
|
||||
|
||||
@@ -54,7 +54,8 @@ LockWatcher::LockWatcher(QObject *parent)
|
||||
|
||||
findSession();
|
||||
|
||||
if (Wayland::hasGlobal("ext_session_lock_manager_v1")) {
|
||||
m_ownLockers = Wayland::hasGlobal("ext_session_lock_manager_v1");
|
||||
if (m_ownLockers) {
|
||||
connect(&m_poll, &QTimer::timeout, this, &LockWatcher::pollLockers);
|
||||
m_poll.start(PollMs);
|
||||
// Not from here: nobody is connected yet to hear about a lock screen
|
||||
@@ -161,6 +162,11 @@ void LockWatcher::pollLockers()
|
||||
update();
|
||||
}
|
||||
|
||||
bool LockWatcher::canUnlock() const
|
||||
{
|
||||
return !m_ownLockers || m_lockerRunning;
|
||||
}
|
||||
|
||||
void LockWatcher::update()
|
||||
{
|
||||
const bool locked = m_screenSaver || m_lockedHint || m_lockerRunning;
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
// (ext-session-lock).
|
||||
//
|
||||
// hyprlock and swaylock do not listen to logind. They unlock on SIGUSR1.
|
||||
// Any other lock screen of that kind only opens itself: it gets the face
|
||||
// through the PAM module in its own stack (see src/lib/pam.sh), when Enter is
|
||||
// pressed.
|
||||
//
|
||||
// None of this lowers the bar: any program running as this user can already
|
||||
// ask logind to unlock the session, or send its own lock screen a signal. The
|
||||
@@ -35,6 +38,9 @@ public:
|
||||
{
|
||||
return m_locked;
|
||||
}
|
||||
// Whether unlock() can open the lock screen that is up: always where
|
||||
// logind unlocks (Plasma, GNOME), only hyprlock and swaylock elsewhere.
|
||||
bool canUnlock() const;
|
||||
void unlock();
|
||||
|
||||
Q_SIGNALS:
|
||||
@@ -55,6 +61,8 @@ private:
|
||||
bool m_lockedHint = false;
|
||||
bool m_lockerRunning = false;
|
||||
bool m_locked = false;
|
||||
// The compositor's lock screen is a program of its own.
|
||||
bool m_ownLockers = false;
|
||||
QString m_session;
|
||||
QTimer m_poll;
|
||||
};
|
||||
+10
-1
@@ -468,7 +468,7 @@ void Server::handleVerify(Client *client, const QJsonObject &request)
|
||||
}
|
||||
|
||||
QString purpose = request.value(u"purpose").toString();
|
||||
static const QStringList purposes = {QStringLiteral("unlock"), QStringLiteral("sudo"), QStringLiteral("polkit"), QStringLiteral("test")};
|
||||
static const QStringList purposes = {QStringLiteral("unlock"), QStringLiteral("sudo"), QStringLiteral("polkit"), QStringLiteral("lockscreen"), QStringLiteral("test")};
|
||||
if (!purposes.contains(purpose)) {
|
||||
purpose = QStringLiteral("other");
|
||||
}
|
||||
@@ -579,6 +579,15 @@ void Server::onJobDone()
|
||||
state.lastUnlock = now;
|
||||
state.lastPurpose = scan->purpose();
|
||||
|
||||
// A lock screen that asks for the face through PAM only asks
|
||||
// again after a password, so Enter on the empty field is how a
|
||||
// scan starts there, and pam_faillock counts it as a wrong
|
||||
// password. The face was right: taken back, as a correct
|
||||
// password would.
|
||||
if (geteuid() == 0 && scan->purpose() == u"lockscreen") {
|
||||
System::resetFailedLogins(scan->uid());
|
||||
}
|
||||
|
||||
// Learn from a confident match, the way Face ID keeps up with a
|
||||
// beard growing in. Only well clear of the threshold, so the
|
||||
// samples cannot creep towards somebody else one borderline
|
||||
|
||||
+9
-1
@@ -110,6 +110,11 @@ fu_do_enable() {
|
||||
# What was on the last time face unlock was on.
|
||||
[[ $CFG_SUDO == yes ]] && ! fu_pam_enabled sudo && fu_root pam-enable sudo
|
||||
[[ $CFG_POLKIT == yes ]] && ! fu_pam_enabled polkit-1 && fu_root pam-enable polkit-1
|
||||
# The lock screens of Hyprland, Niri and the like only open themselves:
|
||||
# the face goes into their password check. On unless turned off.
|
||||
if fu_pam_lockers_here && [[ $CFG_LOCKERS == yes ]] && ! fu_pam_lockers_enabled; then
|
||||
fu_root pam-enable lockscreens
|
||||
fi
|
||||
|
||||
case "$FU_DESKTOP" in
|
||||
gnome)
|
||||
@@ -120,6 +125,9 @@ fu_do_enable() {
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
if fu_pam_lockers_here && fu_pam_lockers_enabled; then
|
||||
fu_note "$(fu_msg "The lock screen (%s) takes your face too. If it does not scan when you come back, press Enter on the empty password field." "$(fu_pam_lockers_list)")"
|
||||
fi
|
||||
|
||||
fu_ok "$(fu_msg "Face unlock is on. Lock the screen and look at it to try.")"
|
||||
if [[ $CFG_SUDO != yes && $CFG_POLKIT != yes ]]; then
|
||||
@@ -133,7 +141,7 @@ fu_do_disable() {
|
||||
[[ $FU_DESKTOP == gnome ]] && fu_gnome_extension_disable
|
||||
|
||||
local service
|
||||
for service in "${FU_PAM_SERVICES[@]}"; do
|
||||
for service in "${FU_PAM_SERVICES[@]}" "${FU_PAM_LOCKERS[@]}"; do
|
||||
if fu_pam_enabled "$service"; then
|
||||
fu_root pam-disable "$service" || true
|
||||
fi
|
||||
|
||||
@@ -126,6 +126,7 @@ fu_config_load() {
|
||||
_fu_kv_lookup "$FU_CONFIG" LockScreen yes; CFG_LOCK=no; fu_is_true "$FU_KV_VALUE" && CFG_LOCK=yes
|
||||
_fu_kv_lookup "$FU_CONFIG" Sudo no; CFG_SUDO=no; fu_is_true "$FU_KV_VALUE" && CFG_SUDO=yes
|
||||
_fu_kv_lookup "$FU_CONFIG" Polkit no; CFG_POLKIT=no; fu_is_true "$FU_KV_VALUE" && CFG_POLKIT=yes
|
||||
_fu_kv_lookup "$FU_CONFIG" LockScreens yes; CFG_LOCKERS=no; fu_is_true "$FU_KV_VALUE" && CFG_LOCKERS=yes
|
||||
|
||||
_fu_kv_lookup "$FU_SYSCONFIG" Liveness light; CFG_LIVENESS="$FU_KV_VALUE"
|
||||
_fu_kv_lookup "$FU_SYSCONFIG" Camera auto; CFG_CAMERA="$FU_KV_VALUE"
|
||||
|
||||
+27
-6
@@ -251,6 +251,9 @@ fu_ui_status() {
|
||||
fi
|
||||
_fu_row "$(fu_msg "sudo")" "$(_fu_small_onoff "$(fu_pam_enabled sudo && echo yes || echo no)")"
|
||||
_fu_row "$(fu_msg "Admin prompts")" "$(_fu_small_onoff "$(fu_pam_enabled polkit-1 && echo yes || echo no)")"
|
||||
if fu_pam_lockers_here; then
|
||||
_fu_row "$(fu_msg "Lock screens")" "$(_fu_small_onoff "$(fu_pam_lockers_enabled && echo yes || echo no)") ${FU_C_DIM}($(fu_pam_lockers_list))${FU_C_RESET}"
|
||||
fi
|
||||
_fu_row "$(fu_msg "Photo check")" "$(fu_value_label Liveness "$CFG_LIVENESS")"
|
||||
|
||||
if (( FU_ST_LOCKOUT > 0 )); then
|
||||
@@ -270,13 +273,15 @@ fu_ui_status() {
|
||||
# ---------------------------------------------------------------------------
|
||||
# Settings
|
||||
# ---------------------------------------------------------------------------
|
||||
# Format: scope|Key|type|default|label-msgid|choices|needs
|
||||
# Format: scope|Key|type|default|label-msgid|choices|needs|only
|
||||
# scope user (this user's file), sys (the system file, through sudo),
|
||||
# pam (the service of that name, through sudo), or group for a
|
||||
# heading, with only the label after it
|
||||
# type bool, choice (steps through the choices) or camera
|
||||
# needs a bool setting this one does nothing without; it is dimmed while
|
||||
# that is off
|
||||
# only lockers: only where the lock screen is a program of its own with a
|
||||
# PAM file (Hyprland, Niri), see fu_pam_lockers_here
|
||||
FU_SETTINGS=(
|
||||
"group|Lock screen"
|
||||
"user|LockScreen|bool|yes|Unlock with your face"
|
||||
@@ -285,6 +290,7 @@ FU_SETTINGS=(
|
||||
"group|Password prompts"
|
||||
"pam|sudo|bool|no|sudo in a terminal"
|
||||
"pam|polkit-1|bool|no|Admin prompts"
|
||||
"pam|lockscreens|bool|yes|Lock screens|||lockers"
|
||||
"group|Recognition"
|
||||
"sys|Liveness|choice|light|Photo check|off,light,heavy"
|
||||
"sys|Strictness|choice|normal|How closely the face has to match|relaxed,normal,strict"
|
||||
@@ -308,12 +314,17 @@ fu_setting_help() {
|
||||
fu_msg "On GNOME a small GNOME extension shows the bubble. Turning face unlock on switches it on."
|
||||
return
|
||||
;;
|
||||
hyprland:LockScreen|niri:LockScreen)
|
||||
fu_msg "Scans when you come back to hyprlock or swaylock and opens them. Any other lock screen scans when you press Enter on the empty password field."
|
||||
return
|
||||
;;
|
||||
esac
|
||||
case "$1:$2" in
|
||||
LockScreen:*) fu_msg "Unlocks the lock screen when it sees your face. Off: only your password works there." ;;
|
||||
ScanOnWake:*) fu_msg "Scans when you press a key or move the mouse on the lock screen, and when the computer wakes up." ;;
|
||||
ScanOnLock:*) fu_msg "Scans as soon as the screen locks. Off by default: if you lock it yourself, it would unlock again right away." ;;
|
||||
sudo:*) fu_msg "sudo takes your face instead of the password. No match: you type the password as usual." ;;
|
||||
lockscreens:*) fu_msg "The lock screen takes your face in its password check. Press Enter on the empty field to scan. Found here: %s." "$(fu_pam_lockers_list)" ;;
|
||||
polkit-1:*) fu_msg "The password windows of your desktop and apps, for example when you install software. No match: you type the password." ;;
|
||||
Liveness:heavy) fu_msg "You have to blink or turn your head a little. This also stops printed photos." ;;
|
||||
Liveness:off) fu_msg "No check at all. Only for trying out a camera." ;;
|
||||
@@ -378,7 +389,15 @@ _fu_setting_value() {
|
||||
case "$1" in
|
||||
user) _fu_kv_lookup "$FU_CONFIG" "$2" "$3"; FU_SETTING_VALUE="$FU_KV_VALUE" ;;
|
||||
sys) _fu_kv_lookup "$FU_SYSCONFIG" "$2" "$3"; FU_SETTING_VALUE="$FU_KV_VALUE" ;;
|
||||
pam) if fu_pam_enabled "$2"; then FU_SETTING_VALUE=yes; else FU_SETTING_VALUE=no; fi ;;
|
||||
pam)
|
||||
if [[ $2 == lockscreens ]]; then
|
||||
if fu_pam_lockers_enabled; then FU_SETTING_VALUE=yes; else FU_SETTING_VALUE=no; fi
|
||||
elif fu_pam_enabled "$2"; then
|
||||
FU_SETTING_VALUE=yes
|
||||
else
|
||||
FU_SETTING_VALUE=no
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
@@ -461,8 +480,9 @@ _fu_setting_change() {
|
||||
# Remembered, so that turning face unlock off and on again brings
|
||||
# it back.
|
||||
case "$key" in
|
||||
sudo) fu_config_set Sudo "$next" ;;
|
||||
polkit-1) fu_config_set Polkit "$next" ;;
|
||||
sudo) fu_config_set Sudo "$next" ;;
|
||||
polkit-1) fu_config_set Polkit "$next" ;;
|
||||
lockscreens) fu_config_set LockScreens "$next" ;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
@@ -474,12 +494,13 @@ _fu_setting_change() {
|
||||
# resolved before the loop.
|
||||
fu_ui_settings() {
|
||||
local -a scopes=() keys=() types=() defaults=() labels=() widths=() choices=() needs=() values=() rows=()
|
||||
local spec scope key type default label choice need locale i j frame row pad dirty=1 cursor=0 shown
|
||||
local spec scope key type default label choice need only locale i j frame row pad dirty=1 cursor=0 shown
|
||||
local width=0 wrap cols
|
||||
|
||||
locale="$(fu_ui_locale)"
|
||||
for spec in "${FU_SETTINGS[@]}"; do
|
||||
IFS='|' read -r scope key type default label choice need <<< "$spec"
|
||||
IFS='|' read -r scope key type default label choice need only <<< "$spec"
|
||||
[[ $only == lockers ]] && ! fu_pam_lockers_here && continue
|
||||
# A heading has its label where the key would be.
|
||||
[[ $scope == group ]] && label="$key" key=''
|
||||
scopes+=("$scope"); keys+=("$key"); types+=("$type"); defaults+=("$default")
|
||||
|
||||
+71
-12
@@ -1,17 +1,21 @@
|
||||
# shellcheck shell=bash
|
||||
#
|
||||
# Putting face unlock in front of sudo and polkit's admin prompts, and taking
|
||||
# it out again.
|
||||
# Putting face unlock in front of sudo, polkit's admin prompts and the lock
|
||||
# screens of Hyprland and Niri, and taking it out again.
|
||||
#
|
||||
# Two services and nothing else. The lock screen does not go through PAM at all
|
||||
# (see src/agent/lockcontroller.h), and the login screen stays with the
|
||||
# password: logging in is what unlocks the wallet, and a face has no password
|
||||
# to hand it.
|
||||
# The lock screens of Plasma and GNOME do not go through this at all: the
|
||||
# agent opens them (see src/agent/lockcontroller.h). hyprlock, swaylock and
|
||||
# the others are programs of their own that only open themselves, so the face
|
||||
# goes into their password check, and pressing Enter on the empty field is
|
||||
# how a scan starts. The login screen stays with the password: logging in is
|
||||
# what unlocks the wallet, and a face has no password to hand it.
|
||||
#
|
||||
# The line that goes in:
|
||||
#
|
||||
# -auth sufficient /usr/lib/security/pam_face_unlock.so
|
||||
#
|
||||
# with "lockscreen" after it for a lock screen (see the PAM module).
|
||||
#
|
||||
# "sufficient": a match lets the person in, anything else falls through to the
|
||||
# lines below as if this one were not there. The dash makes PAM skip it
|
||||
# quietly if the module ever goes missing, say because the package was removed
|
||||
@@ -29,6 +33,9 @@
|
||||
FU_PAM_MARK="# face-unlock: the face first, the password if that does not work"
|
||||
FU_PAM_WRAPPER_MARK="# Written by face-unlock."
|
||||
FU_PAM_SERVICES=(sudo polkit-1)
|
||||
# Lock screens with a PAM file of their own. One that uses "login" or
|
||||
# "system-auth" directly is left alone: those also let people log in.
|
||||
FU_PAM_LOCKERS=(hyprlock swaylock gtklock waylock)
|
||||
|
||||
# Overridable for the tests only; the root side never takes them from the
|
||||
# environment (see the top of face-unlock).
|
||||
@@ -52,8 +59,59 @@ fu_pam_vendor() {
|
||||
return 1
|
||||
}
|
||||
|
||||
# fu_pam_is_locker <service>
|
||||
fu_pam_is_locker() {
|
||||
local s
|
||||
for s in "${FU_PAM_LOCKERS[@]}"; do
|
||||
[[ $s == "$1" ]] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# fu_pam_line <service>
|
||||
fu_pam_line() {
|
||||
printf -- '-auth sufficient %s\n' "$FU_PAM_MODULE"
|
||||
if fu_pam_is_locker "$1"; then
|
||||
printf -- '-auth sufficient %s lockscreen\n' "$FU_PAM_MODULE"
|
||||
else
|
||||
printf -- '-auth sufficient %s\n' "$FU_PAM_MODULE"
|
||||
fi
|
||||
}
|
||||
|
||||
# fu_pam_lockers
|
||||
# The lock screens installed here, one per line.
|
||||
fu_pam_lockers() {
|
||||
local s
|
||||
for s in "${FU_PAM_LOCKERS[@]}"; do
|
||||
if [[ -f $(fu_pam_etc "$s") ]] || fu_pam_vendor "$s" > /dev/null; then
|
||||
printf '%s\n' "$s"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# fu_pam_lockers_list
|
||||
# The same, for people: "hyprlock, swaylock".
|
||||
fu_pam_lockers_list() {
|
||||
local list
|
||||
list="$(fu_pam_lockers | paste -sd ',')"
|
||||
printf '%s\n' "${list//,/, }"
|
||||
}
|
||||
|
||||
# fu_pam_lockers_here
|
||||
# Whether this desktop's lock screen is a program of its own (Hyprland,
|
||||
# Niri and the like), and one with a PAM file is installed.
|
||||
fu_pam_lockers_here() {
|
||||
[[ $FU_DESKTOP != plasma && $FU_DESKTOP != gnome && -n $(fu_pam_lockers) ]]
|
||||
}
|
||||
|
||||
# fu_pam_lockers_enabled
|
||||
# Whether every lock screen installed here takes the face, and there is one.
|
||||
fu_pam_lockers_enabled() {
|
||||
local s found=0
|
||||
while IFS= read -r s; do
|
||||
found=1
|
||||
fu_pam_enabled "$s" || return 1
|
||||
done < <(fu_pam_lockers)
|
||||
(( found ))
|
||||
}
|
||||
|
||||
# fu_pam_enabled <service>
|
||||
@@ -63,13 +121,13 @@ fu_pam_enabled() {
|
||||
[[ -r $file ]] && grep -q 'pam_face_unlock\.so' "$file"
|
||||
}
|
||||
|
||||
# fu_pam_insert <file>
|
||||
# fu_pam_insert <file> <service>
|
||||
# Prints the file with the line added before its first auth line. Debian and
|
||||
# Ubuntu have none in sudo's file, only "@include common-auth", and that
|
||||
# counts as one: after it the password has already been asked for. A file
|
||||
# with neither (which would be odd for either service) gets it at the end.
|
||||
fu_pam_insert() {
|
||||
awk -v mark="$FU_PAM_MARK" -v line="$(fu_pam_line)" '
|
||||
awk -v mark="$FU_PAM_MARK" -v line="$(fu_pam_line "$2")" '
|
||||
!done && ($0 ~ /^[[:space:]]*-?auth[[:space:]]/ || $0 ~ /^[[:space:]]*@include[[:space:]]+common-auth([[:space:]]|$)/) {
|
||||
print mark
|
||||
print line
|
||||
@@ -95,13 +153,14 @@ fu_pam_remove_lines() {
|
||||
' "$1"
|
||||
}
|
||||
|
||||
# fu_pam_wrapper <vendor file> <service>
|
||||
fu_pam_wrapper() {
|
||||
local vendor="$1"
|
||||
printf '#%%PAM-1.0\n'
|
||||
printf '%s The face first, then everything\n' "$FU_PAM_WRAPPER_MARK"
|
||||
printf '# %s does for this service. Removed again by\n' "$vendor"
|
||||
printf '# "face-unlock disable" or from its settings.\n\n'
|
||||
fu_pam_line
|
||||
fu_pam_line "$2"
|
||||
printf 'auth include %s\n' "$vendor"
|
||||
printf 'account include %s\n' "$vendor"
|
||||
printf 'password include %s\n' "$vendor"
|
||||
@@ -133,9 +192,9 @@ fu_pam_enable() {
|
||||
fu_pam_enabled "$service" && return 0
|
||||
|
||||
if [[ -f $file ]]; then
|
||||
content="$(fu_pam_insert "$file")" || return 1
|
||||
content="$(fu_pam_insert "$file" "$service")" || return 1
|
||||
elif vendor="$(fu_pam_vendor "$service")"; then
|
||||
content="$(fu_pam_wrapper "$vendor")"
|
||||
content="$(fu_pam_wrapper "$vendor" "$service")"
|
||||
else
|
||||
fu_bad "$(fu_msg "There is no PAM configuration for %s on this system." "$service")"
|
||||
return 1
|
||||
|
||||
+17
-9
@@ -9,8 +9,8 @@
|
||||
# the root side a command of one's own.
|
||||
#
|
||||
# --root set <Key> <Value> one line of /etc/face-unlock/config
|
||||
# --root pam-enable <service> sudo or polkit-1, see pam.sh
|
||||
# --root pam-disable <service>
|
||||
# --root pam-enable <service> sudo, polkit-1, a lock screen, or lockscreens
|
||||
# --root pam-disable <service> for all lock screens installed; see pam.sh
|
||||
# --root socket-enable start the daemon's socket, and at boot
|
||||
# --root socket-disable
|
||||
# --root migrate move over from plasma-face-unlock, see migrate.sh
|
||||
@@ -69,16 +69,24 @@ fu_root_verb() {
|
||||
chmod 0644 "$FU_SYSCONFIG"
|
||||
;;
|
||||
pam-enable|pam-disable)
|
||||
local service="${1:-}" known=0 s
|
||||
for s in "${FU_PAM_SERVICES[@]}"; do
|
||||
local service="${1:-}" known=0 s rc=0
|
||||
local -a services=("$service")
|
||||
for s in "${FU_PAM_SERVICES[@]}" "${FU_PAM_LOCKERS[@]}"; do
|
||||
[[ $s == "$service" ]] && known=1
|
||||
done
|
||||
(( known )) || { fu_bad "$(fu_msg "Not a service this can be used for: %s" "$service")"; return 1; }
|
||||
if [[ $verb == pam-enable ]]; then
|
||||
fu_pam_enable "$service"
|
||||
else
|
||||
fu_pam_disable "$service"
|
||||
if [[ $service == lockscreens ]]; then
|
||||
known=1
|
||||
mapfile -t services < <(fu_pam_lockers)
|
||||
fi
|
||||
(( known )) || { fu_bad "$(fu_msg "Not a service this can be used for: %s" "$service")"; return 1; }
|
||||
for s in "${services[@]}"; do
|
||||
if [[ $verb == pam-enable ]]; then
|
||||
fu_pam_enable "$s" || rc=1
|
||||
else
|
||||
fu_pam_disable "$s" || rc=1
|
||||
fi
|
||||
done
|
||||
return $rc
|
||||
;;
|
||||
socket-enable)
|
||||
systemctl enable --now "$FU_UNIT_SOCKET" > /dev/null 2>&1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// pam_face_unlock: face unlock for sudo and for admin prompts.
|
||||
// pam_face_unlock: face unlock for sudo, admin prompts and lock screens.
|
||||
//
|
||||
// All the vision is in the daemon. This asks it to scan for the user and
|
||||
// turns the answer into a PAM result, and it is meant to sit first in a stack
|
||||
@@ -18,6 +18,8 @@
|
||||
// Options:
|
||||
// purpose=sudo|polkit|other what the bubble says (default: from the
|
||||
// service name)
|
||||
// lockscreen in a lock screen's stack (hyprlock, swaylock,
|
||||
// gtklock...): see is_lock_starting()
|
||||
// socket=PATH the daemon's socket (for development)
|
||||
// timeout=SECONDS give up waiting for the daemon after this
|
||||
// debug log to the auth log what happened
|
||||
@@ -57,6 +59,7 @@ struct options {
|
||||
const char *socket;
|
||||
const char *purpose;
|
||||
int timeout;
|
||||
bool lockscreen;
|
||||
bool debug;
|
||||
};
|
||||
|
||||
@@ -65,6 +68,7 @@ static void parse_options(struct options *o, int argc, const char **argv)
|
||||
o->socket = FU_SOCKET;
|
||||
o->purpose = NULL;
|
||||
o->timeout = 25;
|
||||
o->lockscreen = false;
|
||||
o->debug = false;
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
if (strncmp(argv[i], "socket=", 7) == 0) {
|
||||
@@ -76,6 +80,8 @@ static void parse_options(struct options *o, int argc, const char **argv)
|
||||
if (o->timeout < 3 || o->timeout > 120) {
|
||||
o->timeout = 25;
|
||||
}
|
||||
} else if (strcmp(argv[i], "lockscreen") == 0) {
|
||||
o->lockscreen = true;
|
||||
} else if (strcmp(argv[i], "debug") == 0) {
|
||||
o->debug = true;
|
||||
}
|
||||
@@ -94,6 +100,36 @@ static bool nonempty(const char *s)
|
||||
return s && *s;
|
||||
}
|
||||
|
||||
// hyprlock asks PAM the moment it starts, before anybody pressed a key.
|
||||
// Scanning then would open the screen again for whoever just locked it on
|
||||
// purpose while still sitting in front of it. So a lock screen that is less
|
||||
// than two seconds old gets no scan; pressing Enter asks again, and the agent
|
||||
// scans when somebody comes back.
|
||||
static bool is_lock_starting(void)
|
||||
{
|
||||
FILE *stat = fopen("/proc/self/stat", "r");
|
||||
FILE *uptime = fopen("/proc/uptime", "r");
|
||||
unsigned long long started = 0;
|
||||
double up = 0;
|
||||
bool ok = stat && uptime && fscanf(uptime, "%lf", &up) == 1;
|
||||
if (ok) {
|
||||
// After the name in brackets, which can hold anything, the start
|
||||
// time is the 20th field.
|
||||
char line[1024];
|
||||
ok = fgets(line, sizeof(line), stat) != NULL;
|
||||
const char *p = ok ? strrchr(line, ')') : NULL;
|
||||
ok = p && sscanf(p + 2, "%*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %*u %*u %*d %*d %*d %*d %*d %*d %llu", &started) == 1;
|
||||
}
|
||||
if (stat) {
|
||||
fclose(stat);
|
||||
}
|
||||
if (uptime) {
|
||||
fclose(uptime);
|
||||
}
|
||||
const long ticks = sysconf(_SC_CLK_TCK);
|
||||
return ok && ticks > 0 && up - (double)started / (double)ticks < 2.0;
|
||||
}
|
||||
|
||||
// Whoever types this is not whoever sits in front of the camera.
|
||||
static bool is_remote(pam_handle_t *pamh)
|
||||
{
|
||||
@@ -239,6 +275,9 @@ __attribute__((visibility("default"))) PAM_EXTERN int pam_sm_authenticate(pam_ha
|
||||
const void *service = NULL;
|
||||
pam_get_item(pamh, PAM_SERVICE, &service);
|
||||
const char *purpose = o.purpose;
|
||||
if (!purpose && o.lockscreen) {
|
||||
purpose = "lockscreen";
|
||||
}
|
||||
if (!purpose) {
|
||||
purpose = !service ? "other"
|
||||
: strncmp(service, "sudo", 4) == 0 ? "sudo"
|
||||
@@ -246,6 +285,12 @@ __attribute__((visibility("default"))) PAM_EXTERN int pam_sm_authenticate(pam_ha
|
||||
: "other";
|
||||
}
|
||||
|
||||
if (o.lockscreen && is_lock_starting()) {
|
||||
if (o.debug) {
|
||||
pam_syslog(pamh, LOG_DEBUG, "the lock screen has only just started, not scanning for %s", user);
|
||||
}
|
||||
return PAM_IGNORE;
|
||||
}
|
||||
if (is_remote(pamh)) {
|
||||
if (o.debug) {
|
||||
pam_syslog(pamh, LOG_DEBUG, "remote session, not scanning for %s", user);
|
||||
|
||||
Reference in new issue
Block a user