Skip to content
This repository was archived by the owner on Apr 18, 2026. It is now read-only.

Commit 782b283

Browse files
committed
feat: enhance ui support for pam_u2f
Adds support for a interactive screen when receiving info/msg from PAM. This means that clock will keep ticking if you receive an error or info message from PAM. This is particularly useful for when using pam_u2f, where you get your U2F token blinking waiting for an input while PAM will be waiting and the screen will be left with a message "Please touch the device". Previously that screen would seem frozen, as the clock (if displayed) would be stopped and your screen would never go blank (if configured). You had two options, either finish the authentication by touching the device, or remove it so it would fail and go back to the fallback authentication mechanism (usually user/password). For that feature to work well, there's also a new env variable you should set (`XSECURELOCK_AUTHPROTO_KILL_ON_TIMEOUT`): it will kill the authproto process responsible for that auth instance. That must be done if the PAM auth module you're using keeps waiting indefinitely for an action from the user, which will inevitably make the screen hang forever if that condition is not met.
1 parent 7949d30 commit 782b283

2 files changed

Lines changed: 112 additions & 15 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,10 @@ Options to XSecureLock can be passed by environment variables:
251251
* `XSECURELOCK_AUTH_TIMEOUT`: specifies the time (in seconds) to wait for
252252
response to a prompt by `auth_x11` before giving up and reverting to
253253
the screen saver.
254+
* `XSECURELOCK_AUTHPROTO_KILL_ON_TIMEOUT`: specifies whether after the auth
255+
timeout, the `authproto` application should be killed. It is useful so that
256+
PAM modules that don't have timeout (such as `pam_u2f`) don't leave the
257+
screen on, thus never triggering blank timeout.
254258
* `XSECURELOCK_AUTH_WARNING_COLOR`: specifies the X11 color (see manpage of
255259
XParseColor) for the warning text of the auth dialog.
256260
* `XSECURELOCK_BACKGROUND_COLOR`: specifies the X11 color (see manpage

helpers/auth_x11.c

Lines changed: 108 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ limitations under the License.
2323
#include <sys/select.h> // for timeval, select, fd_set, FD_SET
2424
#include <sys/time.h> // for gettimeofday, timeval
2525
#include <time.h> // for time, nanosleep, localtime_r
26-
#include <unistd.h> // for close, _exit, dup2, pipe, dup
26+
#include <signal.h> // for SIGTERM
27+
#include <unistd.h> // for close, _exit, dup2, pipe, dup,
28+
// STDIN_FILENO, STDOUT_FILENO
2729

2830
#if __STDC_VERSION__ >= 199901L
2931
#include <inttypes.h>
@@ -75,6 +77,9 @@ const char *authproto_executable;
7577
//! The maximum time to wait at a prompt for user input in seconds.
7678
int prompt_timeout;
7779

80+
//! Whether to kill the authproto after prompt timeout is reached.
81+
int kill_authproto_after_timeout;
82+
7883
//! Number of dancers in the disco password display
7984
#define DISCO_PASSWORD_DANCERS 5
8085

@@ -821,8 +826,10 @@ void BuildTitle(char *output, size_t output_size, const char *input) {
821826
* \param title The title of the message.
822827
* \param str The message itself.
823828
* \param is_warning Whether to use the warning style to display the message.
829+
* \param show_kbd_indicators Whether to show kbd indicators (like Caps Lock).
824830
*/
825-
void DisplayMessage(const char *title, const char *str, int is_warning) {
831+
void DisplayMessage(const char *title, const char *str, int is_warning,
832+
int show_kbd_indicators) {
826833
char full_title[256];
827834
BuildTitle(full_title, sizeof(full_title), title);
828835

@@ -837,8 +844,8 @@ void DisplayMessage(const char *title, const char *str, int is_warning) {
837844

838845
int indicators_warning = 0;
839846
int have_multiple_layouts = 0;
840-
const char *indicators =
841-
GetIndicators(&indicators_warning, &have_multiple_layouts);
847+
const char *indicators = show_kbd_indicators ?
848+
GetIndicators(&indicators_warning, &have_multiple_layouts) : "";
842849
int len_indicators = strlen(indicators);
843850
int tw_indicators = TextWidth(indicators, len_indicators);
844851

@@ -1026,6 +1033,88 @@ void ShowFromArray(const char **array, size_t displaymarker, char *displaybuf,
10261033
*displaylen = strlen(selection);
10271034
}
10281035

1036+
/*! \brief Shows the user a static message such as a PAM text info or error
1037+
*
1038+
* \param title The title of the message.
1039+
* \param msg The message itself.
1040+
* \param is_warning Whether to use the warning style to display the message.
1041+
* \param extra_read_fd Pass an extra fd to monitor so in case something is
1042+
* written to it, we can capture and return immediatelly.
1043+
* \return 1 if successful, anything else otherwise.
1044+
*/
1045+
int PromptStaticMessage(const char *title, const char *msg, int is_warning, int extra_read_fd) {
1046+
XEvent x11_ev;
1047+
char inputbuf;
1048+
int result = 1;
1049+
time_t deadline = time(NULL) + prompt_timeout; // global variable
1050+
1051+
PlaySound(is_warning ? SOUND_ERROR : SOUND_INFO);
1052+
1053+
while (1) {
1054+
DisplayMessage(title, msg, is_warning, 0);
1055+
1056+
// Blocks waiting for user input or activity on extra_read_fd
1057+
struct timeval timeout;
1058+
timeout.tv_sec = 0;
1059+
timeout.tv_usec = BLINK_INTERVAL;
1060+
fd_set set;
1061+
memset(&set, 0, sizeof(set)); // For clang-analyzer.
1062+
FD_ZERO(&set);
1063+
FD_SET(STDIN_FILENO, &set);
1064+
int max_nfds = 0;
1065+
if (extra_read_fd >= 1) {
1066+
FD_SET(extra_read_fd, &set);
1067+
max_nfds = extra_read_fd;
1068+
}
1069+
int nfds = select(max_nfds + 1, &set, NULL, NULL, &timeout);
1070+
1071+
if (nfds < 0) {
1072+
LogErrno("select");
1073+
break;
1074+
}
1075+
1076+
time_t now = time(NULL);
1077+
if (now > deadline) {
1078+
Log("AUTH_TIMEOUT hit");
1079+
result = 0;
1080+
break;
1081+
}
1082+
1083+
// Select timeout reached, means we just re-render and continue the loop
1084+
if (nfds == 0) {
1085+
continue;
1086+
}
1087+
1088+
// Reset the prompt timeout, so we keep the screen on if a key is pressed
1089+
deadline = now + prompt_timeout;
1090+
1091+
// If extra_read_fd is available for reading, then we return immediatelly
1092+
if (FD_ISSET(extra_read_fd, &set)) {
1093+
break;
1094+
}
1095+
1096+
// Reads user input, but stops on EOF
1097+
if (read(STDIN_FILENO, &inputbuf, 1) <= 0) {
1098+
break;
1099+
}
1100+
1101+
// And if the user types Esc, then we quit
1102+
if (inputbuf == '\033') {
1103+
result = 0;
1104+
break;
1105+
}
1106+
}
1107+
1108+
// Handle X11 events that queued up.
1109+
while (XPending(display) && (XNextEvent(display, &x11_ev), 1)) {
1110+
if (IsMonitorChangeEvent(display, x11_ev.type)) {
1111+
per_monitor_windows_dirty = 1; // global variable
1112+
}
1113+
}
1114+
1115+
return result;
1116+
}
1117+
10291118
/*! \brief Ask a question to the user.
10301119
*
10311120
* \param msg The message.
@@ -1074,7 +1163,7 @@ int Prompt(const char *msg, char **response, int echo) {
10741163
LogErrno("mlock");
10751164
// We continue anyway, as the user being unable to unlock the screen is
10761165
// worse. But let's alert the user.
1077-
DisplayMessage("Error", "Password will not be stored securely.", 1);
1166+
DisplayMessage("Error", "Password will not be stored securely.", 1, 1);
10781167
WaitForKeypress(1);
10791168
}
10801169

@@ -1204,7 +1293,7 @@ int Prompt(const char *msg, char **response, int echo) {
12041293
}
12051294
}
12061295
}
1207-
DisplayMessage(msg, priv.displaybuf, 0);
1296+
DisplayMessage(msg, priv.displaybuf, 0, 1);
12081297

12091298
if (!played_sound) {
12101299
PlaySound(SOUND_PROMPT);
@@ -1314,7 +1403,7 @@ int Prompt(const char *msg, char **response, int echo) {
13141403
// We continue anyway, as the user being unable to unlock the screen
13151404
// is worse. But let's alert the user of this.
13161405
DisplayMessage("Error", "Password has not been stored securely.",
1317-
1);
1406+
1, 1);
13181407
WaitForKeypress(1);
13191408
}
13201409
if (priv.pwlen != 0) {
@@ -1446,18 +1535,20 @@ int Authenticate() {
14461535
char type = ReadPacket(requestfd[0], &message, 1);
14471536
switch (type) {
14481537
case PTYPE_INFO_MESSAGE:
1449-
DisplayMessage("PAM says", message, 0);
1538+
if (!PromptStaticMessage("PAM message", message, 0, requestfd[0])) {
1539+
DisplayMessage("Processing...", "", 0, 0);
1540+
if (kill_authproto_after_timeout) {
1541+
kill(childpid, SIGTERM);
1542+
}
1543+
goto done;
1544+
}
14501545
explicit_bzero(message, strlen(message));
14511546
free(message);
1452-
PlaySound(SOUND_INFO);
1453-
WaitForKeypress(1);
14541547
break;
14551548
case PTYPE_ERROR_MESSAGE:
1456-
DisplayMessage("Error", message, 1);
1549+
PromptStaticMessage("Error", message, 1, requestfd[0]);
14571550
explicit_bzero(message, strlen(message));
14581551
free(message);
1459-
PlaySound(SOUND_ERROR);
1460-
WaitForKeypress(1);
14611552
break;
14621553
case PTYPE_PROMPT_LIKE_USERNAME:
14631554
if (Prompt(message, &response, 1)) {
@@ -1469,7 +1560,7 @@ int Authenticate() {
14691560
}
14701561
explicit_bzero(message, strlen(message));
14711562
free(message);
1472-
DisplayMessage("Processing...", "", 0);
1563+
DisplayMessage("Processing...", "", 0, 1);
14731564
break;
14741565
case PTYPE_PROMPT_LIKE_PASSWORD:
14751566
if (Prompt(message, &response, 0)) {
@@ -1481,7 +1572,7 @@ int Authenticate() {
14811572
}
14821573
explicit_bzero(message, strlen(message));
14831574
free(message);
1484-
DisplayMessage("Processing...", "", 0);
1575+
DisplayMessage("Processing...", "", 0, 1);
14851576
break;
14861577
case 0:
14871578
goto done;
@@ -1598,6 +1689,8 @@ int main(int argc_local, char **argv_local) {
15981689
GetIntSetting("XSECURELOCK_BURNIN_MITIGATION_DYNAMIC", 0);
15991690

16001691
prompt_timeout = GetIntSetting("XSECURELOCK_AUTH_TIMEOUT", 5 * 60);
1692+
kill_authproto_after_timeout =
1693+
GetIntSetting("XSECURELOCK_AUTHPROTO_KILL_ON_TIMEOUT", 0);
16011694
show_username = GetIntSetting("XSECURELOCK_SHOW_USERNAME", 1);
16021695
show_hostname = GetIntSetting("XSECURELOCK_SHOW_HOSTNAME", 1);
16031696
paranoid_password_flag = GetIntSetting(

0 commit comments

Comments
 (0)