diff options
Diffstat (limited to 'swaylock')
-rw-r--r-- | swaylock/main.c | 5 | ||||
-rw-r--r-- | swaylock/meson.build | 50 | ||||
-rw-r--r-- | swaylock/pam.c | 62 | ||||
-rw-r--r-- | swaylock/password.c | 51 | ||||
-rw-r--r-- | swaylock/shadow.c | 128 |
5 files changed, 224 insertions, 72 deletions
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, | |||
845 | static struct swaylock_state state; | 845 | static struct swaylock_state state; |
846 | 846 | ||
847 | int main(int argc, char **argv) { | 847 | int 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 @@ | |||
1 | sysconfdir = get_option('sysconfdir') | 1 | sysconfdir = get_option('sysconfdir') |
2 | 2 | ||
3 | executable( | 3 | dependencies = [ |
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 | |||
15 | sources = [ | ||
16 | 'main.c', | ||
17 | 'password.c', | ||
18 | 'render.c', | ||
19 | 'seat.c' | ||
20 | ] | ||
21 | |||
22 | if libpam.found() | ||
23 | sources += ['pam.c'] | ||
24 | dependencies += [libpam] | ||
25 | else | ||
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'] | ||
29 | endif | ||
30 | |||
31 | executable('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 | |||
11 | void initialize_pw_backend(void) { | ||
12 | // TODO: only call pam_start once. keep the same handle the whole time | ||
13 | } | ||
14 | |||
15 | static 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 | |||
36 | bool 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; | ||
59 | fail: | ||
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 | ||
14 | static 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 | |||
35 | void clear_password_buffer(struct swaylock_password *pw) { | 13 | void 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 | ||
45 | static 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; | ||
69 | fail: | ||
70 | clear_password_buffer(pw); | ||
71 | return false; | ||
72 | } | ||
73 | |||
74 | static bool backspace(struct swaylock_password *pw) { | 23 | static 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 | |||
10 | static int comm[2][2]; | ||
11 | |||
12 | void 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 | |||
70 | void 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 | |||
104 | bool 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); | ||
125 | ret: | ||
126 | clear_password_buffer(pw); | ||
127 | return result; | ||
128 | } | ||