1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
|
#define _POSIX_C_SOURCE 200809L
#include <pwd.h>
#include <security/pam_appl.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "comm.h"
#include "log.h"
#include "password-buffer.h"
#include "swaylock.h"
void initialize_pw_backend(int argc, char **argv) {
if (getuid() != geteuid() || getgid() != getegid()) {
swaylock_log(LOG_ERROR,
"swaylock is setuid, but was compiled with the PAM"
" backend. Run 'chmod a-s %s' to fix. Aborting.", argv[0]);
exit(EXIT_FAILURE);
}
if (!spawn_comm_child()) {
exit(EXIT_FAILURE);
}
}
struct conv_state {
char *password;
};
static int handle_conversation(int num_msg, const struct pam_message **msg,
struct pam_response **resp, void *data) {
struct conv_state *state = data;
/* PAM expects an array of responses, one for each message */
struct pam_response *pam_reply =
calloc(num_msg, sizeof(struct pam_response));
if (pam_reply == NULL) {
swaylock_log(LOG_ERROR, "Allocation failed");
return PAM_ABORT;
}
*resp = pam_reply;
for (int i = 0; i < num_msg; ++i) {
switch (msg[i]->msg_style) {
case PAM_PROMPT_ECHO_OFF:
case PAM_PROMPT_ECHO_ON:
/* workaround pam_systemd_home internal retries:
* https://github.com/systemd/systemd/blob/main/src/home/pam_systemd_home.c#L594-L599
* if the password has already been rejected once, abort the conversation */
if (state->password == NULL) {
return PAM_ABORT;
}
pam_reply[i].resp = strdup(state->password); // PAM clears and frees this
if (pam_reply[i].resp == NULL) {
swaylock_log(LOG_ERROR, "Allocation failed");
return PAM_ABORT;
}
state->password = NULL;
break;
case PAM_ERROR_MSG:
case PAM_TEXT_INFO:
break;
}
}
return PAM_SUCCESS;
}
static const char *get_pam_auth_error(int pam_status) {
switch (pam_status) {
case PAM_AUTH_ERR:
return "invalid credentials";
case PAM_CRED_INSUFFICIENT:
return "swaylock cannot authenticate users; check /etc/pam.d/swaylock "
"has been installed properly";
case PAM_AUTHINFO_UNAVAIL:
return "authentication information unavailable";
case PAM_MAXTRIES:
return "maximum number of authentication tries exceeded";
default:;
static char msg[64];
snprintf(msg, sizeof(msg), "unknown error (%d)", pam_status);
return msg;
}
}
void run_pw_backend_child(void) {
char *pw_buf = NULL;
struct passwd *passwd = getpwuid(getuid());
if (!passwd) {
swaylock_log_errno(LOG_ERROR, "getpwuid failed");
exit(EXIT_FAILURE);
}
char *username = passwd->pw_name;
struct conv_state state = {0};
const struct pam_conv conv = {
.conv = handle_conversation,
.appdata_ptr = &state,
};
pam_handle_t *auth_handle = NULL;
if (pam_start("swaylock", username, &conv, &auth_handle) != PAM_SUCCESS) {
swaylock_log(LOG_ERROR, "pam_start failed");
exit(EXIT_FAILURE);
}
/* This code does not run as root */
swaylock_log(LOG_DEBUG, "Prepared to authorize user %s", username);
int pam_status = PAM_SUCCESS;
while (1) {
ssize_t size = read_comm_request(&pw_buf);
if (size < 0) {
exit(EXIT_FAILURE);
} else if (size == 0) {
break;
}
state.password = pw_buf;
int pam_status = pam_authenticate(auth_handle, 0);
password_buffer_destroy(pw_buf, size);
pw_buf = NULL;
state.password = NULL;
bool success = pam_status == PAM_SUCCESS;
if (!success) {
swaylock_log(LOG_ERROR, "pam_authenticate failed: %s",
get_pam_auth_error(pam_status));
}
if (!write_comm_reply(success)) {
exit(EXIT_FAILURE);
}
if (success) {
/* Unsuccessful requests may be queued after a successful one;
* do not process them. */
break;
}
}
pam_setcred(auth_handle, PAM_REFRESH_CRED);
if (pam_end(auth_handle, pam_status) != PAM_SUCCESS) {
swaylock_log(LOG_ERROR, "pam_end failed");
exit(EXIT_FAILURE);
}
exit((pam_status == PAM_SUCCESS) ? EXIT_SUCCESS : EXIT_FAILURE);
}
|