This involves setuid'ing swaylock, which then forks and drops perms on the parent process. The child process remains root and listens on a pipe for requests to validate passwords against /etc/shadow.master
parent
58af001517
commit
c977349120
@ -0,0 +1,62 @@
|
||||
#define _XOPEN_SOURCE 500
|
||||
#include <pwd.h>
|
||||
#include <security/pam_appl.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <wlr/util/log.h>
|
||||
#include "swaylock/swaylock.h"
|
||||
|
||||
void initialize_pw_backend(void) {
|
||||
// TODO: only call pam_start once. keep the same handle the whole time
|
||||
}
|
||||
|
||||
static int function_conversation(int num_msg, const struct pam_message **msg,
|
||||
struct pam_response **resp, void *data) {
|
||||
struct swaylock_password *pw = data;
|
||||
/* PAM expects an array of responses, one for each message */
|
||||
struct pam_response *pam_reply = calloc(
|
||||
num_msg, sizeof(struct pam_response));
|
||||
*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:
|
||||
pam_reply[i].resp = strdup(pw->buffer); // PAM clears and frees this
|
||||
break;
|
||||
case PAM_ERROR_MSG:
|
||||
case PAM_TEXT_INFO:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
bool attempt_password(struct swaylock_password *pw) {
|
||||
struct passwd *passwd = getpwuid(getuid());
|
||||
char *username = passwd->pw_name;
|
||||
const struct pam_conv local_conversation = {
|
||||
function_conversation, pw
|
||||
};
|
||||
pam_handle_t *local_auth_handle = NULL;
|
||||
int pam_err;
|
||||
if ((pam_err = pam_start("swaylock", username,
|
||||
&local_conversation, &local_auth_handle)) != PAM_SUCCESS) {
|
||||
wlr_log(WLR_ERROR, "PAM returned error %d", pam_err);
|
||||
}
|
||||
if ((pam_err = pam_authenticate(local_auth_handle, 0)) != PAM_SUCCESS) {
|
||||
wlr_log(WLR_ERROR, "pam_authenticate failed");
|
||||
goto fail;
|
||||
}
|
||||
// TODO: only call pam_end once we succeed at authing. refresh tokens beforehand
|
||||
if ((pam_err = pam_end(local_auth_handle, pam_err)) != PAM_SUCCESS) {
|
||||
wlr_log(WLR_ERROR, "pam_end failed");
|
||||
goto fail;
|
||||
}
|
||||
clear_password_buffer(pw);
|
||||
return true;
|
||||
fail:
|
||||
clear_password_buffer(pw);
|
||||
return false;
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
#define _XOPEN_SOURCE
|
||||
#include <pwd.h>
|
||||
#include <shadow.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <wlr/util/log.h>
|
||||
#include "swaylock/swaylock.h"
|
||||
|
||||
static int comm[2][2];
|
||||
|
||||
void run_child(void) {
|
||||
/* This code runs as root */
|
||||
struct passwd *pwent = getpwuid(getuid());
|
||||
if (!pwent) {
|
||||
wlr_log_errno(WLR_ERROR, "failed to getpwuid");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
char *encpw = pwent->pw_passwd;
|
||||
if (strcmp(encpw, "x") == 0) {
|
||||
struct spwd *swent = getspnam(pwent->pw_name);
|
||||
if (!swent) {
|
||||
wlr_log_errno(WLR_ERROR, "failed to getspnam");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
encpw = swent->sp_pwdp;
|
||||
}
|
||||
wlr_log(WLR_DEBUG, "prepared to authorize user %s", pwent->pw_name);
|
||||
|
||||
size_t size;
|
||||
char *buf;
|
||||
while (1) {
|
||||
ssize_t amt;
|
||||
amt = read(comm[0][0], &size, sizeof(size));
|
||||
if (amt == 0) {
|
||||
break;
|
||||
} else if (amt < 0) {
|
||||
wlr_log_errno(WLR_ERROR, "read pw request");
|
||||
}
|
||||
wlr_log(WLR_DEBUG, "received pw check request");
|
||||
buf = malloc(size);
|
||||
if (!buf) {
|
||||
wlr_log_errno(WLR_ERROR, "failed to malloc pw buffer");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
size_t offs = 0;
|
||||
do {
|
||||
amt = read(comm[0][0], &buf[offs], size - offs);
|
||||
if (amt <= 0) {
|
||||
wlr_log_errno(WLR_ERROR, "failed to read pw");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
offs += (size_t)amt;
|
||||
} while (offs < size);
|
||||
bool result = false;
|
||||
char *c = crypt(buf, encpw);
|
||||
if (c == NULL) {
|
||||
wlr_log_errno(WLR_ERROR, "crypt");
|
||||
}
|
||||
result = strcmp(c, encpw) == 0;
|
||||
if (write(comm[1][1], &result, sizeof(result)) != sizeof(result)) {
|
||||
wlr_log_errno(WLR_ERROR, "failed to write pw check result");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
void initialize_pw_backend(void) {
|
||||
if (geteuid() != 0) {
|
||||
wlr_log(WLR_ERROR, "swaylock needs to be setuid to read /etc/shadow");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (pipe(comm[0]) != 0) {
|
||||
wlr_log_errno(WLR_ERROR, "failed to create pipe");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (pipe(comm[1]) != 0) {
|
||||
wlr_log_errno(WLR_ERROR, "failed to create pipe");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(comm[0][1]);
|
||||
close(comm[1][0]);
|
||||
run_child();
|
||||
} else if (child < 0) {
|
||||
wlr_log_errno(WLR_ERROR, "failed to fork");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
close(comm[0][0]);
|
||||
close(comm[1][1]);
|
||||
if (setgid(getgid()) != 0) {
|
||||
wlr_log_errno(WLR_ERROR, "Unable to drop root");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (setuid(getuid()) != 0) {
|
||||
wlr_log_errno(WLR_ERROR, "Unable to drop root");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
bool attempt_password(struct swaylock_password *pw) {
|
||||
bool result = false;
|
||||
size_t len = pw->len + 1;
|
||||
size_t offs = 0;
|
||||
if (write(comm[0][1], &len, sizeof(len)) < 0) {
|
||||
wlr_log_errno(WLR_ERROR, "Failed to request pw check");
|
||||
goto ret;
|
||||
}
|
||||
do {
|
||||
ssize_t amt = write(comm[0][1], &pw->buffer[offs], len - offs);
|
||||
if (amt < 0) {
|
||||
wlr_log_errno(WLR_ERROR, "Failed to write pw buffer");
|
||||
goto ret;
|
||||
}
|
||||
offs += amt;
|
||||
} while (offs < len);
|
||||
if (read(comm[1][0], &result, sizeof(result)) != sizeof(result)) {
|
||||
wlr_log_errno(WLR_ERROR, "Failed to read pw result");
|
||||
goto ret;
|
||||
}
|
||||
wlr_log(WLR_DEBUG, "pw result: %d", result);
|
||||
ret:
|
||||
clear_password_buffer(pw);
|
||||
return result;
|
||||
}
|
Loading…
Reference in new issue