]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/home/pam_systemd_home.c
pam_systemd/pam_systemd_home: rework how we cache user records
[thirdparty/systemd.git] / src / home / pam_systemd_home.c
CommitLineData
26cf9fb7
LP
1/* SPDX-License-Identifier: LGPL-2.1+ */
2
3#include <security/pam_ext.h>
4#include <security/pam_modules.h>
5
6#include "sd-bus.h"
7
8#include "bus-common-errors.h"
ea7cbf5b 9#include "bus-util.h"
26cf9fb7
LP
10#include "errno-util.h"
11#include "fd-util.h"
12#include "home-util.h"
13#include "memory-util.h"
14#include "pam-util.h"
15#include "parse-util.h"
16#include "strv.h"
17#include "user-record-util.h"
18#include "user-record.h"
19#include "user-util.h"
20
26cf9fb7
LP
21static int parse_argv(
22 pam_handle_t *handle,
23 int argc, const char **argv,
24 bool *please_suspend,
25 bool *debug) {
26
27 int i;
28
29 assert(argc >= 0);
30 assert(argc == 0 || argv);
31
32 for (i = 0; i < argc; i++) {
33 const char *v;
34
f12d19b3 35 if ((v = startswith(argv[i], "suspend="))) {
26cf9fb7
LP
36 int k;
37
38 k = parse_boolean(v);
39 if (k < 0)
80ace4f2 40 pam_syslog(handle, LOG_WARNING, "Failed to parse suspend= argument, ignoring: %s", v);
26cf9fb7
LP
41 else if (please_suspend)
42 *please_suspend = k;
43
332f38d0
JS
44 } else if (streq(argv[i], "debug")) {
45 if (debug)
46 *debug = true;
47
26cf9fb7
LP
48 } else if ((v = startswith(argv[i], "debug="))) {
49 int k;
26cf9fb7
LP
50 k = parse_boolean(v);
51 if (k < 0)
52 pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring: %s", v);
53 else if (debug)
54 *debug = k;
55
56 } else
57 pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
58 }
59
60 return 0;
61}
62
63static int acquire_user_record(
64 pam_handle_t *handle,
65 UserRecord **ret_record) {
66
67 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
68 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
69 _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
70 _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
71 const char *username = NULL, *json = NULL;
dbe7fff4 72 _cleanup_free_ char *homed_field = NULL;
26cf9fb7
LP
73 int r;
74
75 assert(handle);
76
77 r = pam_get_user(handle, &username, NULL);
78 if (r != PAM_SUCCESS) {
79 pam_syslog(handle, LOG_ERR, "Failed to get user name: %s", pam_strerror(handle, r));
80 return r;
81 }
82
83 if (isempty(username)) {
84 pam_syslog(handle, LOG_ERR, "User name not set.");
85 return PAM_SERVICE_ERR;
86 }
87
88 /* Let's bypass all IPC complexity for the two user names we know for sure we don't manage, and for
89 * user names we don't consider valid. */
7a8867ab 90 if (STR_IN_SET(username, "root", NOBODY_USER_NAME) || !valid_user_group_name(username, 0))
26cf9fb7
LP
91 return PAM_USER_UNKNOWN;
92
dbe7fff4
LP
93 /* We cache the user record in the PAM context. We use a field name that includes the username, since
94 * clients might change the user name associated with a PAM context underneath us. Notably, 'sudo'
95 * creates a single PAM context and first authenticates it with the user set to the originating user,
96 * then updates the user for the destination user and issues the session stack with the same PAM
97 * context. We thus must be prepared that the user record changes between calls and we keep any
98 * caching separate. */
99 homed_field = strjoin("systemd-home-user-record-", username);
100 if (!homed_field)
101 return pam_log_oom(handle);
102
103 /* Let's use the cache, so that we can share it between the session and the authentication hooks */
104 r = pam_get_data(handle, homed_field, (const void**) &json);
26cf9fb7 105 if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) {
dbe7fff4 106 pam_syslog(handle, LOG_ERR, "Failed to get PAM user record data: %s", pam_strerror(handle, r));
26cf9fb7 107 return r;
26cf9fb7 108 }
dbe7fff4
LP
109 if (r == PAM_SUCCESS && json) {
110 /* We determined earlier that this is not a homed user? Then exit early. (We use -1 as
111 * negative cache indicator) */
112 if (json == (void*) -1)
113 return PAM_USER_UNKNOWN;
114 } else {
26cf9fb7 115 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
dbe7fff4 116 _cleanup_free_ char *generic_field = NULL, *json_copy = NULL;
26cf9fb7
LP
117
118 r = pam_acquire_bus_connection(handle, &bus);
119 if (r != PAM_SUCCESS)
120 return r;
121
8a1596aa 122 r = bus_call_method(bus, bus_home_mgr, "GetUserRecordByName", &error, &reply, "s", username);
26cf9fb7
LP
123 if (r < 0) {
124 if (sd_bus_error_has_name(&error, SD_BUS_ERROR_SERVICE_UNKNOWN) ||
125 sd_bus_error_has_name(&error, SD_BUS_ERROR_NAME_HAS_NO_OWNER)) {
126 pam_syslog(handle, LOG_DEBUG, "systemd-homed is not available: %s", bus_error_message(&error, r));
127 goto user_unknown;
128 }
129
130 if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_HOME)) {
131 pam_syslog(handle, LOG_DEBUG, "Not a user managed by systemd-homed: %s", bus_error_message(&error, r));
132 goto user_unknown;
133 }
134
135 pam_syslog(handle, LOG_ERR, "Failed to query user record: %s", bus_error_message(&error, r));
136 return PAM_SERVICE_ERR;
137 }
138
139 r = sd_bus_message_read(reply, "sbo", &json, NULL, NULL);
140 if (r < 0)
141 return pam_bus_log_parse_error(handle, r);
142
dbe7fff4
LP
143 /* First copy: for the homed-specific data field, i.e. where we know the user record is from
144 * homed */
26cf9fb7
LP
145 json_copy = strdup(json);
146 if (!json_copy)
147 return pam_log_oom(handle);
148
dbe7fff4 149 r = pam_set_data(handle, homed_field, json_copy, pam_cleanup_free);
26cf9fb7 150 if (r != PAM_SUCCESS) {
dbe7fff4
LP
151 pam_syslog(handle, LOG_ERR, "Failed to set PAM user record data '%s': %s",
152 homed_field, pam_strerror(handle, r));
26cf9fb7
LP
153 return r;
154 }
155
dbe7fff4
LP
156 /* Take a second copy: for the generic data field, the one which we share with
157 * pam_systemd. While we insist on only reusing homed records, pam_systemd is fine with homed
158 * and non-homed user records. */
159 json_copy = strdup(json);
160 if (!json_copy)
161 return pam_log_oom(handle);
162
163 generic_field = strjoin("systemd-user-record-", username);
164 if (!generic_field)
165 return pam_log_oom(handle);
26cf9fb7 166
dbe7fff4 167 r = pam_set_data(handle, generic_field, json_copy, pam_cleanup_free);
26cf9fb7 168 if (r != PAM_SUCCESS) {
dbe7fff4
LP
169 pam_syslog(handle, LOG_ERR, "Failed to set PAM user record data '%s': %s",
170 homed_field, pam_strerror(handle, r));
26cf9fb7
LP
171 return r;
172 }
dbe7fff4
LP
173
174 TAKE_PTR(json_copy);
26cf9fb7
LP
175 }
176
177 r = json_parse(json, JSON_PARSE_SENSITIVE, &v, NULL, NULL);
178 if (r < 0) {
179 pam_syslog(handle, LOG_ERR, "Failed to parse JSON user record: %s", strerror_safe(r));
180 return PAM_SERVICE_ERR;
181 }
182
183 ur = user_record_new();
184 if (!ur)
185 return pam_log_oom(handle);
186
187 r = user_record_load(ur, v, USER_RECORD_LOAD_REFUSE_SECRET);
188 if (r < 0) {
189 pam_syslog(handle, LOG_ERR, "Failed to load user record: %s", strerror_safe(r));
190 return PAM_SERVICE_ERR;
191 }
192
dbe7fff4 193 /* Safety check if cached record actually matches what we are looking for */
26cf9fb7
LP
194 if (!streq_ptr(username, ur->user_name)) {
195 pam_syslog(handle, LOG_ERR, "Acquired user record does not match user name.");
196 return PAM_SERVICE_ERR;
197 }
198
199 if (ret_record)
200 *ret_record = TAKE_PTR(ur);
201
202 return PAM_SUCCESS;
203
204user_unknown:
205 /* Cache this, so that we don't check again */
dbe7fff4 206 r = pam_set_data(handle, homed_field, (void*) -1, NULL);
26cf9fb7 207 if (r != PAM_SUCCESS)
dbe7fff4
LP
208 pam_syslog(handle, LOG_ERR, "Failed to set PAM user record data '%s' to invalid, ignoring: %s",
209 homed_field, pam_strerror(handle, r));
26cf9fb7
LP
210
211 return PAM_USER_UNKNOWN;
212}
213
dbe7fff4
LP
214static int release_user_record(pam_handle_t *handle, const char *username) {
215 _cleanup_free_ char *homed_field = NULL, *generic_field = NULL;
26cf9fb7
LP
216 int r, k;
217
dbe7fff4
LP
218 assert(handle);
219 assert(username);
220
221 homed_field = strjoin("systemd-home-user-record-", username);
222 if (!homed_field)
223 return pam_log_oom(handle);
224
225 r = pam_set_data(handle, homed_field, NULL, NULL);
26cf9fb7 226 if (r != PAM_SUCCESS)
dbe7fff4
LP
227 pam_syslog(handle, LOG_ERR, "Failed to release PAM user record data '%s': %s", homed_field, pam_strerror(handle, r));
228
229 generic_field = strjoin("systemd-user-record-", username);
230 if (!generic_field)
231 return pam_log_oom(handle);
26cf9fb7 232
dbe7fff4 233 k = pam_set_data(handle, generic_field, NULL, NULL);
26cf9fb7 234 if (k != PAM_SUCCESS)
dbe7fff4 235 pam_syslog(handle, LOG_ERR, "Failed to release PAM user record data '%s': %s", generic_field, pam_strerror(handle, k));
26cf9fb7
LP
236
237 return IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA) ? k : r;
238}
239
240static void cleanup_home_fd(pam_handle_t *handle, void *data, int error_status) {
241 safe_close(PTR_TO_FD(data));
242}
243
244static int handle_generic_user_record_error(
245 pam_handle_t *handle,
246 const char *user_name,
247 UserRecord *secret,
248 int ret,
249 const sd_bus_error *error) {
250
251 assert(user_name);
252 assert(secret);
253 assert(error);
254
255 int r;
256
257 /* Logs about all errors, except for PAM_CONV_ERR, i.e. when requesting more info failed. */
258
259 if (sd_bus_error_has_name(error, BUS_ERROR_HOME_ABSENT)) {
260 (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Home of user %s is currently absent, please plug in the necessary storage device or backing file system.", user_name);
261 pam_syslog(handle, LOG_ERR, "Failed to acquire home for user %s: %s", user_name, bus_error_message(error, ret));
262 return PAM_PERM_DENIED;
263
264 } else if (sd_bus_error_has_name(error, BUS_ERROR_AUTHENTICATION_LIMIT_HIT)) {
265 (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Too frequent unsuccessful login attempts for user %s, try again later.", user_name);
266 pam_syslog(handle, LOG_ERR, "Failed to acquire home for user %s: %s", user_name, bus_error_message(error, ret));
267 return PAM_MAXTRIES;
268
269 } else if (sd_bus_error_has_name(error, BUS_ERROR_BAD_PASSWORD)) {
270 _cleanup_(erase_and_freep) char *newp = NULL;
271
272 /* This didn't work? Ask for an (additional?) password */
273
274 if (strv_isempty(secret->password))
275 r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Password: ");
d4232943
JS
276 else {
277 (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Password incorrect or not sufficient for authentication of user %s.", user_name);
278 r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Sorry, try again: ");
279 }
26cf9fb7
LP
280 if (r != PAM_SUCCESS)
281 return PAM_CONV_ERR; /* no logging here */
282
283 if (isempty(newp)) {
284 pam_syslog(handle, LOG_DEBUG, "Password request aborted.");
285 return PAM_AUTHTOK_ERR;
286 }
287
288 r = user_record_set_password(secret, STRV_MAKE(newp), true);
289 if (r < 0) {
290 pam_syslog(handle, LOG_ERR, "Failed to store password: %s", strerror_safe(r));
291 return PAM_SERVICE_ERR;
292 }
293
294 } else if (sd_bus_error_has_name(error, BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN)) {
295 _cleanup_(erase_and_freep) char *newp = NULL;
296
d4232943
JS
297 if (strv_isempty(secret->password)) {
298 (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Security token of user %s not inserted.", user_name);
299 r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Try again with password: ");
300 } else {
301 (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Password incorrect or not sufficient, and configured security token of user %s not inserted.", user_name);
302 r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Try again with password: ");
303 }
26cf9fb7
LP
304 if (r != PAM_SUCCESS)
305 return PAM_CONV_ERR; /* no logging here */
306
307 if (isempty(newp)) {
308 pam_syslog(handle, LOG_DEBUG, "Password request aborted.");
309 return PAM_AUTHTOK_ERR;
310 }
311
312 r = user_record_set_password(secret, STRV_MAKE(newp), true);
313 if (r < 0) {
314 pam_syslog(handle, LOG_ERR, "Failed to store password: %s", strerror_safe(r));
315 return PAM_SERVICE_ERR;
316 }
317
318 } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PIN_NEEDED)) {
319 _cleanup_(erase_and_freep) char *newp = NULL;
320
d4232943 321 r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Security token PIN: ");
26cf9fb7
LP
322 if (r != PAM_SUCCESS)
323 return PAM_CONV_ERR; /* no logging here */
324
325 if (isempty(newp)) {
326 pam_syslog(handle, LOG_DEBUG, "PIN request aborted.");
327 return PAM_AUTHTOK_ERR;
328 }
329
330 r = user_record_set_pkcs11_pin(secret, STRV_MAKE(newp), false);
331 if (r < 0) {
332 pam_syslog(handle, LOG_ERR, "Failed to store PIN: %s", strerror_safe(r));
333 return PAM_SERVICE_ERR;
334 }
335
336 } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED)) {
337
338 (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Please authenticate physically on security token of user %s.", user_name);
339
340 r = user_record_set_pkcs11_protected_authentication_path_permitted(secret, true);
341 if (r < 0) {
342 pam_syslog(handle, LOG_ERR, "Failed to set PKCS#11 protected authentication path permitted flag: %s", strerror_safe(r));
343 return PAM_SERVICE_ERR;
344 }
345
346 } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN)) {
347 _cleanup_(erase_and_freep) char *newp = NULL;
348
d4232943
JS
349 (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Security token PIN incorrect for user %s.", user_name);
350 r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Sorry, retry security token PIN: ");
26cf9fb7
LP
351 if (r != PAM_SUCCESS)
352 return PAM_CONV_ERR; /* no logging here */
353
354 if (isempty(newp)) {
355 pam_syslog(handle, LOG_DEBUG, "PIN request aborted.");
356 return PAM_AUTHTOK_ERR;
357 }
358
359 r = user_record_set_pkcs11_pin(secret, STRV_MAKE(newp), false);
360 if (r < 0) {
361 pam_syslog(handle, LOG_ERR, "Failed to store PIN: %s", strerror_safe(r));
362 return PAM_SERVICE_ERR;
363 }
364
365 } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT)) {
366 _cleanup_(erase_and_freep) char *newp = NULL;
367
d4232943
JS
368 (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Security token PIN of user %s incorrect (only a few tries left!)", user_name);
369 r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Sorry, retry security token PIN: ");
26cf9fb7
LP
370 if (r != PAM_SUCCESS)
371 return PAM_CONV_ERR; /* no logging here */
372
373 if (isempty(newp)) {
374 pam_syslog(handle, LOG_DEBUG, "PIN request aborted.");
375 return PAM_AUTHTOK_ERR;
376 }
377
378 r = user_record_set_pkcs11_pin(secret, STRV_MAKE(newp), false);
379 if (r < 0) {
380 pam_syslog(handle, LOG_ERR, "Failed to store PIN: %s", strerror_safe(r));
381 return PAM_SERVICE_ERR;
382 }
383
384 } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN_ONE_TRY_LEFT)) {
385 _cleanup_(erase_and_freep) char *newp = NULL;
386
d4232943
JS
387 (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Security token PIN of user %s incorrect (only one try left!)", user_name);
388 r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Sorry, retry security token PIN: ");
26cf9fb7
LP
389 if (r != PAM_SUCCESS)
390 return PAM_CONV_ERR; /* no logging here */
391
392 if (isempty(newp)) {
393 pam_syslog(handle, LOG_DEBUG, "PIN request aborted.");
394 return PAM_AUTHTOK_ERR;
395 }
396
397 r = user_record_set_pkcs11_pin(secret, STRV_MAKE(newp), false);
398 if (r < 0) {
399 pam_syslog(handle, LOG_ERR, "Failed to store PIN: %s", strerror_safe(r));
400 return PAM_SERVICE_ERR;
401 }
402
403 } else {
404 pam_syslog(handle, LOG_ERR, "Failed to acquire home for user %s: %s", user_name, bus_error_message(error, ret));
405 return PAM_SERVICE_ERR;
406 }
407
408 return PAM_SUCCESS;
409}
410
411static int acquire_home(
412 pam_handle_t *handle,
413 bool please_authenticate,
414 bool please_suspend,
415 bool debug) {
416
417 _cleanup_(user_record_unrefp) UserRecord *ur = NULL, *secret = NULL;
418 bool do_auth = please_authenticate, home_not_active = false, home_locked = false;
419 _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
420 _cleanup_close_ int acquired_fd = -1;
421 const void *home_fd_ptr = NULL;
422 unsigned n_attempts = 0;
423 int r;
424
425 assert(handle);
426
427 /* This acquires a reference to a home directory in one of two ways: if please_authenticate is true,
428 * then we'll call AcquireHome() after asking the user for a password. Otherwise it tries to call
429 * RefHome() and if that fails queries the user for a password and uses AcquireHome().
430 *
431 * The idea is that the PAM authentication hook sets please_authenticate and thus always
432 * authenticates, while the other PAM hooks unset it so that they can a ref of their own without
433 * authentication if possible, but with authentication if necessary. */
434
435 /* If we already have acquired the fd, let's shortcut this */
436 r = pam_get_data(handle, "systemd-home-fd", &home_fd_ptr);
da4340fd 437 if (r == PAM_SUCCESS && PTR_TO_FD(home_fd_ptr) >= 0)
26cf9fb7
LP
438 return PAM_SUCCESS;
439
440 r = pam_acquire_bus_connection(handle, &bus);
441 if (r != PAM_SUCCESS)
442 return r;
443
444 r = acquire_user_record(handle, &ur);
445 if (r != PAM_SUCCESS)
446 return r;
447
448 /* Implement our own retry loop here instead of relying on the PAM client's one. That's because it
449 * might happen that the the record we stored on the host does not match the encryption password of
450 * the LUKS image in case the image was used in a different system where the password was
451 * changed. In that case it will happen that the LUKS password and the host password are
452 * different, and we handle that by collecting and passing multiple passwords in that case. Hence we
453 * treat bad passwords as a request to collect one more password and pass the new all all previously
454 * used passwords again. */
455
456 for (;;) {
457 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
458 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
459
460 if (do_auth && !secret) {
461 const char *cached_password = NULL;
462
463 secret = user_record_new();
464 if (!secret)
465 return pam_log_oom(handle);
466
467 /* If there's already a cached password, use it. But if not let's authenticate
468 * without anything, maybe some other authentication mechanism systemd-homed
469 * implements (such as PKCS#11) allows us to authenticate without anything else. */
470 r = pam_get_item(handle, PAM_AUTHTOK, (const void**) &cached_password);
471 if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS)) {
472 pam_syslog(handle, LOG_ERR, "Failed to get cached password: %s", pam_strerror(handle, r));
473 return r;
474 }
475
476 if (!isempty(cached_password)) {
477 r = user_record_set_password(secret, STRV_MAKE(cached_password), true);
478 if (r < 0) {
479 pam_syslog(handle, LOG_ERR, "Failed to store password: %s", strerror_safe(r));
480 return PAM_SERVICE_ERR;
481 }
482 }
483 }
484
8a1596aa 485 r = bus_message_new_method_call(bus, &m, bus_home_mgr, do_auth ? "AcquireHome" : "RefHome");
26cf9fb7
LP
486 if (r < 0)
487 return pam_bus_log_create_error(handle, r);
488
489 r = sd_bus_message_append(m, "s", ur->user_name);
490 if (r < 0)
491 return pam_bus_log_create_error(handle, r);
492
493 if (do_auth) {
494 r = bus_message_append_secret(m, secret);
495 if (r < 0)
496 return pam_bus_log_create_error(handle, r);
497 }
498
499 r = sd_bus_message_append(m, "b", please_suspend);
500 if (r < 0)
501 return pam_bus_log_create_error(handle, r);
502
503 r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply);
504 if (r < 0) {
505
506 if (sd_bus_error_has_name(&error, BUS_ERROR_HOME_NOT_ACTIVE))
507 /* Only on RefHome(): We can't access the home directory currently, unless
508 * it's unlocked with a password. Hence, let's try this again, this time with
509 * authentication. */
510 home_not_active = true;
511 else if (sd_bus_error_has_name(&error, BUS_ERROR_HOME_LOCKED))
512 home_locked = true; /* Similar */
513 else {
514 r = handle_generic_user_record_error(handle, ur->user_name, secret, r, &error);
515 if (r == PAM_CONV_ERR) {
516 /* Password/PIN prompts will fail in certain environments, for example when
517 * we are called from OpenSSH's account or session hooks, or in systemd's
518 * per-service PAM logic. In that case, print a friendly message and accept
519 * failure. */
520
521 if (home_not_active)
522 (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Home of user %s is currently not active, please log in locally first.", ur->user_name);
523 if (home_locked)
524 (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Home of user %s is currently locked, please unlock locally first.", ur->user_name);
525
526 pam_syslog(handle, please_authenticate ? LOG_ERR : LOG_DEBUG, "Failed to prompt for password/prompt.");
527
528 return home_not_active || home_locked ? PAM_PERM_DENIED : PAM_CONV_ERR;
529 }
530 if (r != PAM_SUCCESS)
531 return r;
532 }
533
534 } else {
535 int fd;
536
537 r = sd_bus_message_read(reply, "h", &fd);
538 if (r < 0)
539 return pam_bus_log_parse_error(handle, r);
540
541 acquired_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
542 if (acquired_fd < 0) {
543 pam_syslog(handle, LOG_ERR, "Failed to duplicate acquired fd: %s", bus_error_message(&error, r));
544 return PAM_SERVICE_ERR;
545 }
546
547 break;
548 }
549
550 if (++n_attempts >= 5) {
551 (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Too many unsuccessful login attempts for user %s, refusing.", ur->user_name);
552 pam_syslog(handle, LOG_ERR, "Failed to acquire home for user %s: %s", ur->user_name, bus_error_message(&error, r));
553 return PAM_MAXTRIES;
554 }
555
556 /* Try again, this time with authentication if we didn't do that before. */
557 do_auth = true;
558 }
559
560 r = pam_set_data(handle, "systemd-home-fd", FD_TO_PTR(acquired_fd), cleanup_home_fd);
561 if (r < 0) {
562 pam_syslog(handle, LOG_ERR, "Failed to set PAM bus data: %s", pam_strerror(handle, r));
563 return r;
564 }
565 TAKE_FD(acquired_fd);
566
567 if (do_auth) {
568 /* We likely just activated the home directory, let's flush out the user record, since a
569 * newer embedded user record might have been acquired from the activation. */
570
dbe7fff4 571 r = release_user_record(handle, ur->user_name);
26cf9fb7
LP
572 if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
573 return r;
574 }
575
576 pam_syslog(handle, LOG_NOTICE, "Home for user %s successfully acquired.", ur->user_name);
577
578 return PAM_SUCCESS;
579}
580
581static int release_home_fd(pam_handle_t *handle) {
582 const void *home_fd_ptr = NULL;
583 int r;
584
585 r = pam_get_data(handle, "systemd-home-fd", &home_fd_ptr);
586 if (r == PAM_NO_MODULE_DATA || PTR_TO_FD(home_fd_ptr) < 0)
587 return PAM_NO_MODULE_DATA;
588
589 r = pam_set_data(handle, "systemd-home-fd", NULL, NULL);
590 if (r != PAM_SUCCESS)
591 pam_syslog(handle, LOG_ERR, "Failed to release PAM home reference fd: %s", pam_strerror(handle, r));
592
593 return r;
594}
595
596_public_ PAM_EXTERN int pam_sm_authenticate(
597 pam_handle_t *handle,
598 int flags,
599 int argc, const char **argv) {
600
601 bool debug = false, suspend_please = false;
602
603 if (parse_argv(handle,
604 argc, argv,
605 &suspend_please,
606 &debug) < 0)
607 return PAM_AUTH_ERR;
608
609 if (debug)
610 pam_syslog(handle, LOG_DEBUG, "pam-systemd-homed authenticating");
611
612 return acquire_home(handle, /* please_authenticate= */ true, suspend_please, debug);
613}
614
615_public_ PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) {
616 return PAM_SUCCESS;
617}
618
619_public_ PAM_EXTERN int pam_sm_open_session(
620 pam_handle_t *handle,
621 int flags,
622 int argc, const char **argv) {
623
624 bool debug = false, suspend_please = false;
625 int r;
626
627 if (parse_argv(handle,
628 argc, argv,
629 &suspend_please,
630 &debug) < 0)
631 return PAM_SESSION_ERR;
632
633 if (debug)
634 pam_syslog(handle, LOG_DEBUG, "pam-systemd-homed session start");
635
636 r = acquire_home(handle, /* please_authenticate = */ false, suspend_please, debug);
637 if (r == PAM_USER_UNKNOWN) /* Not managed by us? Don't complain. */
638 return PAM_SUCCESS;
639 if (r != PAM_SUCCESS)
640 return r;
641
642 r = pam_putenv(handle, "SYSTEMD_HOME=1");
643 if (r != PAM_SUCCESS) {
644 pam_syslog(handle, LOG_ERR, "Failed to set PAM environment variable $SYSTEMD_HOME: %s", pam_strerror(handle, r));
645 return r;
646 }
647
648 /* Let's release the D-Bus connection, after all the session might live quite a long time, and we are
649 * not going to process the bus connection in that time, so let's better close before the daemon
650 * kicks us off because we are not processing anything. */
651 (void) pam_release_bus_connection(handle);
652 return PAM_SUCCESS;
653}
654
655_public_ PAM_EXTERN int pam_sm_close_session(
656 pam_handle_t *handle,
657 int flags,
658 int argc, const char **argv) {
659
660 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
661 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
662 _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
663 const char *username = NULL;
664 bool debug = false;
665 int r;
666
667 if (parse_argv(handle,
668 argc, argv,
669 NULL,
670 &debug) < 0)
671 return PAM_SESSION_ERR;
672
673 if (debug)
674 pam_syslog(handle, LOG_DEBUG, "pam-systemd-homed session end");
675
676 /* Let's explicitly drop the reference to the homed session, so that the subsequent ReleaseHome()
677 * call will be able to do its thing. */
678 r = release_home_fd(handle);
679 if (r == PAM_NO_MODULE_DATA) /* Nothing to do, we never acquired an fd */
680 return PAM_SUCCESS;
681 if (r != PAM_SUCCESS)
682 return r;
683
684 r = pam_get_user(handle, &username, NULL);
685 if (r != PAM_SUCCESS) {
686 pam_syslog(handle, LOG_ERR, "Failed to get user name: %s", pam_strerror(handle, r));
687 return r;
688 }
689
690 r = pam_acquire_bus_connection(handle, &bus);
691 if (r != PAM_SUCCESS)
692 return r;
693
8a1596aa 694 r = bus_message_new_method_call(bus, &m, bus_home_mgr, "ReleaseHome");
26cf9fb7
LP
695 if (r < 0)
696 return pam_bus_log_create_error(handle, r);
697
698 r = sd_bus_message_append(m, "s", username);
699 if (r < 0)
700 return pam_bus_log_create_error(handle, r);
701
702 r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
703 if (r < 0) {
704 if (sd_bus_error_has_name(&error, BUS_ERROR_HOME_BUSY))
705 pam_syslog(handle, LOG_NOTICE, "Not deactivating home directory of %s, as it is still used.", username);
706 else {
707 pam_syslog(handle, LOG_ERR, "Failed to release user home: %s", bus_error_message(&error, r));
708 return PAM_SESSION_ERR;
709 }
710 }
711
712 return PAM_SUCCESS;
713}
714
715_public_ PAM_EXTERN int pam_sm_acct_mgmt(
716 pam_handle_t *handle,
717 int flags,
718 int argc,
719 const char **argv) {
720
721 _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
722 bool debug = false, please_suspend = false;
723 usec_t t;
724 int r;
725
726 if (parse_argv(handle,
727 argc, argv,
728 &please_suspend,
729 &debug) < 0)
730 return PAM_AUTH_ERR;
731
732 if (debug)
733 pam_syslog(handle, LOG_DEBUG, "pam-systemd-homed account management");
734
735 r = acquire_home(handle, /* please_authenticate = */ false, please_suspend, debug);
736 if (r == PAM_USER_UNKNOWN)
737 return PAM_SUCCESS; /* we don't have anything to say about users we don't manage */
738 if (r != PAM_SUCCESS)
739 return r;
740
741 r = acquire_user_record(handle, &ur);
742 if (r != PAM_SUCCESS)
743 return r;
744
745 r = user_record_test_blocked(ur);
746 switch (r) {
747
748 case -ESTALE:
749 (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "User record is newer than current system time, prohibiting access.");
750 return PAM_ACCT_EXPIRED;
751
752 case -ENOLCK:
753 (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "User record is blocked, prohibiting access.");
754 return PAM_ACCT_EXPIRED;
755
756 case -EL2HLT:
757 (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "User record is not valid yet, prohibiting access.");
758 return PAM_ACCT_EXPIRED;
759
760 case -EL3HLT:
761 (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "User record is not valid anymore, prohibiting access.");
762 return PAM_ACCT_EXPIRED;
763
764 default:
765 if (r < 0) {
766 (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "User record not valid, prohibiting access.");
767 return PAM_ACCT_EXPIRED;
768 }
769
770 break;
771 }
772
773 t = user_record_ratelimit_next_try(ur);
774 if (t != USEC_INFINITY) {
775 usec_t n = now(CLOCK_REALTIME);
776
777 if (t > n) {
778 char buf[FORMAT_TIMESPAN_MAX];
779 (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Too many logins, try again in %s.",
780 format_timespan(buf, sizeof(buf), t - n, USEC_PER_SEC));
781
782 return PAM_MAXTRIES;
783 }
784 }
785
786 r = user_record_test_password_change_required(ur);
787 switch (r) {
788
789 case -EKEYREVOKED:
790 (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Password change required.");
791 return PAM_NEW_AUTHTOK_REQD;
792
793 case -EOWNERDEAD:
794 (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Password expired, change requird.");
795 return PAM_NEW_AUTHTOK_REQD;
796
797 case -EKEYREJECTED:
798 /* Strictly speaking this is only about password expiration, and we might want to allow
799 * authentication via PKCS#11 or so, but let's ignore this fine distinction for now. */
800 (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Password is expired, but can't change, refusing login.");
801 return PAM_AUTHTOK_EXPIRED;
802
803 case -EKEYEXPIRED:
804 (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Password will expire soon, please change.");
805 break;
806
807 case -EROFS:
808 /* All good, just means the password if we wanted to change we couldn't, but we don't need to */
809 break;
810
811 default:
812 if (r < 0) {
813 (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "User record not valid, prohibiting access.");
814 return PAM_AUTHTOK_EXPIRED;
815 }
816
817 break;
818 }
819
820 return PAM_SUCCESS;
821}
822
823_public_ PAM_EXTERN int pam_sm_chauthtok(
824 pam_handle_t *handle,
825 int flags,
826 int argc,
827 const char **argv) {
828
829 _cleanup_(user_record_unrefp) UserRecord *ur = NULL, *old_secret = NULL, *new_secret = NULL;
830 const char *old_password = NULL, *new_password = NULL;
831 _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
832 unsigned n_attempts = 0;
833 bool debug = false;
834 int r;
835
836 if (parse_argv(handle,
837 argc, argv,
838 NULL,
839 &debug) < 0)
840 return PAM_AUTH_ERR;
841
842 if (debug)
843 pam_syslog(handle, LOG_DEBUG, "pam-systemd-homed account management");
844
845 r = pam_acquire_bus_connection(handle, &bus);
846 if (r != PAM_SUCCESS)
847 return r;
848
849 r = acquire_user_record(handle, &ur);
850 if (r != PAM_SUCCESS)
851 return r;
852
853 /* Start with cached credentials */
854 r = pam_get_item(handle, PAM_OLDAUTHTOK, (const void**) &old_password);
855 if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS)) {
856 pam_syslog(handle, LOG_ERR, "Failed to get old password: %s", pam_strerror(handle, r));
857 return r;
858 }
859 r = pam_get_item(handle, PAM_AUTHTOK, (const void**) &new_password);
860 if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS)) {
861 pam_syslog(handle, LOG_ERR, "Failed to get cached password: %s", pam_strerror(handle, r));
862 return r;
863 }
864
865 if (isempty(new_password)) {
866 /* No, it's not cached, then let's ask for the password and its verification, and cache
867 * it. */
868
869 r = pam_get_authtok_noverify(handle, &new_password, "New password: ");
870 if (r != PAM_SUCCESS) {
871 pam_syslog(handle, LOG_ERR, "Failed to get new password: %s", pam_strerror(handle, r));
872 return r;
873 }
874 if (isempty(new_password)) {
875 pam_syslog(handle, LOG_DEBUG, "Password request aborted.");
876 return PAM_AUTHTOK_ERR;
877 }
878
879 r = pam_get_authtok_verify(handle, &new_password, "new password: "); /* Lower case, since PAM prefixes 'Repeat' */
880 if (r != PAM_SUCCESS) {
881 pam_syslog(handle, LOG_ERR, "Failed to get password again: %s", pam_strerror(handle, r));
882 return r;
883 }
884
885 // FIXME: pam_pwquality will ask for the password a third time. It really shouldn't do
886 // that, and instead assume the password was already verified once when it is found to be
887 // cached already. needs to be fixed in pam_pwquality
888 }
889
890 /* Now everything is cached and checked, let's exit from the preliminary check */
891 if (FLAGS_SET(flags, PAM_PRELIM_CHECK))
892 return PAM_SUCCESS;
893
26cf9fb7
LP
894 old_secret = user_record_new();
895 if (!old_secret)
896 return pam_log_oom(handle);
897
898 if (!isempty(old_password)) {
899 r = user_record_set_password(old_secret, STRV_MAKE(old_password), true);
900 if (r < 0) {
901 pam_syslog(handle, LOG_ERR, "Failed to store old password: %s", strerror_safe(r));
902 return PAM_SERVICE_ERR;
903 }
904 }
905
906 new_secret = user_record_new();
907 if (!new_secret)
908 return pam_log_oom(handle);
909
910 r = user_record_set_password(new_secret, STRV_MAKE(new_password), true);
911 if (r < 0) {
912 pam_syslog(handle, LOG_ERR, "Failed to store new password: %s", strerror_safe(r));
913 return PAM_SERVICE_ERR;
914 }
915
916 for (;;) {
917 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
918 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
919
8a1596aa 920 r = bus_message_new_method_call(bus, &m, bus_home_mgr, "ChangePasswordHome");
26cf9fb7
LP
921 if (r < 0)
922 return pam_bus_log_create_error(handle, r);
923
924 r = sd_bus_message_append(m, "s", ur->user_name);
925 if (r < 0)
926 return pam_bus_log_create_error(handle, r);
927
928 r = bus_message_append_secret(m, new_secret);
929 if (r < 0)
930 return pam_bus_log_create_error(handle, r);
931
932 r = bus_message_append_secret(m, old_secret);
933 if (r < 0)
934 return pam_bus_log_create_error(handle, r);
935
936 r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
937 if (r < 0) {
938 r = handle_generic_user_record_error(handle, ur->user_name, old_secret, r, &error);
939 if (r == PAM_CONV_ERR) {
940 pam_syslog(handle, LOG_ERR, "Failed to prompt for password/prompt.");
941 return PAM_CONV_ERR;
942 }
943 if (r != PAM_SUCCESS)
944 return r;
945 } else {
946 pam_syslog(handle, LOG_NOTICE, "Successfully changed password for user %s.", ur->user_name);
947 return PAM_SUCCESS;
948 }
949
950 if (++n_attempts >= 5)
951 break;
952
953 /* Try again */
954 };
955
956 pam_syslog(handle, LOG_NOTICE, "Failed to change password for user %s: %m", ur->user_name);
957 return PAM_MAXTRIES;
958}