swaylock/shadow.c
Drew DeVault 64259db8d6 Add support for building swaylock without PAM
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.
2018-09-28 13:53:01 +02:00

129 lines
3.1 KiB
C

#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;
}