From c46b33d18826969cdbcd0b87c6748bc023640820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Gunyh=C3=B3?= Date: Thu, 2 Jan 2025 09:10:09 +0200 Subject: [PATCH 01/11] Show PID in log --- include/log.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/log.h b/include/log.h index fc3747a15..1292abd18 100644 --- a/include/log.h +++ b/include/log.h @@ -27,7 +27,7 @@ void _swaylock_log(enum log_importance verbosity, const char *format, ...) const char *_swaylock_strip_path(const char *filepath); #define swaylock_log(verb, fmt, ...) \ - _swaylock_log(verb, "[%s:%d] " fmt, _swaylock_strip_path(__FILE__), \ + _swaylock_log(verb, "[%d:%s:%d] " fmt, getpid(), _swaylock_strip_path(__FILE__), \ __LINE__, ##__VA_ARGS__) #define swaylock_log_errno(verb, fmt, ...) \ From 4c8c29ffde505daa01eeef54dad88dec61ea6599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Gunyh=C3=B3?= Date: Thu, 2 Jan 2025 18:48:09 +0200 Subject: [PATCH 02/11] Print PAM messages to debug --- pam.c | 1 + 1 file changed, 1 insertion(+) diff --git a/pam.c b/pam.c index 3181acad1..ac653c372 100644 --- a/pam.c +++ b/pam.c @@ -35,6 +35,7 @@ static int handle_conversation(int num_msg, const struct pam_message **msg, } *resp = pam_reply; for (int i = 0; i < num_msg; ++i) { + swaylock_log(LOG_DEBUG, "PAM message %d: %s", i, msg[i]->msg); switch (msg[i]->msg_style) { case PAM_PROMPT_ECHO_OFF: case PAM_PROMPT_ECHO_ON: From 419992d0ab5758dd558c959da6302528751e021a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Gunyh=C3=B3?= Date: Thu, 2 Jan 2025 10:13:58 +0200 Subject: [PATCH 03/11] Factor out function to read a size & string from a fd --- comm.c | 35 +++++++++++++++++++++++------------ include/comm.h | 7 +++++++ 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/comm.c b/comm.c index 6add38ad3..23337f5bb 100644 --- a/comm.c +++ b/comm.c @@ -9,35 +9,46 @@ static int comm[2][2] = {{-1, -1}, {-1, -1}}; -ssize_t read_comm_request(char **buf_ptr) { +ssize_t read_string(int fd, char *(*alloc)(size_t), char **output) { size_t size; - ssize_t amt; - amt = read(comm[0][0], &size, sizeof(size)); - if (amt == 0) { + ssize_t amt = read(fd, &size, sizeof(size)); + if (amt <= 0) { + swaylock_log_errno(LOG_ERROR, "Failed to read string size"); + return amt; + } + if (size == 0) { return 0; - } else if (amt < 0) { - swaylock_log_errno(LOG_ERROR, "read pw request"); - return -1; } - swaylock_log(LOG_DEBUG, "received pw check request"); - char *buf = password_buffer_create(size); + char *buf = alloc(size); if (!buf) { return -1; } size_t offs = 0; do { - amt = read(comm[0][0], &buf[offs], size - offs); + ssize_t amt = read(fd, &buf[offs], size - offs); if (amt <= 0) { - swaylock_log_errno(LOG_ERROR, "failed to read pw"); + swaylock_log_errno(LOG_ERROR, "Failed to read string"); return -1; } offs += (size_t)amt; } while (offs < size); - *buf_ptr = buf; + *output = buf; return size; } +ssize_t read_comm_request(char **buf_ptr) { + ssize_t amt = read_string(comm[0][0], password_buffer_create, buf_ptr); + swaylock_log(LOG_DEBUG, "received pw check request"); + if (amt == 0) { + return 0; + } else if (amt < 0) { + swaylock_log(LOG_ERROR, "read pw request"); + return -1; + } + return amt; +} + bool write_comm_reply(bool success) { if (write(comm[1][1], &success, sizeof(success)) != sizeof(success)) { swaylock_log_errno(LOG_ERROR, "failed to write pw check result"); diff --git a/include/comm.h b/include/comm.h index defc2f40f..4600b984a 100644 --- a/include/comm.h +++ b/include/comm.h @@ -6,6 +6,13 @@ struct swaylock_password; bool spawn_comm_child(void); + +// Read a string from a file descriptor by first reading the size, allocating +// memory to output using alloc(size) and then reading the string data to it. +// Returns the number of bytes in the string, *not* the total no. of bytes read +// - the total number is the return value plus sizeof(size_t). +ssize_t read_string(int fd, char *(*alloc)(size_t), char **output); + ssize_t read_comm_request(char **buf_ptr); bool write_comm_reply(bool success); // Requests the provided password to be checked. The password is always cleared From c45bb3fcfe35b3469524ac214da38cecdabcd8b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Gunyh=C3=B3?= Date: Thu, 2 Jan 2025 18:47:53 +0200 Subject: [PATCH 04/11] Factor out function to write size & string to fd --- comm.c | 40 ++++++++++++++++++++++++---------------- include/comm.h | 7 +++++++ 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/comm.c b/comm.c index 23337f5bb..5f784a063 100644 --- a/comm.c +++ b/comm.c @@ -9,6 +9,26 @@ static int comm[2][2] = {{-1, -1}, {-1, -1}}; +ssize_t write_string(int fd, const char * const *string, size_t len) { + size_t offs = 0; + if (write(fd, &len, sizeof(len)) < 0) { + swaylock_log_errno(LOG_ERROR, "Failed to write string size"); + return -1; + } + + do { + ssize_t amt = write(fd, string[offs], len - offs); + if (amt < 0) { + swaylock_log_errno(LOG_ERROR, "Failed to write string"); + //TODO: different return value for different error? + return -1; + } + offs += amt; + } while (offs < len); + + return (ssize_t) len; +} + ssize_t read_string(int fd, char *(*alloc)(size_t), char **output) { size_t size; ssize_t amt = read(fd, &size, sizeof(size)); @@ -82,26 +102,14 @@ bool spawn_comm_child(void) { bool write_comm_request(struct swaylock_password *pw) { bool result = false; + ssize_t amt = write_string(comm[0][1], (const char * const *)&pw->buffer, pw->len + 1); - size_t len = pw->len + 1; - size_t offs = 0; - if (write(comm[0][1], &len, sizeof(len)) < 0) { + if (amt < 0) { swaylock_log_errno(LOG_ERROR, "Failed to request pw check"); - goto out; + } else { + result = true; } - do { - ssize_t amt = write(comm[0][1], &pw->buffer[offs], len - offs); - if (amt < 0) { - swaylock_log_errno(LOG_ERROR, "Failed to write pw buffer"); - goto out; - } - offs += amt; - } while (offs < len); - - result = true; - -out: clear_password_buffer(pw); return result; } diff --git a/include/comm.h b/include/comm.h index 4600b984a..4791357a8 100644 --- a/include/comm.h +++ b/include/comm.h @@ -7,6 +7,13 @@ struct swaylock_password; bool spawn_comm_child(void); +// Write a string to a file descriptor by first sending the size and then the +// string data. len should be the length of the string, *including* null +// termination. Returns the number of bytes in the written string, *not* the +// total no. of bytes written - the total number is the return value plus +// sizeof(size_t). +ssize_t write_string(int fd, const char * const *string, size_t len); + // Read a string from a file descriptor by first reading the size, allocating // memory to output using alloc(size) and then reading the string data to it. // Returns the number of bytes in the string, *not* the total no. of bytes read From 002f796f1df7a9e5e19ac132fd6ecfb7fc00be56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Gunyh=C3=B3?= Date: Fri, 3 Jan 2025 18:37:15 +0200 Subject: [PATCH 05/11] Rename comm_request to comm_prompt_response This helps clarify the purpose --- comm.c | 10 +++++----- include/comm.h | 11 +++++++---- pam.c | 2 +- password.c | 2 +- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/comm.c b/comm.c index 5f784a063..a3dc6a660 100644 --- a/comm.c +++ b/comm.c @@ -57,13 +57,13 @@ ssize_t read_string(int fd, char *(*alloc)(size_t), char **output) { return size; } -ssize_t read_comm_request(char **buf_ptr) { +ssize_t read_comm_prompt_response(char **buf_ptr) { ssize_t amt = read_string(comm[0][0], password_buffer_create, buf_ptr); - swaylock_log(LOG_DEBUG, "received pw check request"); + swaylock_log(LOG_DEBUG, "received response to prompt"); if (amt == 0) { return 0; } else if (amt < 0) { - swaylock_log(LOG_ERROR, "read pw request"); + swaylock_log(LOG_ERROR, "Error reading prompt response"); return -1; } return amt; @@ -100,12 +100,12 @@ bool spawn_comm_child(void) { return true; } -bool write_comm_request(struct swaylock_password *pw) { +bool write_comm_prompt_response(struct swaylock_password *pw) { bool result = false; ssize_t amt = write_string(comm[0][1], (const char * const *)&pw->buffer, pw->len + 1); if (amt < 0) { - swaylock_log_errno(LOG_ERROR, "Failed to request pw check"); + swaylock_log_errno(LOG_ERROR, "Failed to write prompt response"); } else { result = true; } diff --git a/include/comm.h b/include/comm.h index 4791357a8..8b8c76315 100644 --- a/include/comm.h +++ b/include/comm.h @@ -20,12 +20,15 @@ ssize_t write_string(int fd, const char * const *string, size_t len); // - the total number is the return value plus sizeof(size_t). ssize_t read_string(int fd, char *(*alloc)(size_t), char **output); -ssize_t read_comm_request(char **buf_ptr); bool write_comm_reply(bool success); -// Requests the provided password to be checked. The password is always cleared -// when the function returns. -bool write_comm_request(struct swaylock_password *pw); bool read_comm_reply(void); + +// Read / write the response typed by the user (password, PIN code, etc) to a +// prompt sent by the backend. The password buffer is always cleared when the +// function returns. //TODO: verify +ssize_t read_comm_prompt_response(char **buf_ptr); +bool write_comm_prompt_response(struct swaylock_password *pw); + // FD to poll for password authentication replies. int get_comm_reply_fd(void); diff --git a/pam.c b/pam.c index ac653c372..02e71ec8c 100644 --- a/pam.c +++ b/pam.c @@ -95,7 +95,7 @@ void run_pw_backend_child(void) { int pam_status = PAM_SUCCESS; while (1) { - ssize_t size = read_comm_request(&pw_buf); + ssize_t size = read_comm_prompt_response(&pw_buf); //TODO: should be moved to handle_conversation if (size < 0) { exit(EXIT_FAILURE); } else if (size == 0) { diff --git a/password.c b/password.c index 5349e1369..951b6a813 100644 --- a/password.c +++ b/password.c @@ -117,7 +117,7 @@ static void submit_password(struct swaylock_state *state) { cancel_password_clear(state); cancel_input_idle(state); - if (!write_comm_request(&state->password)) { + if (!write_comm_prompt_response(&state->password)) { state->auth_state = AUTH_STATE_INVALID; schedule_auth_idle(state); } From 3b74d682fc5bd3fb8d0811e8d6ea558fc34d00f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Gunyh=C3=B3?= Date: Sat, 4 Jan 2025 00:19:08 +0200 Subject: [PATCH 06/11] Rework comm_reply to more general message_from_backend Note that write_comm_auth_result_from_backend could return boolean (like comm_reply_did), but using ssize_t for consistency with the other comm functions. --- comm.c | 74 ++++++++++++++++++++++++++++++++++++++++++-------- include/comm.h | 28 ++++++++++++++++--- main.c | 39 ++++++++++++++++++++++---- pam.c | 2 +- 4 files changed, 121 insertions(+), 22 deletions(-) diff --git a/comm.c b/comm.c index a3dc6a660..1020ac005 100644 --- a/comm.c +++ b/comm.c @@ -69,12 +69,27 @@ ssize_t read_comm_prompt_response(char **buf_ptr) { return amt; } -bool write_comm_reply(bool success) { +ssize_t write_comm_text_message_from_backend(const char * const *msg) { + enum backend_message_type msg_type = BACKEND_MESSAGE_TYPE_TEXT; + if (write(comm[1][1], &msg_type, sizeof(msg_type)) != sizeof(msg_type)) { + swaylock_log_errno(LOG_ERROR, "failed to write message type"); + return -1; + } + return write_string(comm[1][1], msg, strlen(*msg) + 1); +} + +ssize_t write_comm_auth_result_from_backend(bool success) { + enum backend_message_type msg_type = BACKEND_MESSAGE_TYPE_AUTH_RESULT; + if (write(comm[1][1], &msg_type, sizeof(msg_type)) != sizeof(msg_type)) { + swaylock_log_errno(LOG_ERROR, "failed to write message type"); + return -1; + } + if (write(comm[1][1], &success, sizeof(success)) != sizeof(success)) { - swaylock_log_errno(LOG_ERROR, "failed to write pw check result"); - return false; + swaylock_log_errno(LOG_ERROR, "failed to write authentication result"); + return -1; } - return true; + return sizeof(success); } bool spawn_comm_child(void) { @@ -114,15 +129,52 @@ bool write_comm_prompt_response(struct swaylock_password *pw) { return result; } -bool read_comm_reply(void) { - bool result = false; - if (read(comm[1][0], &result, sizeof(result)) != sizeof(result)) { - swaylock_log_errno(LOG_ERROR, "Failed to read pw result"); - result = false; +char *malloc_str(size_t size) { + char *res = (char *) malloc(size * sizeof(char)); + if (!res) { + swaylock_log_errno(LOG_ERROR, "failed to allocate string"); + return NULL; } - return result; + return res; +} + +ssize_t read_comm_message_from_backend(enum backend_message_type *msg_type, void **data) { + enum backend_message_type read_type; + void *read_data; + + if (read(comm[1][0], &read_type, sizeof(read_type)) != sizeof(read_type)) { + swaylock_log_errno(LOG_ERROR, "Failed to read message type from backend"); + return -1; + } + + ssize_t amt; + switch(read_type) { + case BACKEND_MESSAGE_TYPE_TEXT: + amt = read_string(comm[1][0], malloc_str, (char **) &read_data); + if (amt < 0) { + swaylock_log(LOG_ERROR, "Error reading string from backend"); + return -1; + } else if (amt == 0) { + read_data = NULL; //TODO: good? + } + break; + + case BACKEND_MESSAGE_TYPE_AUTH_RESULT: + read_data = malloc(sizeof(bool)); + amt = read(comm[1][0], (bool *) read_data, sizeof(bool)); + if (amt != sizeof(bool)) { + swaylock_log(LOG_ERROR, "Error reading boolean from backend"); + return -1; + } + break; + + } + + *msg_type = read_type; + *data = read_data; + return amt; } -int get_comm_reply_fd(void) { +int get_comm_backend_message_fd(void) { return comm[1][0]; } diff --git a/include/comm.h b/include/comm.h index 8b8c76315..d128adb1b 100644 --- a/include/comm.h +++ b/include/comm.h @@ -5,6 +5,12 @@ struct swaylock_password; +enum backend_message_type { + //TODO: error messages separately? + BACKEND_MESSAGE_TYPE_TEXT, // Text info, error message or prompt + BACKEND_MESSAGE_TYPE_AUTH_RESULT, // Boolean indicating authorization success or failure +}; + bool spawn_comm_child(void); // Write a string to a file descriptor by first sending the size and then the @@ -20,8 +26,22 @@ ssize_t write_string(int fd, const char * const *string, size_t len); // - the total number is the return value plus sizeof(size_t). ssize_t read_string(int fd, char *(*alloc)(size_t), char **output); -bool write_comm_reply(bool success); -bool read_comm_reply(void); +// Read a message from the password checking backend (e.g. a prompt or the +// result of authentication) in the main thread. Read first the message type, +// then the associated data itself (size + string data for strings, just the +// boolean value for booleans). +// Returns the no. of bytes in the *data* that was read, the total no. of bytes +// read is the return value plus sizeof(enum backend_message_type). +ssize_t read_comm_message_from_backend(enum backend_message_type *msg_type, void **data); +// Write a string containing a message from the backend +// Returns the no. of bytes in the *message* that was written, the total no. of +// bytes written is the return value plus sizeof(enum backend_message_type). +ssize_t write_comm_text_message_from_backend(const char * const *msg); +// Write a boolean value indicating the result of authentication +// Returns the no. of bytes in the *data* that was written (i.e. sizeof(bool)), +// the total no. of bytes written is the return value plus +// sizeof(enum backend_message_type). +ssize_t write_comm_auth_result_from_backend(bool success); // Read / write the response typed by the user (password, PIN code, etc) to a // prompt sent by the backend. The password buffer is always cleared when the @@ -29,7 +49,7 @@ bool read_comm_reply(void); ssize_t read_comm_prompt_response(char **buf_ptr); bool write_comm_prompt_response(struct swaylock_password *pw); -// FD to poll for password authentication replies. -int get_comm_reply_fd(void); +// FD to poll for messages from the backend +int get_comm_backend_message_fd(void); #endif diff --git a/main.c b/main.c index c0d72264a..053366936 100644 --- a/main.c +++ b/main.c @@ -1034,13 +1034,15 @@ static void display_in(int fd, short mask, void *data) { } } -static void comm_in(int fd, short mask, void *data) { - if (read_comm_reply()) { +static void handle_backend_text_message(char *msg) { + //TODO: show message in UI + swaylock_log(LOG_DEBUG, "Received text message from backend: %s", msg); +} + +static void handle_auth_result(bool success) { + if (success) { // Authentication succeeded state.run_display = false; - } else if (mask & (POLLHUP | POLLERR)) { - swaylock_log(LOG_ERROR, "Password checking subprocess crashed; exiting."); - exit(EXIT_FAILURE); } else { state.auth_state = AUTH_STATE_INVALID; schedule_auth_idle(&state); @@ -1049,6 +1051,31 @@ static void comm_in(int fd, short mask, void *data) { } } +static void comm_in(int fd, short mask, void *data) { + enum backend_message_type msg_type; + void *comm_data; + ssize_t amt = read_comm_message_from_backend(&msg_type, &comm_data); + + if (amt < 0) { + swaylock_log(LOG_ERROR, "Error reading message from backend; exiting."); + exit(EXIT_FAILURE); + } else if (mask & (POLLHUP | POLLERR)) { + swaylock_log(LOG_ERROR, "Password checking subprocess crashed; exiting."); + exit(EXIT_FAILURE); + } + + switch(msg_type){ + case BACKEND_MESSAGE_TYPE_TEXT: + handle_backend_text_message((char *) comm_data); + //TODO: memory leak; where to free comm_data? + break; + case BACKEND_MESSAGE_TYPE_AUTH_RESULT: + handle_auth_result(*(bool *) comm_data); + free(comm_data); + break; + } +} + static void term_in(int fd, short mask, void *data) { state.run_display = false; } @@ -1238,7 +1265,7 @@ int main(int argc, char **argv) { loop_add_fd(state.eventloop, wl_display_get_fd(state.display), POLLIN, display_in, NULL); - loop_add_fd(state.eventloop, get_comm_reply_fd(), POLLIN, comm_in, NULL); + loop_add_fd(state.eventloop, get_comm_backend_message_fd(), POLLIN, comm_in, NULL); loop_add_fd(state.eventloop, sigusr_fds[0], POLLIN, term_in, NULL); diff --git a/pam.c b/pam.c index 02e71ec8c..62b25ae0d 100644 --- a/pam.c +++ b/pam.c @@ -112,7 +112,7 @@ void run_pw_backend_child(void) { get_pam_auth_error(pam_status)); } - if (!write_comm_reply(success)) { + if (write_comm_auth_result_from_backend(success) < 0) { exit(EXIT_FAILURE); } From e0775f4ad808e7b1077f724d66e43d9135701041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Gunyh=C3=B3?= Date: Sat, 4 Jan 2025 01:27:15 +0200 Subject: [PATCH 07/11] Move communication to PAM conversation function This gets rid of the static pw_buf. --- pam.c | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/pam.c b/pam.c index 62b25ae0d..449a8413a 100644 --- a/pam.c +++ b/pam.c @@ -10,8 +10,6 @@ #include "password-buffer.h" #include "swaylock.h" -static char *pw_buf = NULL; - void initialize_pw_backend(int argc, char **argv) { if (getuid() != geteuid() || getgid() != getegid()) { swaylock_log(LOG_ERROR, @@ -37,16 +35,39 @@ static int handle_conversation(int num_msg, const struct pam_message **msg, for (int i = 0; i < num_msg; ++i) { swaylock_log(LOG_DEBUG, "PAM message %d: %s", i, msg[i]->msg); switch (msg[i]->msg_style) { - case PAM_PROMPT_ECHO_OFF: case PAM_PROMPT_ECHO_ON: + if(write_comm_text_message_from_backend(&msg[i]->msg) < 0) { + swaylock_log(LOG_ERROR, "Failed to write message from backend"); + return PAM_ABORT; + } + #ifdef __GNUC__ + // both PAM_PROMPT_ECHO_ON and PAM_PROMPT_ECHO_OFF expect a string response + __attribute__((fallthrough)); + #endif + + case PAM_PROMPT_ECHO_OFF: + char *pw_buf = NULL; + ssize_t size = read_comm_prompt_response(&pw_buf); + if (size < 0) { + swaylock_log(LOG_ERROR, "Failed to read prompt response"); + return PAM_ABORT; + } + //TODO: what to do if size == 0? in fbc5a8136187, if read_comm_prompt_response (there called read_comm_request) returned 0, it would break the while loop (i.e. auth successfully); we can't do that here + pam_reply[i].resp = strdup(pw_buf); // PAM clears and frees this if (pam_reply[i].resp == NULL) { swaylock_log(LOG_ERROR, "Allocation failed"); return PAM_ABORT; } + password_buffer_destroy(pw_buf, size); + pw_buf = NULL; break; case PAM_ERROR_MSG: case PAM_TEXT_INFO: + if(write_comm_text_message_from_backend(&msg[i]->msg) < 0) { + swaylock_log(LOG_ERROR, "Failed to write message from backend"); + return PAM_ABORT; + } break; } } @@ -95,16 +116,7 @@ void run_pw_backend_child(void) { int pam_status = PAM_SUCCESS; while (1) { - ssize_t size = read_comm_prompt_response(&pw_buf); //TODO: should be moved to handle_conversation - if (size < 0) { - exit(EXIT_FAILURE); - } else if (size == 0) { - break; - } - int pam_status = pam_authenticate(auth_handle, 0); - password_buffer_destroy(pw_buf, size); - pw_buf = NULL; bool success = pam_status == PAM_SUCCESS; if (!success) { From 7b8ef41be136e4d937fc8bbd449438e040984248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Gunyh=C3=B3?= Date: Sat, 4 Jan 2025 23:10:23 +0200 Subject: [PATCH 08/11] Update comm function names in shadow backend --- shadow.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shadow.c b/shadow.c index 3399d040d..66780b596 100644 --- a/shadow.c +++ b/shadow.c @@ -64,7 +64,7 @@ void run_pw_backend_child(void) { assert(encpw != NULL); while (1) { char *buf; - ssize_t size = read_comm_request(&buf); + ssize_t size = read_comm_prompt_response(&buf); if (size < 0) { exit(EXIT_FAILURE); } else if (size == 0) { @@ -81,7 +81,7 @@ void run_pw_backend_child(void) { } bool success = strcmp(c, encpw) == 0; - if (!write_comm_reply(success)) { + if (write_comm_auth_result_from_backend(success) < 0) { exit(EXIT_FAILURE); } From eead45d2ded5739a29f5b4004303caa9b227fbae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Gunyh=C3=B3?= Date: Sun, 5 Jan 2025 23:06:27 +0200 Subject: [PATCH 09/11] Show backend messages similarly to the keyboard layout --- cairo.c | 4 +++ include/cairo.h | 1 + include/swaylock.h | 11 +++++++ main.c | 31 ++++++++++++++++-- render.c | 82 ++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 123 insertions(+), 6 deletions(-) diff --git a/cairo.c b/cairo.c index 75314bdb8..b2dd5b1d3 100644 --- a/cairo.c +++ b/cairo.c @@ -5,6 +5,10 @@ #include #endif +uint32_t color_with_alpha(uint32_t color, uint8_t alpha) { + return (color & 0xFFFFFF00) | alpha; +} + void cairo_set_source_u32(cairo_t *cairo, uint32_t color) { cairo_set_source_rgba(cairo, (color >> (3*8) & 0xFF) / 255.0, diff --git a/include/cairo.h b/include/cairo.h index f8cd544f0..c87ee307d 100644 --- a/include/cairo.h +++ b/include/cairo.h @@ -9,6 +9,7 @@ #include #endif +uint32_t color_with_alpha(uint32_t color, uint8_t alpha); void cairo_set_source_u32(cairo_t *cairo, uint32_t color); cairo_subpixel_order_t to_cairo_subpixel_order(enum wl_output_subpixel subpixel); diff --git a/include/swaylock.h b/include/swaylock.h index cd4e73ed1..5698e9417 100644 --- a/include/swaylock.h +++ b/include/swaylock.h @@ -77,6 +77,12 @@ struct swaylock_password { char *buffer; }; +struct swaylock_backend_message_list { + size_t num_messages; + size_t max_num_messages; + char **messages; +}; + struct swaylock_state { struct loop *eventloop; struct loop_timer *input_idle_timer; // timer to reset input state to IDLE @@ -90,6 +96,7 @@ struct swaylock_state { struct wl_list images; struct swaylock_args args; struct swaylock_password password; + struct swaylock_backend_message_list backend_message_list; // backend messages displayed in the UI struct swaylock_xkb xkb; cairo_surface_t *test_surface; cairo_t *test_cairo; // used to estimate font/text sizes @@ -144,4 +151,8 @@ void initialize_pw_backend(int argc, char **argv); void run_pw_backend_child(void); void clear_buffer(char *buf, size_t size); +// Add a message to the list of displayed backend messages. Creates a copy of +// the string, it can be freed after calling this function. +void add_backend_message(struct swaylock_state *state, char *msg); + #endif diff --git a/main.c b/main.c index 053366936..333c7668b 100644 --- a/main.c +++ b/main.c @@ -1034,9 +1034,24 @@ static void display_in(int fd, short mask, void *data) { } } +void add_backend_message(struct swaylock_state *state, char *msg) { + size_t n_max = state->backend_message_list.max_num_messages; + char **messages = state->backend_message_list.messages; + if (state->backend_message_list.num_messages == n_max) { + free(messages[n_max - 1]); + } else { + state->backend_message_list.num_messages += 1; + } + for (int i = n_max - 1; i > 0; i--) { + messages[i] = messages[i - 1]; + } + messages[0] = strdup(msg); +} + static void handle_backend_text_message(char *msg) { - //TODO: show message in UI - swaylock_log(LOG_DEBUG, "Received text message from backend: %s", msg); + //TODO: schedule old messages to be hidden after a timeout? + add_backend_message(&state, msg); + damage_state(&state); } static void handle_auth_result(bool success) { @@ -1067,7 +1082,7 @@ static void comm_in(int fd, short mask, void *data) { switch(msg_type){ case BACKEND_MESSAGE_TYPE_TEXT: handle_backend_text_message((char *) comm_data); - //TODO: memory leak; where to free comm_data? + free(comm_data); break; case BACKEND_MESSAGE_TYPE_AUTH_RESULT: handle_auth_result(*(bool *) comm_data); @@ -1178,6 +1193,16 @@ int main(int argc, char **argv) { return EXIT_FAILURE; } + state.backend_message_list = (struct swaylock_backend_message_list){ + .num_messages = 0, + //TODO: make configurable? + .max_num_messages = 4, + }; + state.backend_message_list.messages = malloc(state.backend_message_list.max_num_messages * sizeof(char *)); + for (size_t i = 0; i < state.backend_message_list.max_num_messages; i++) { + state.backend_message_list.messages[i] = NULL; + } + if (pipe(sigusr_fds) != 0) { swaylock_log(LOG_ERROR, "Failed to pipe"); return EXIT_FAILURE; diff --git a/render.c b/render.c index 1aa8ae52c..cd46e1249 100644 --- a/render.c +++ b/render.c @@ -189,6 +189,7 @@ static bool render_frame(struct swaylock_surface *surface) { int buffer_diameter = (arc_radius + arc_thickness) * 2; int buffer_width = buffer_diameter; int buffer_height = buffer_diameter; + double box_padding = 4.0 * surface->scale; if (text || layout_text) { cairo_set_antialias(state->test_cairo, CAIRO_ANTIALIAS_BEST); @@ -204,7 +205,6 @@ static bool render_frame(struct swaylock_surface *surface) { if (layout_text) { cairo_text_extents_t extents; cairo_font_extents_t fe; - double box_padding = 4.0 * surface->scale; cairo_text_extents(state->test_cairo, layout_text, &extents); cairo_font_extents(state->test_cairo, &fe); buffer_height += fe.height + 2 * box_padding; @@ -213,6 +213,25 @@ static bool render_frame(struct swaylock_surface *surface) { } } } + if (state->backend_message_list.num_messages > 0) { + cairo_set_antialias(state->test_cairo, CAIRO_ANTIALIAS_BEST); + configure_font_drawing(state->test_cairo, state, surface->subpixel, arc_radius); + + buffer_height += 2 * box_padding; + for (size_t i = 0; i < state->backend_message_list.num_messages; i++) { + cairo_text_extents_t extents; + cairo_font_extents_t fe; + cairo_text_extents(state->test_cairo, state->backend_message_list.messages[i], &extents); + cairo_font_extents(state->test_cairo, &fe); + // NOTE: if the indicator is not shown, this results in + // unnecessarily large buffer_height, that could be checked here + buffer_height += fe.height + 2 * box_padding; + if (buffer_width < extents.width + 2 * box_padding) { + buffer_width = extents.width + 2 * box_padding; + } + } + } + // Ensure buffer size is multiple of buffer scale - required by protocol buffer_height += surface->scale - (buffer_height % surface->scale); buffer_width += surface->scale - (buffer_width % surface->scale); @@ -260,6 +279,8 @@ static bool render_frame(struct swaylock_surface *surface) { float type_indicator_border_thickness = TYPE_INDICATOR_BORDER_THICKNESS * surface->scale; + float layout_text_box_height = 0; + if (draw_indicator) { // Fill inner circle cairo_set_line_width(cairo, 0); @@ -348,7 +369,6 @@ static bool render_frame(struct swaylock_surface *surface) { cairo_text_extents_t extents; cairo_font_extents_t fe; double x, y; - double box_padding = 4.0 * surface->scale; cairo_text_extents(cairo, layout_text, &extents); cairo_font_extents(cairo, &fe); // upper left coordinates for box @@ -356,9 +376,10 @@ static bool render_frame(struct swaylock_surface *surface) { y = buffer_diameter; // background box + layout_text_box_height = fe.height + 2.0 * box_padding; cairo_rectangle(cairo, x, y, extents.width + 2.0 * box_padding, - fe.height + 2.0 * box_padding); + layout_text_box_height); cairo_set_source_u32(cairo, state->args.colors.layout_background); cairo_fill_preserve(cairo); // border @@ -375,6 +396,61 @@ static bool render_frame(struct swaylock_surface *surface) { } } + // display messages from backend + if (state->backend_message_list.num_messages > 0) { + configure_font_drawing(cairo, state, surface->subpixel, arc_radius); + for (size_t i = 0; i < state->backend_message_list.num_messages; i++) { + char *str = state->backend_message_list.messages[i]; + + cairo_text_extents_t extents; + cairo_font_extents_t fe; + double x, y; + + cairo_text_extents(cairo, str, &extents); + cairo_font_extents(cairo, &fe); + + // upper left coordinates for box + x = (buffer_width / 2) - (extents.width / 2) - box_padding; + if (draw_indicator) { + y = buffer_diameter + 2 * box_padding; + if (layout_text) { + y += layout_text_box_height; + } + } else { + y = (buffer_diameter / 2) - (fe.height / 2) - box_padding; + } + y += (fe.height + 2 * box_padding) * i; + + // background box + cairo_rectangle(cairo, x, y, + extents.width + 2.0 * box_padding, + fe.height + 2.0 * box_padding); + //TODO: make colors customizable + cairo_set_source_u32(cairo, + color_with_alpha( + state->args.colors.layout_background, + (uint8_t) (state->args.colors.layout_background & 0xFF) / (i == 0 ? 1 : 2))); + cairo_fill_preserve(cairo); + + // border + cairo_set_source_u32(cairo, + color_with_alpha( + state->args.colors.layout_border, + (uint8_t) (state->args.colors.layout_border & 0xFF) / (i == 0 ? 1 : 2))); + cairo_stroke(cairo); + + cairo_move_to(cairo, + x - extents.x_bearing + box_padding, + y + (fe.height - fe.descent) + box_padding); + cairo_set_source_u32(cairo, + color_with_alpha( + state->args.colors.layout_text, + (uint8_t) (state->args.colors.layout_text & 0xFF) / (i == 0 ? 1 : 2))); + cairo_show_text(cairo, str); + cairo_new_sub_path(cairo); + } + } + // Send Wayland requests wl_subsurface_set_position(surface->subsurface, subsurf_xpos, subsurf_ypos); From bc18cc2efc45a0467092d25cbc7c3ff7b1375ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Gunyh=C3=B3?= Date: Sat, 11 Jan 2025 14:20:19 +0200 Subject: [PATCH 10/11] fixup: use single pointer for writing strings --- comm.c | 10 +++++----- include/comm.h | 4 ++-- pam.c | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/comm.c b/comm.c index 1020ac005..bb1fdaac2 100644 --- a/comm.c +++ b/comm.c @@ -9,7 +9,7 @@ static int comm[2][2] = {{-1, -1}, {-1, -1}}; -ssize_t write_string(int fd, const char * const *string, size_t len) { +ssize_t write_string(int fd, const char *string, size_t len) { size_t offs = 0; if (write(fd, &len, sizeof(len)) < 0) { swaylock_log_errno(LOG_ERROR, "Failed to write string size"); @@ -17,7 +17,7 @@ ssize_t write_string(int fd, const char * const *string, size_t len) { } do { - ssize_t amt = write(fd, string[offs], len - offs); + ssize_t amt = write(fd, &string[offs], len - offs); if (amt < 0) { swaylock_log_errno(LOG_ERROR, "Failed to write string"); //TODO: different return value for different error? @@ -69,13 +69,13 @@ ssize_t read_comm_prompt_response(char **buf_ptr) { return amt; } -ssize_t write_comm_text_message_from_backend(const char * const *msg) { +ssize_t write_comm_text_message_from_backend(const char *msg) { enum backend_message_type msg_type = BACKEND_MESSAGE_TYPE_TEXT; if (write(comm[1][1], &msg_type, sizeof(msg_type)) != sizeof(msg_type)) { swaylock_log_errno(LOG_ERROR, "failed to write message type"); return -1; } - return write_string(comm[1][1], msg, strlen(*msg) + 1); + return write_string(comm[1][1], msg, strlen(msg) + 1); } ssize_t write_comm_auth_result_from_backend(bool success) { @@ -117,7 +117,7 @@ bool spawn_comm_child(void) { bool write_comm_prompt_response(struct swaylock_password *pw) { bool result = false; - ssize_t amt = write_string(comm[0][1], (const char * const *)&pw->buffer, pw->len + 1); + ssize_t amt = write_string(comm[0][1], pw->buffer, pw->len + 1); if (amt < 0) { swaylock_log_errno(LOG_ERROR, "Failed to write prompt response"); diff --git a/include/comm.h b/include/comm.h index d128adb1b..0ff30d121 100644 --- a/include/comm.h +++ b/include/comm.h @@ -18,7 +18,7 @@ bool spawn_comm_child(void); // termination. Returns the number of bytes in the written string, *not* the // total no. of bytes written - the total number is the return value plus // sizeof(size_t). -ssize_t write_string(int fd, const char * const *string, size_t len); +ssize_t write_string(int fd, const char *string, size_t len); // Read a string from a file descriptor by first reading the size, allocating // memory to output using alloc(size) and then reading the string data to it. @@ -36,7 +36,7 @@ ssize_t read_comm_message_from_backend(enum backend_message_type *msg_type, void // Write a string containing a message from the backend // Returns the no. of bytes in the *message* that was written, the total no. of // bytes written is the return value plus sizeof(enum backend_message_type). -ssize_t write_comm_text_message_from_backend(const char * const *msg); +ssize_t write_comm_text_message_from_backend(const char *msg); // Write a boolean value indicating the result of authentication // Returns the no. of bytes in the *data* that was written (i.e. sizeof(bool)), // the total no. of bytes written is the return value plus diff --git a/pam.c b/pam.c index 449a8413a..e8883a91b 100644 --- a/pam.c +++ b/pam.c @@ -36,7 +36,7 @@ static int handle_conversation(int num_msg, const struct pam_message **msg, swaylock_log(LOG_DEBUG, "PAM message %d: %s", i, msg[i]->msg); switch (msg[i]->msg_style) { case PAM_PROMPT_ECHO_ON: - if(write_comm_text_message_from_backend(&msg[i]->msg) < 0) { + if(write_comm_text_message_from_backend(msg[i]->msg) < 0) { swaylock_log(LOG_ERROR, "Failed to write message from backend"); return PAM_ABORT; } @@ -64,7 +64,7 @@ static int handle_conversation(int num_msg, const struct pam_message **msg, break; case PAM_ERROR_MSG: case PAM_TEXT_INFO: - if(write_comm_text_message_from_backend(&msg[i]->msg) < 0) { + if(write_comm_text_message_from_backend(msg[i]->msg) < 0) { swaylock_log(LOG_ERROR, "Failed to write message from backend"); return PAM_ABORT; } From 60943f87909dc9fc813aa053841e86e216005d6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Gunyh=C3=B3?= Date: Sat, 11 Jan 2025 14:33:21 +0200 Subject: [PATCH 11/11] fixup: return PAM_ABORT if read 0 bytes --- pam.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pam.c b/pam.c index e8883a91b..b0eca5a46 100644 --- a/pam.c +++ b/pam.c @@ -48,11 +48,10 @@ static int handle_conversation(int num_msg, const struct pam_message **msg, case PAM_PROMPT_ECHO_OFF: char *pw_buf = NULL; ssize_t size = read_comm_prompt_response(&pw_buf); - if (size < 0) { + if (size <= 0) { swaylock_log(LOG_ERROR, "Failed to read prompt response"); return PAM_ABORT; } - //TODO: what to do if size == 0? in fbc5a8136187, if read_comm_prompt_response (there called read_comm_request) returned 0, it would break the while loop (i.e. auth successfully); we can't do that here pam_reply[i].resp = strdup(pw_buf); // PAM clears and frees this if (pam_reply[i].resp == NULL) {