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

Commit d211cd6

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 698139b commit d211cd6

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

@@ -825,8 +830,10 @@ void BuildTitle(char *output, size_t output_size, const char *input) {
825830
* \param title The title of the message.
826831
* \param str The message itself.
827832
* \param is_warning Whether to use the warning style to display the message.
833+
* \param show_kbd_indicators Whether to show kbd indicators (like Caps Lock).
828834
*/
829-
void DisplayMessage(const char *title, const char *str, int is_warning) {
835+
void DisplayMessage(const char *title, const char *str, int is_warning,
836+
int show_kbd_indicators) {
830837
char full_title[256];
831838
BuildTitle(full_title, sizeof(full_title), title);
832839

@@ -841,8 +848,8 @@ void DisplayMessage(const char *title, const char *str, int is_warning) {
841848

842849
int indicators_warning = 0;
843850
int have_multiple_layouts = 0;
844-
const char *indicators =
845-
GetIndicators(&indicators_warning, &have_multiple_layouts);
851+
const char *indicators = show_kbd_indicators ?
852+
GetIndicators(&indicators_warning, &have_multiple_layouts) : "";
846853
int len_indicators = strlen(indicators);
847854
int tw_indicators = TextWidth(indicators, len_indicators);
848855

@@ -1030,6 +1037,88 @@ void ShowFromArray(const char **array, size_t displaymarker, char *displaybuf,
10301037
*displaylen = strlen(selection);
10311038
}
10321039

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

@@ -1208,7 +1297,7 @@ int Prompt(const char *msg, char **response, int echo) {
12081297
}
12091298
}
12101299
}
1211-
DisplayMessage(msg, priv.displaybuf, 0);
1300+
DisplayMessage(msg, priv.displaybuf, 0, 1);
12121301

12131302
if (!played_sound) {
12141303
PlaySound(SOUND_PROMPT);
@@ -1318,7 +1407,7 @@ int Prompt(const char *msg, char **response, int echo) {
13181407
// We continue anyway, as the user being unable to unlock the screen
13191408
// is worse. But let's alert the user of this.
13201409
DisplayMessage("Error", "Password has not been stored securely.",
1321-
1);
1410+
1, 1);
13221411
WaitForKeypress(1);
13231412
}
13241413
if (priv.pwlen != 0) {
@@ -1450,18 +1539,20 @@ int Authenticate() {
14501539
char type = ReadPacket(requestfd[0], &message, 1);
14511540
switch (type) {
14521541
case PTYPE_INFO_MESSAGE:
1453-
DisplayMessage("PAM says", message, 0);
1542+
if (!PromptStaticMessage("PAM message", message, 0, requestfd[0])) {
1543+
DisplayMessage("Processing...", "", 0, 0);
1544+
if (kill_authproto_after_timeout) {
1545+
kill(childpid, SIGTERM);
1546+
}
1547+
goto done;
1548+
}
14541549
explicit_bzero(message, strlen(message));
14551550
free(message);
1456-
PlaySound(SOUND_INFO);
1457-
WaitForKeypress(1);
14581551
break;
14591552
case PTYPE_ERROR_MESSAGE:
1460-
DisplayMessage("Error", message, 1);
1553+
PromptStaticMessage("Error", message, 1, requestfd[0]);
14611554
explicit_bzero(message, strlen(message));
14621555
free(message);
1463-
PlaySound(SOUND_ERROR);
1464-
WaitForKeypress(1);
14651556
break;
14661557
case PTYPE_PROMPT_LIKE_USERNAME:
14671558
if (Prompt(message, &response, 1)) {
@@ -1473,7 +1564,7 @@ int Authenticate() {
14731564
}
14741565
explicit_bzero(message, strlen(message));
14751566
free(message);
1476-
DisplayMessage("Processing...", "", 0);
1567+
DisplayMessage("Processing...", "", 0, 1);
14771568
break;
14781569
case PTYPE_PROMPT_LIKE_PASSWORD:
14791570
if (Prompt(message, &response, 0)) {
@@ -1485,7 +1576,7 @@ int Authenticate() {
14851576
}
14861577
explicit_bzero(message, strlen(message));
14871578
free(message);
1488-
DisplayMessage("Processing...", "", 0);
1579+
DisplayMessage("Processing...", "", 0, 1);
14891580
break;
14901581
case 0:
14911582
goto done;
@@ -1602,6 +1693,8 @@ int main(int argc_local, char **argv_local) {
16021693
GetIntSetting("XSECURELOCK_BURNIN_MITIGATION_DYNAMIC", 0);
16031694

16041695
prompt_timeout = GetIntSetting("XSECURELOCK_AUTH_TIMEOUT", 5 * 60);
1696+
kill_authproto_after_timeout =
1697+
GetIntSetting("XSECURELOCK_AUTHPROTO_KILL_ON_TIMEOUT", 0);
16051698
show_username = GetIntSetting("XSECURELOCK_SHOW_USERNAME", 1);
16061699
show_hostname = GetIntSetting("XSECURELOCK_SHOW_HOSTNAME", 1);
16071700
paranoid_password_flag = GetIntSetting(

0 commit comments

Comments
 (0)