From a0d4dd81f8b21046aae9b4f9d53c03be1414c2a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Stachowiak?= Date: Sat, 15 Nov 2025 18:08:11 +0100 Subject: [PATCH 1/3] Fix ESC/timeout incorrectly counting as failed login attempt When the user presses ESC or the prompt times out, the auth UI sends PTYPE_RESPONSE_CANCELLED to the PAM conversation handler. Previously, this returned PAM_CONV_ERR, which caused some PAM modules (particularly with PAM 1.4.0+) to attempt authentication with empty input, resulting in PAM_AUTH_ERR. This was counted by pam_faillock as a failed login attempt, potentially locking users out after 3 ESC presses or timeouts. The fix returns PAM_ABORT instead of PAM_CONV_ERR when receiving PTYPE_RESPONSE_CANCELLED. This cleanly aborts the PAM authentication session without counting as a failed attempt, which is the correct semantic meaning of a user-initiated cancellation. Fixes #114 Amp-Thread-ID: https://ampcode.com/threads/T-60336806-26ca-40b3-bb77-86a913f74a0c Co-authored-by: Amp --- helpers/authproto_pam.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/helpers/authproto_pam.c b/helpers/authproto_pam.c index d060e228..fbd13bce 100644 --- a/helpers/authproto_pam.c +++ b/helpers/authproto_pam.c @@ -44,11 +44,25 @@ int ConverseOne(const struct pam_message *msg, struct pam_response *resp) { case PAM_PROMPT_ECHO_OFF: { WritePacket(1, PTYPE_PROMPT_LIKE_PASSWORD, msg->msg); char type = ReadPacket(0, &resp->resp, 0); + if (type == PTYPE_RESPONSE_CANCELLED) { + // User pressed ESC or prompt timed out. Return PAM_ABORT to cleanly + // abort the authentication session. Returning PAM_CONV_ERR instead would + // cause some PAM modules to attempt authentication with empty input, + // which trips faillock and counts as a failed login attempt. + return PAM_ABORT; + } return type == PTYPE_RESPONSE_LIKE_PASSWORD ? PAM_SUCCESS : PAM_CONV_ERR; } case PAM_PROMPT_ECHO_ON: { WritePacket(1, PTYPE_PROMPT_LIKE_USERNAME, msg->msg); char type = ReadPacket(0, &resp->resp, 0); + if (type == PTYPE_RESPONSE_CANCELLED) { + // User pressed ESC or prompt timed out. Return PAM_ABORT to cleanly + // abort the authentication session. Returning PAM_CONV_ERR instead would + // cause some PAM modules to attempt authentication with empty input, + // which trips faillock and counts as a failed login attempt. + return PAM_ABORT; + } return type == PTYPE_RESPONSE_LIKE_USERNAME ? PAM_SUCCESS : PAM_CONV_ERR; } case PAM_ERROR_MSG: From 779903c663b00cfdd8728ff9cc88d382b973ff18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Stachowiak?= Date: Sat, 15 Nov 2025 18:08:11 +0100 Subject: [PATCH 2/3] Make ESC clear password field instead of canceling prompt Change the behavior of the ESC key to clear the password input field (similar to Ctrl-U) instead of immediately canceling the authentication prompt. This provides better UX - users who accidentally start typing their password can press ESC to clear it and try again, rather than having the prompt close. Users can still cancel the prompt by waiting for the timeout, and the previous commit ensures timeout/cancellation won't count as a failed login attempt. Related to #114 Amp-Thread-ID: https://ampcode.com/threads/T-60336806-26ca-40b3-bb77-86a913f74a0c Co-authored-by: Amp --- helpers/auth_x11.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/helpers/auth_x11.c b/helpers/auth_x11.c index 58a19139..18d0daba 100644 --- a/helpers/auth_x11.c +++ b/helpers/auth_x11.c @@ -1306,8 +1306,14 @@ int Prompt(const char *msg, char **response, int echo) { BumpDisplayMarker(priv.pwlen, &priv.displaymarker, &priv.last_keystroke); break; - case 0: // Shouldn't happen. case '\033': // Escape. + // Clear the input line (like Ctrl-U). User can still press ESC + // again or wait for timeout to cancel the prompt entirely. + priv.pwlen = 0; + BumpDisplayMarker(priv.pwlen, &priv.displaymarker, + &priv.last_keystroke); + break; + case 0: // Shouldn't happen. done = 1; break; case '\r': // Return. From 6200d92cf58233bdb5919d4f74af8de058ddb1f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Stachowiak?= <50bda1cd2babf798@rdslw.online> Date: Sun, 5 Apr 2026 18:36:59 +0200 Subject: [PATCH 3/3] Add XSECURELOCK_AUTH_Y_POSITION env var for vertical dialog placement Allow configuring the vertical position of the auth dialog as a percentage (0=top, 50=center, 100=bottom), defaulting to 50 (centered) to preserve existing behavior. --- README.md | 4 ++++ helpers/auth_x11.c | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 53a1047f..c6d8f16f 100644 --- a/README.md +++ b/README.md @@ -253,6 +253,10 @@ Options to XSecureLock can be passed by environment variables: the screen saver. * `XSECURELOCK_AUTH_WARNING_COLOR`: specifies the X11 color (see manpage of XParseColor) for the warning text of the auth dialog. +* `XSECURELOCK_AUTH_Y_POSITION`: vertical position of the auth dialog as a + percentage of screen height, from 0 (top) to 100 (bottom). Defaults to 50 + (vertically centered). For example, set to 80 to position the dialog near + the bottom of the screen. * `XSECURELOCK_BACKGROUND_COLOR`: specifies the X11 color (see manpage of XParseColor) for the background of the main and saver windows. * `XSECURELOCK_BLANK_TIMEOUT`: specifies the time (in seconds) before telling diff --git a/helpers/auth_x11.c b/helpers/auth_x11.c index 18d0daba..563734d1 100644 --- a/helpers/auth_x11.c +++ b/helpers/auth_x11.c @@ -245,6 +245,10 @@ static int auth_sounds = 0; //! Whether to blink the cursor in the auth dialog. static int auth_cursor_blink = 1; +//! Vertical position of the auth dialog as a percentage (0=top, 50=center, +//! 100=bottom). +static int auth_y_position = 50; + //! Whether we only want a single auth window. static int single_auth_window = 0; @@ -570,7 +574,7 @@ void CreateOrUpdatePerMonitorWindow(size_t i, const Monitor *monitor, int w = region_w; int h = region_h; int x = monitor->x + (monitor->width - w) / 2 + x_offset; - int y = monitor->y + (monitor->height - h) / 2 + y_offset; + int y = monitor->y + (monitor->height - h) * auth_y_position / 100 + y_offset; // Clip to monitor. if (x < 0) { w += x; @@ -1620,6 +1624,9 @@ int main(int argc_local, char **argv_local) { auth_sounds = GetIntSetting("XSECURELOCK_AUTH_SOUNDS", 0); single_auth_window = GetIntSetting("XSECURELOCK_SINGLE_AUTH_WINDOW", 0); auth_cursor_blink = GetIntSetting("XSECURELOCK_AUTH_CURSOR_BLINK", 1); + auth_y_position = GetIntSetting("XSECURELOCK_AUTH_Y_POSITION", 50); + if (auth_y_position < 0) auth_y_position = 0; + if (auth_y_position > 100) auth_y_position = 100; #ifdef HAVE_XKB_EXT show_keyboard_layout = GetIntSetting("XSECURELOCK_SHOW_KEYBOARD_LAYOUT", 1);