mirror of
https://github.com/swaywm/sway.git
synced 2024-12-28 07:56:31 +01:00
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.
This commit is contained in:
parent
58af001517
commit
c977349120
7 changed files with 233 additions and 76 deletions
|
@ -101,5 +101,8 @@ void render_frame(struct swaylock_surface *surface);
|
||||||
void render_frames(struct swaylock_state *state);
|
void render_frames(struct swaylock_state *state);
|
||||||
void damage_surface(struct swaylock_surface *surface);
|
void damage_surface(struct swaylock_surface *surface);
|
||||||
void damage_state(struct swaylock_state *state);
|
void damage_state(struct swaylock_state *state);
|
||||||
|
void initialize_pw_backend(void);
|
||||||
|
bool attempt_password(struct swaylock_password *pw);
|
||||||
|
void clear_password_buffer(struct swaylock_password *pw);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
10
meson.build
10
meson.build
|
@ -74,6 +74,11 @@ if elogind.found()
|
||||||
swayidle_deps += elogind
|
swayidle_deps += elogind
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if not systemd.found() and not elogind.found()
|
||||||
|
warning('The sway binary must be setuid when compiled without (e)logind')
|
||||||
|
warning('You must do this manually post-install: chmod a+s /path/to/sway')
|
||||||
|
endif
|
||||||
|
|
||||||
scdoc = find_program('scdoc', required: false)
|
scdoc = find_program('scdoc', required: false)
|
||||||
|
|
||||||
if scdoc.found()
|
if scdoc.found()
|
||||||
|
@ -139,10 +144,7 @@ subdir('swaybg')
|
||||||
subdir('swaybar')
|
subdir('swaybar')
|
||||||
subdir('swayidle')
|
subdir('swayidle')
|
||||||
subdir('swaynag')
|
subdir('swaynag')
|
||||||
|
subdir('swaylock')
|
||||||
if libpam.found()
|
|
||||||
subdir('swaylock')
|
|
||||||
endif
|
|
||||||
|
|
||||||
config = configuration_data()
|
config = configuration_data()
|
||||||
config.set('sysconfdir', join_paths(prefix, sysconfdir))
|
config.set('sysconfdir', join_paths(prefix, sysconfdir))
|
||||||
|
|
|
@ -845,6 +845,9 @@ static int load_config(char *path, struct swaylock_state *state,
|
||||||
static struct swaylock_state state;
|
static struct swaylock_state state;
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
|
wlr_log_init(WLR_DEBUG, NULL);
|
||||||
|
initialize_pw_backend();
|
||||||
|
|
||||||
enum line_mode line_mode = LM_LINE;
|
enum line_mode line_mode = LM_LINE;
|
||||||
state.args = (struct swaylock_args){
|
state.args = (struct swaylock_args){
|
||||||
.mode = BACKGROUND_MODE_SOLID_COLOR,
|
.mode = BACKGROUND_MODE_SOLID_COLOR,
|
||||||
|
@ -857,8 +860,6 @@ int main(int argc, char **argv) {
|
||||||
wl_list_init(&state.images);
|
wl_list_init(&state.images);
|
||||||
set_default_colors(&state.args.colors);
|
set_default_colors(&state.args.colors);
|
||||||
|
|
||||||
wlr_log_init(WLR_DEBUG, NULL);
|
|
||||||
|
|
||||||
char *config_path = NULL;
|
char *config_path = NULL;
|
||||||
int result = parse_options(argc, argv, NULL, NULL, &config_path);
|
int result = parse_options(argc, argv, NULL, NULL, &config_path);
|
||||||
if (result != 0) {
|
if (result != 0) {
|
||||||
|
|
|
@ -1,25 +1,37 @@
|
||||||
sysconfdir = get_option('sysconfdir')
|
sysconfdir = get_option('sysconfdir')
|
||||||
|
|
||||||
executable(
|
dependencies = [
|
||||||
'swaylock', [
|
cairo,
|
||||||
'main.c',
|
client_protos,
|
||||||
'password.c',
|
gdk_pixbuf,
|
||||||
'render.c',
|
math,
|
||||||
'seat.c'
|
pango,
|
||||||
],
|
pangocairo,
|
||||||
|
xkbcommon,
|
||||||
|
wayland_client,
|
||||||
|
wlroots,
|
||||||
|
]
|
||||||
|
|
||||||
|
sources = [
|
||||||
|
'main.c',
|
||||||
|
'password.c',
|
||||||
|
'render.c',
|
||||||
|
'seat.c'
|
||||||
|
]
|
||||||
|
|
||||||
|
if libpam.found()
|
||||||
|
sources += ['pam.c']
|
||||||
|
dependencies += [libpam]
|
||||||
|
else
|
||||||
|
warning('The swaylock binary must be setuid when compiled without libpam')
|
||||||
|
warning('You must do this manually post-install: chmod a+s /path/to/swaylock')
|
||||||
|
sources += ['shadow.c']
|
||||||
|
endif
|
||||||
|
|
||||||
|
executable('swaylock',
|
||||||
|
sources,
|
||||||
include_directories: [sway_inc],
|
include_directories: [sway_inc],
|
||||||
dependencies: [
|
dependencies: dependencies,
|
||||||
cairo,
|
|
||||||
client_protos,
|
|
||||||
gdk_pixbuf,
|
|
||||||
libpam,
|
|
||||||
math,
|
|
||||||
pango,
|
|
||||||
pangocairo,
|
|
||||||
xkbcommon,
|
|
||||||
wayland_client,
|
|
||||||
wlroots,
|
|
||||||
],
|
|
||||||
link_with: [lib_sway_common, lib_sway_client],
|
link_with: [lib_sway_common, lib_sway_client],
|
||||||
install: true
|
install: true
|
||||||
)
|
)
|
||||||
|
|
62
swaylock/pam.c
Normal file
62
swaylock/pam.c
Normal file
|
@ -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;
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
#define _XOPEN_SOURCE 500
|
#define _XOPEN_SOURCE 500
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#include <security/pam_appl.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
@ -11,27 +10,6 @@
|
||||||
#include "swaylock/seat.h"
|
#include "swaylock/seat.h"
|
||||||
#include "unicode.h"
|
#include "unicode.h"
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear_password_buffer(struct swaylock_password *pw) {
|
void clear_password_buffer(struct swaylock_password *pw) {
|
||||||
// Use volatile keyword so so compiler can't optimize this out.
|
// Use volatile keyword so so compiler can't optimize this out.
|
||||||
volatile char *buffer = pw->buffer;
|
volatile char *buffer = pw->buffer;
|
||||||
|
@ -42,35 +20,6 @@ void clear_password_buffer(struct swaylock_password *pw) {
|
||||||
pw->len = 0;
|
pw->len = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static 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;
|
|
||||||
// TODO: only call pam_start once. keep the same handle the whole time
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool backspace(struct swaylock_password *pw) {
|
static bool backspace(struct swaylock_password *pw) {
|
||||||
if (pw->len != 0) {
|
if (pw->len != 0) {
|
||||||
pw->buffer[--pw->len] = 0;
|
pw->buffer[--pw->len] = 0;
|
||||||
|
|
128
swaylock/shadow.c
Normal file
128
swaylock/shadow.c
Normal file
|
@ -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 a new issue