aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/swaylock/swaylock.h3
-rw-r--r--meson.build10
-rw-r--r--swaylock/main.c5
-rw-r--r--swaylock/meson.build50
-rw-r--r--swaylock/pam.c62
-rw-r--r--swaylock/password.c51
-rw-r--r--swaylock/shadow.c128
7 files changed, 233 insertions, 76 deletions
diff --git a/include/swaylock/swaylock.h b/include/swaylock/swaylock.h
index 2f0cd34d..970e3cc9 100644
--- a/include/swaylock/swaylock.h
+++ b/include/swaylock/swaylock.h
@@ -101,5 +101,8 @@ void render_frame(struct swaylock_surface *surface);
void render_frames(struct swaylock_state *state);
void damage_surface(struct swaylock_surface *surface);
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
diff --git a/meson.build b/meson.build
index 76eaff20..de6573ea 100644
--- a/meson.build
+++ b/meson.build
@@ -74,6 +74,11 @@ if elogind.found()
swayidle_deps += elogind
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)
if scdoc.found()
@@ -139,10 +144,7 @@ subdir('swaybg')
subdir('swaybar')
subdir('swayidle')
subdir('swaynag')
-
-if libpam.found()
- subdir('swaylock')
-endif
+subdir('swaylock')
config = configuration_data()
config.set('sysconfdir', join_paths(prefix, sysconfdir))
diff --git a/swaylock/main.c b/swaylock/main.c
index c25c8eec..693cbc10 100644
--- a/swaylock/main.c
+++ b/swaylock/main.c
@@ -845,6 +845,9 @@ static int load_config(char *path, struct swaylock_state *state,
static struct swaylock_state state;
int main(int argc, char **argv) {
+ wlr_log_init(WLR_DEBUG, NULL);
+ initialize_pw_backend();
+
enum line_mode line_mode = LM_LINE;
state.args = (struct swaylock_args){
.mode = BACKGROUND_MODE_SOLID_COLOR,
@@ -857,8 +860,6 @@ int main(int argc, char **argv) {
wl_list_init(&state.images);
set_default_colors(&state.args.colors);
- wlr_log_init(WLR_DEBUG, NULL);
-
char *config_path = NULL;
int result = parse_options(argc, argv, NULL, NULL, &config_path);
if (result != 0) {
diff --git a/swaylock/meson.build b/swaylock/meson.build
index 675b8c69..6c87d173 100644
--- a/swaylock/meson.build
+++ b/swaylock/meson.build
@@ -1,25 +1,37 @@
sysconfdir = get_option('sysconfdir')
-executable(
- 'swaylock', [
- 'main.c',
- 'password.c',
- 'render.c',
- 'seat.c'
- ],
+dependencies = [
+ cairo,
+ client_protos,
+ gdk_pixbuf,
+ math,
+ 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],
- dependencies: [
- cairo,
- client_protos,
- gdk_pixbuf,
- libpam,
- math,
- pango,
- pangocairo,
- xkbcommon,
- wayland_client,
- wlroots,
- ],
+ dependencies: dependencies,
link_with: [lib_sway_common, lib_sway_client],
install: true
)
diff --git a/swaylock/pam.c b/swaylock/pam.c
new file mode 100644
index 00000000..cac95a85
--- /dev/null
+++ b/swaylock/pam.c
@@ -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;
+}
diff --git a/swaylock/password.c b/swaylock/password.c
index 7c686b34..6a956bcb 100644
--- a/swaylock/password.c
+++ b/swaylock/password.c
@@ -1,7 +1,6 @@
#define _XOPEN_SOURCE 500
#include <assert.h>
#include <pwd.h>
-#include <security/pam_appl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
@@ -11,27 +10,6 @@
#include "swaylock/seat.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) {
// Use volatile keyword so so compiler can't optimize this out.
volatile char *buffer = pw->buffer;
@@ -42,35 +20,6 @@ void clear_password_buffer(struct swaylock_password *pw) {
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) {
if (pw->len != 0) {
pw->buffer[--pw->len] = 0;
diff --git a/swaylock/shadow.c b/swaylock/shadow.c
new file mode 100644
index 00000000..1f10514c
--- /dev/null
+++ b/swaylock/shadow.c
@@ -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;
+}