summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Drew DeVault <sir@cmpwn.com>2018-09-28 12:18:54 +0200
committerLibravatar Drew DeVault <sir@cmpwn.com>2018-09-28 13:53:01 +0200
commitc9773491207d36d6f5e651adcb7a64c7a015bba3 (patch)
treeed2d195ac03609bdb1b3132d1ef748ad59132e8a
parentMerge pull request #2717 from ianyfan/tablet-config (diff)
downloadsway-c9773491207d36d6f5e651adcb7a64c7a015bba3.tar.gz
sway-c9773491207d36d6f5e651adcb7a64c7a015bba3.tar.zst
sway-c9773491207d36d6f5e651adcb7a64c7a015bba3.zip
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.
-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);
101void render_frames(struct swaylock_state *state); 101void render_frames(struct swaylock_state *state);
102void damage_surface(struct swaylock_surface *surface); 102void damage_surface(struct swaylock_surface *surface);
103void damage_state(struct swaylock_state *state); 103void damage_state(struct swaylock_state *state);
104void initialize_pw_backend(void);
105bool attempt_password(struct swaylock_password *pw);
106void clear_password_buffer(struct swaylock_password *pw);
104 107
105#endif 108#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()
74 swayidle_deps += elogind 74 swayidle_deps += elogind
75endif 75endif
76 76
77if not systemd.found() and not elogind.found()
78 warning('The sway binary must be setuid when compiled without (e)logind')
79 warning('You must do this manually post-install: chmod a+s /path/to/sway')
80endif
81
77scdoc = find_program('scdoc', required: false) 82scdoc = find_program('scdoc', required: false)
78 83
79if scdoc.found() 84if scdoc.found()
@@ -139,10 +144,7 @@ subdir('swaybg')
139subdir('swaybar') 144subdir('swaybar')
140subdir('swayidle') 145subdir('swayidle')
141subdir('swaynag') 146subdir('swaynag')
142 147subdir('swaylock')
143if libpam.found()
144 subdir('swaylock')
145endif
146 148
147config = configuration_data() 149config = configuration_data()
148config.set('sysconfdir', join_paths(prefix, sysconfdir)) 150config.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,
845static struct swaylock_state state; 845static struct swaylock_state state;
846 846
847int main(int argc, char **argv) { 847int main(int argc, char **argv) {
848 wlr_log_init(WLR_DEBUG, NULL);
849 initialize_pw_backend();
850
848 enum line_mode line_mode = LM_LINE; 851 enum line_mode line_mode = LM_LINE;
849 state.args = (struct swaylock_args){ 852 state.args = (struct swaylock_args){
850 .mode = BACKGROUND_MODE_SOLID_COLOR, 853 .mode = BACKGROUND_MODE_SOLID_COLOR,
@@ -857,8 +860,6 @@ int main(int argc, char **argv) {
857 wl_list_init(&state.images); 860 wl_list_init(&state.images);
858 set_default_colors(&state.args.colors); 861 set_default_colors(&state.args.colors);
859 862
860 wlr_log_init(WLR_DEBUG, NULL);
861
862 char *config_path = NULL; 863 char *config_path = NULL;
863 int result = parse_options(argc, argv, NULL, NULL, &config_path); 864 int result = parse_options(argc, argv, NULL, NULL, &config_path);
864 if (result != 0) { 865 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 @@
1sysconfdir = get_option('sysconfdir') 1sysconfdir = get_option('sysconfdir')
2 2
3executable( 3dependencies = [
4 'swaylock', [ 4 cairo,
5 'main.c', 5 client_protos,
6 'password.c', 6 gdk_pixbuf,
7 'render.c', 7 math,
8 'seat.c' 8 pango,
9 ], 9 pangocairo,
10 xkbcommon,
11 wayland_client,
12 wlroots,
13]
14
15sources = [
16 'main.c',
17 'password.c',
18 'render.c',
19 'seat.c'
20]
21
22if libpam.found()
23 sources += ['pam.c']
24 dependencies += [libpam]
25else
26 warning('The swaylock binary must be setuid when compiled without libpam')
27 warning('You must do this manually post-install: chmod a+s /path/to/swaylock')
28 sources += ['shadow.c']
29endif
30
31executable('swaylock',
32 sources,
10 include_directories: [sway_inc], 33 include_directories: [sway_inc],
11 dependencies: [ 34 dependencies: dependencies,
12 cairo,
13 client_protos,
14 gdk_pixbuf,
15 libpam,
16 math,
17 pango,
18 pangocairo,
19 xkbcommon,
20 wayland_client,
21 wlroots,
22 ],
23 link_with: [lib_sway_common, lib_sway_client], 35 link_with: [lib_sway_common, lib_sway_client],
24 install: true 36 install: true
25) 37)
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 @@
1#define _XOPEN_SOURCE 500
2#include <pwd.h>
3#include <security/pam_appl.h>
4#include <stdbool.h>
5#include <stdlib.h>
6#include <string.h>
7#include <unistd.h>
8#include <wlr/util/log.h>
9#include "swaylock/swaylock.h"
10
11void initialize_pw_backend(void) {
12 // TODO: only call pam_start once. keep the same handle the whole time
13}
14
15static int function_conversation(int num_msg, const struct pam_message **msg,
16 struct pam_response **resp, void *data) {
17 struct swaylock_password *pw = data;
18 /* PAM expects an array of responses, one for each message */
19 struct pam_response *pam_reply = calloc(
20 num_msg, sizeof(struct pam_response));
21 *resp = pam_reply;
22 for (int i = 0; i < num_msg; ++i) {
23 switch (msg[i]->msg_style) {
24 case PAM_PROMPT_ECHO_OFF:
25 case PAM_PROMPT_ECHO_ON:
26 pam_reply[i].resp = strdup(pw->buffer); // PAM clears and frees this
27 break;
28 case PAM_ERROR_MSG:
29 case PAM_TEXT_INFO:
30 break;
31 }
32 }
33 return PAM_SUCCESS;
34}
35
36bool attempt_password(struct swaylock_password *pw) {
37 struct passwd *passwd = getpwuid(getuid());
38 char *username = passwd->pw_name;
39 const struct pam_conv local_conversation = {
40 function_conversation, pw
41 };
42 pam_handle_t *local_auth_handle = NULL;
43 int pam_err;
44 if ((pam_err = pam_start("swaylock", username,
45 &local_conversation, &local_auth_handle)) != PAM_SUCCESS) {
46 wlr_log(WLR_ERROR, "PAM returned error %d", pam_err);
47 }
48 if ((pam_err = pam_authenticate(local_auth_handle, 0)) != PAM_SUCCESS) {
49 wlr_log(WLR_ERROR, "pam_authenticate failed");
50 goto fail;
51 }
52 // TODO: only call pam_end once we succeed at authing. refresh tokens beforehand
53 if ((pam_err = pam_end(local_auth_handle, pam_err)) != PAM_SUCCESS) {
54 wlr_log(WLR_ERROR, "pam_end failed");
55 goto fail;
56 }
57 clear_password_buffer(pw);
58 return true;
59fail:
60 clear_password_buffer(pw);
61 return false;
62}
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 @@
1#define _XOPEN_SOURCE 500 1#define _XOPEN_SOURCE 500
2#include <assert.h> 2#include <assert.h>
3#include <pwd.h> 3#include <pwd.h>
4#include <security/pam_appl.h>
5#include <stdlib.h> 4#include <stdlib.h>
6#include <string.h> 5#include <string.h>
7#include <unistd.h> 6#include <unistd.h>
@@ -11,27 +10,6 @@
11#include "swaylock/seat.h" 10#include "swaylock/seat.h"
12#include "unicode.h" 11#include "unicode.h"
13 12
14static int function_conversation(int num_msg, const struct pam_message **msg,
15 struct pam_response **resp, void *data) {
16 struct swaylock_password *pw = data;
17 /* PAM expects an array of responses, one for each message */
18 struct pam_response *pam_reply = calloc(
19 num_msg, sizeof(struct pam_response));
20 *resp = pam_reply;
21 for (int i = 0; i < num_msg; ++i) {
22 switch (msg[i]->msg_style) {
23 case PAM_PROMPT_ECHO_OFF:
24 case PAM_PROMPT_ECHO_ON:
25 pam_reply[i].resp = strdup(pw->buffer); // PAM clears and frees this
26 break;
27 case PAM_ERROR_MSG:
28 case PAM_TEXT_INFO:
29 break;
30 }
31 }
32 return PAM_SUCCESS;
33}
34
35void clear_password_buffer(struct swaylock_password *pw) { 13void clear_password_buffer(struct swaylock_password *pw) {
36 // Use volatile keyword so so compiler can't optimize this out. 14 // Use volatile keyword so so compiler can't optimize this out.
37 volatile char *buffer = pw->buffer; 15 volatile char *buffer = pw->buffer;
@@ -42,35 +20,6 @@ void clear_password_buffer(struct swaylock_password *pw) {
42 pw->len = 0; 20 pw->len = 0;
43} 21}
44 22
45static bool attempt_password(struct swaylock_password *pw) {
46 struct passwd *passwd = getpwuid(getuid());
47 char *username = passwd->pw_name;
48 const struct pam_conv local_conversation = {
49 function_conversation, pw
50 };
51 pam_handle_t *local_auth_handle = NULL;
52 int pam_err;
53 // TODO: only call pam_start once. keep the same handle the whole time
54 if ((pam_err = pam_start("swaylock", username,
55 &local_conversation, &local_auth_handle)) != PAM_SUCCESS) {
56 wlr_log(WLR_ERROR, "PAM returned error %d", pam_err);
57 }
58 if ((pam_err = pam_authenticate(local_auth_handle, 0)) != PAM_SUCCESS) {
59 wlr_log(WLR_ERROR, "pam_authenticate failed");
60 goto fail;
61 }
62 // TODO: only call pam_end once we succeed at authing. refresh tokens beforehand
63 if ((pam_err = pam_end(local_auth_handle, pam_err)) != PAM_SUCCESS) {
64 wlr_log(WLR_ERROR, "pam_end failed");
65 goto fail;
66 }
67 clear_password_buffer(pw);
68 return true;
69fail:
70 clear_password_buffer(pw);
71 return false;
72}
73
74static bool backspace(struct swaylock_password *pw) { 23static bool backspace(struct swaylock_password *pw) {
75 if (pw->len != 0) { 24 if (pw->len != 0) {
76 pw->buffer[--pw->len] = 0; 25 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 @@
1#define _XOPEN_SOURCE
2#include <pwd.h>
3#include <shadow.h>
4#include <stdbool.h>
5#include <sys/types.h>
6#include <unistd.h>
7#include <wlr/util/log.h>
8#include "swaylock/swaylock.h"
9
10static int comm[2][2];
11
12void run_child(void) {
13 /* This code runs as root */
14 struct passwd *pwent = getpwuid(getuid());
15 if (!pwent) {
16 wlr_log_errno(WLR_ERROR, "failed to getpwuid");
17 exit(EXIT_FAILURE);
18 }
19 char *encpw = pwent->pw_passwd;
20 if (strcmp(encpw, "x") == 0) {
21 struct spwd *swent = getspnam(pwent->pw_name);
22 if (!swent) {
23 wlr_log_errno(WLR_ERROR, "failed to getspnam");
24 exit(EXIT_FAILURE);
25 }
26 encpw = swent->sp_pwdp;
27 }
28 wlr_log(WLR_DEBUG, "prepared to authorize user %s", pwent->pw_name);
29
30 size_t size;
31 char *buf;
32 while (1) {
33 ssize_t amt;
34 amt = read(comm[0][0], &size, sizeof(size));
35 if (amt == 0) {
36 break;
37 } else if (amt < 0) {
38 wlr_log_errno(WLR_ERROR, "read pw request");
39 }
40 wlr_log(WLR_DEBUG, "received pw check request");
41 buf = malloc(size);
42 if (!buf) {
43 wlr_log_errno(WLR_ERROR, "failed to malloc pw buffer");
44 exit(EXIT_FAILURE);
45 }
46 size_t offs = 0;
47 do {
48 amt = read(comm[0][0], &buf[offs], size - offs);
49 if (amt <= 0) {
50 wlr_log_errno(WLR_ERROR, "failed to read pw");
51 exit(EXIT_FAILURE);
52 }
53 offs += (size_t)amt;
54 } while (offs < size);
55 bool result = false;
56 char *c = crypt(buf, encpw);
57 if (c == NULL) {
58 wlr_log_errno(WLR_ERROR, "crypt");
59 }
60 result = strcmp(c, encpw) == 0;
61 if (write(comm[1][1], &result, sizeof(result)) != sizeof(result)) {
62 wlr_log_errno(WLR_ERROR, "failed to write pw check result");
63 exit(EXIT_FAILURE);
64 }
65 free(buf);
66 }
67 exit(EXIT_SUCCESS);
68}
69
70void initialize_pw_backend(void) {
71 if (geteuid() != 0) {
72 wlr_log(WLR_ERROR, "swaylock needs to be setuid to read /etc/shadow");
73 exit(EXIT_FAILURE);
74 }
75 if (pipe(comm[0]) != 0) {
76 wlr_log_errno(WLR_ERROR, "failed to create pipe");
77 exit(EXIT_FAILURE);
78 }
79 if (pipe(comm[1]) != 0) {
80 wlr_log_errno(WLR_ERROR, "failed to create pipe");
81 exit(EXIT_FAILURE);
82 }
83 pid_t child = fork();
84 if (child == 0) {
85 close(comm[0][1]);
86 close(comm[1][0]);
87 run_child();
88 } else if (child < 0) {
89 wlr_log_errno(WLR_ERROR, "failed to fork");
90 exit(EXIT_FAILURE);
91 }
92 close(comm[0][0]);
93 close(comm[1][1]);
94 if (setgid(getgid()) != 0) {
95 wlr_log_errno(WLR_ERROR, "Unable to drop root");
96 exit(EXIT_FAILURE);
97 }
98 if (setuid(getuid()) != 0) {
99 wlr_log_errno(WLR_ERROR, "Unable to drop root");
100 exit(EXIT_FAILURE);
101 }
102}
103
104bool attempt_password(struct swaylock_password *pw) {
105 bool result = false;
106 size_t len = pw->len + 1;
107 size_t offs = 0;
108 if (write(comm[0][1], &len, sizeof(len)) < 0) {
109 wlr_log_errno(WLR_ERROR, "Failed to request pw check");
110 goto ret;
111 }
112 do {
113 ssize_t amt = write(comm[0][1], &pw->buffer[offs], len - offs);
114 if (amt < 0) {
115 wlr_log_errno(WLR_ERROR, "Failed to write pw buffer");
116 goto ret;
117 }
118 offs += amt;
119 } while (offs < len);
120 if (read(comm[1][0], &result, sizeof(result)) != sizeof(result)) {
121 wlr_log_errno(WLR_ERROR, "Failed to read pw result");
122 goto ret;
123 }
124 wlr_log(WLR_DEBUG, "pw result: %d", result);
125ret:
126 clear_password_buffer(pw);
127 return result;
128}