]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/home/pam_systemd_home.c
io.systemd.Unit.List fix context/runtime split (#38172)
[thirdparty/systemd.git] / src / home / pam_systemd_home.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
26cf9fb7 2
572c1fe6 3#include <libintl.h>
26cf9fb7 4#include <security/pam_ext.h>
c747c041 5#include <security/pam_misc.h>
26cf9fb7
LP
6#include <security/pam_modules.h>
7
8#include "sd-bus.h"
9
6553db60 10#include "alloc-util.h"
26cf9fb7 11#include "bus-common-errors.h"
9b71e4ab 12#include "bus-locator.h"
73d3ac8e 13#include "bus-util.h"
26cf9fb7
LP
14#include "fd-util.h"
15#include "home-util.h"
4e680156 16#include "locale-util.h"
26cf9fb7
LP
17#include "pam-util.h"
18#include "parse-util.h"
c747c041 19#include "path-util.h"
572c1fe6 20#include "string-util.h"
26cf9fb7 21#include "strv.h"
572c1fe6 22#include "time-util.h"
26cf9fb7 23#include "user-record.h"
1cf40697 24#include "user-record-util.h"
26cf9fb7
LP
25#include "user-util.h"
26
563c5511
LP
27typedef enum AcquireHomeFlags {
28 ACQUIRE_MUST_AUTHENTICATE = 1 << 0,
29 ACQUIRE_PLEASE_SUSPEND = 1 << 1,
2518230d 30 ACQUIRE_REF_ANYWAY = 1 << 2,
563c5511
LP
31} AcquireHomeFlags;
32
26cf9fb7
LP
33static int parse_argv(
34 pam_handle_t *handle,
35 int argc, const char **argv,
563c5511 36 AcquireHomeFlags *flags,
26cf9fb7
LP
37 bool *debug) {
38
26cf9fb7
LP
39 assert(argc >= 0);
40 assert(argc == 0 || argv);
41
2b9e9055 42 for (int i = 0; i < argc; i++) {
26cf9fb7
LP
43 const char *v;
44
f12d19b3 45 if ((v = startswith(argv[i], "suspend="))) {
26cf9fb7
LP
46 int k;
47
48 k = parse_boolean(v);
49 if (k < 0)
80ace4f2 50 pam_syslog(handle, LOG_WARNING, "Failed to parse suspend= argument, ignoring: %s", v);
563c5511
LP
51 else if (flags)
52 SET_FLAG(*flags, ACQUIRE_PLEASE_SUSPEND, k);
26cf9fb7 53
332f38d0
JS
54 } else if (streq(argv[i], "debug")) {
55 if (debug)
56 *debug = true;
57
26cf9fb7
LP
58 } else if ((v = startswith(argv[i], "debug="))) {
59 int k;
26cf9fb7
LP
60 k = parse_boolean(v);
61 if (k < 0)
62 pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring: %s", v);
63 else if (debug)
64 *debug = k;
65
66 } else
67 pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
68 }
69
70 return 0;
71}
72
764ae4dd
LP
73static int parse_env(
74 pam_handle_t *handle,
563c5511 75 AcquireHomeFlags *flags) {
764ae4dd
LP
76
77 const char *v;
78 int r;
79
80 /* Let's read the suspend setting from an env var in addition to the PAM command line. That makes it
81 * easy to declare the features of a display manager in code rather than configuration, and this is
82 * really a feature of code */
83
84 v = pam_getenv(handle, "SYSTEMD_HOME_SUSPEND");
85 if (!v) {
86 /* Also check the process env block, so that people can control this via an env var from the
87 * outside of our process. */
88 v = secure_getenv("SYSTEMD_HOME_SUSPEND");
89 if (!v)
90 return 0;
91 }
92
93 r = parse_boolean(v);
94 if (r < 0)
95 pam_syslog(handle, LOG_WARNING, "Failed to parse $SYSTEMD_HOME_SUSPEND argument, ignoring: %s", v);
563c5511
LP
96 else if (flags)
97 SET_FLAG(*flags, ACQUIRE_PLEASE_SUSPEND, r);
764ae4dd
LP
98
99 return 0;
100}
101
26cf9fb7
LP
102static int acquire_user_record(
103 pam_handle_t *handle,
6c8428bb 104 const char *username,
f71b55b5 105 bool debug,
ba8d00e8
LP
106 UserRecord **ret_record,
107 PamBusData **bus_data) {
26cf9fb7 108
26cf9fb7
LP
109 int r;
110
111 assert(handle);
112
6c8428bb
LP
113 if (!username) {
114 r = pam_get_user(handle, &username, NULL);
e91b05f4
ZJS
115 if (r != PAM_SUCCESS)
116 return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get user name: @PAMERR@");
e91b05f4
ZJS
117 if (isempty(username))
118 return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, "User name not set.");
26cf9fb7
LP
119 }
120
c747c041
LP
121 /* Possibly split out the area name */
122 _cleanup_free_ char *username_without_area = NULL, *area = NULL;
123 const char *carea = strrchr(username, '%');
124 if (carea && (filename_is_valid(carea + 1) || isempty(carea + 1))) {
125 username_without_area = strndup(username, carea - username);
126 if (!username_without_area)
127 return pam_log_oom(handle);
128
129 username = username_without_area;
130 area = strdup(carea + 1);
131 if (!area)
132 return pam_log_oom(handle);
133 }
134
26cf9fb7
LP
135 /* Let's bypass all IPC complexity for the two user names we know for sure we don't manage, and for
136 * user names we don't consider valid. */
7a8867ab 137 if (STR_IN_SET(username, "root", NOBODY_USER_NAME) || !valid_user_group_name(username, 0))
26cf9fb7
LP
138 return PAM_USER_UNKNOWN;
139
a642f9d2
LP
140 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
141 _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
142 _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
143 const char *json = NULL;
144 bool fresh_data;
145
dbe7fff4
LP
146 /* We cache the user record in the PAM context. We use a field name that includes the username, since
147 * clients might change the user name associated with a PAM context underneath us. Notably, 'sudo'
148 * creates a single PAM context and first authenticates it with the user set to the originating user,
149 * then updates the user for the destination user and issues the session stack with the same PAM
150 * context. We thus must be prepared that the user record changes between calls and we keep any
151 * caching separate. */
a642f9d2 152 _cleanup_free_ char *homed_field = strjoin("systemd-home-user-record-", username);
dbe7fff4
LP
153 if (!homed_field)
154 return pam_log_oom(handle);
155
156 /* Let's use the cache, so that we can share it between the session and the authentication hooks */
157 r = pam_get_data(handle, homed_field, (const void**) &json);
e91b05f4
ZJS
158 if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
159 return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM user record data: @PAMERR@");
dbe7fff4
LP
160 if (r == PAM_SUCCESS && json) {
161 /* We determined earlier that this is not a homed user? Then exit early. (We use -1 as
162 * negative cache indicator) */
66032ef4 163 if (json == POINTER_MAX)
dbe7fff4 164 return PAM_USER_UNKNOWN;
a642f9d2
LP
165
166 fresh_data = false;
dbe7fff4 167 } else {
26cf9fb7 168 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
0324e3d0 169 _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
26cf9fb7 170
f4092cb9 171 r = pam_acquire_bus_connection(handle, "pam-systemd-home", debug, &bus, bus_data);
26cf9fb7
LP
172 if (r != PAM_SUCCESS)
173 return r;
174
8a1596aa 175 r = bus_call_method(bus, bus_home_mgr, "GetUserRecordByName", &error, &reply, "s", username);
26cf9fb7 176 if (r < 0) {
73d3ac8e 177 if (bus_error_is_unknown_service(&error)) {
27ccba26
ZJS
178 pam_debug_syslog(handle, debug,
179 "systemd-homed is not available: %s",
180 bus_error_message(&error, r));
26cf9fb7
LP
181 goto user_unknown;
182 }
183
184 if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_HOME)) {
27ccba26
ZJS
185 pam_debug_syslog(handle, debug,
186 "Not a user managed by systemd-homed: %s",
187 bus_error_message(&error, r));
26cf9fb7
LP
188 goto user_unknown;
189 }
190
27ccba26
ZJS
191 pam_syslog(handle, LOG_ERR,
192 "Failed to query user record: %s", bus_error_message(&error, r));
26cf9fb7
LP
193 return PAM_SERVICE_ERR;
194 }
195
196 r = sd_bus_message_read(reply, "sbo", &json, NULL, NULL);
197 if (r < 0)
198 return pam_bus_log_parse_error(handle, r);
199
a642f9d2
LP
200 fresh_data = true;
201 }
202
203 r = sd_json_parse(json, /* flags= */ 0, &v, NULL, NULL);
204 if (r < 0)
205 return pam_syslog_errno(handle, LOG_ERR, r, "Failed to parse JSON user record: %m");
206
207 ur = user_record_new();
208 if (!ur)
209 return pam_log_oom(handle);
210
211 r = user_record_load(ur, v, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_PERMISSIVE);
212 if (r < 0)
213 return pam_syslog_errno(handle, LOG_ERR, r, "Failed to load user record: %m");
214
215 /* Safety check if cached record actually matches what we are looking for */
216 if (!user_record_matches_user_name(ur, username))
217 return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR,
218 "Acquired user record does not match user name.");
219
220 /* Update the 'username' pointer to point to our own record now. The pam_set_item() call below is
221 * going to invalidate the old version after all */
222 username = ur->user_name;
223
224 /* We passed all checks. Let's now make sure the rest of the PAM stack continues with the primary,
225 * normalized name of the user record (i.e. not an alias or so). */
226 r = pam_set_item(handle, PAM_USER, ur->user_name);
227 if (r != PAM_SUCCESS)
228 return pam_syslog_pam_error(handle, LOG_ERR, r,
229 "Failed to update username PAM item to '%s': @PAMERR@", ur->user_name);
230
231 /* Everything seems to be good, let's cache this data now */
232 if (fresh_data) {
dbe7fff4
LP
233 /* First copy: for the homed-specific data field, i.e. where we know the user record is from
234 * homed */
a642f9d2 235 _cleanup_free_ char *json_copy = strdup(json);
26cf9fb7
LP
236 if (!json_copy)
237 return pam_log_oom(handle);
238
dbe7fff4 239 r = pam_set_data(handle, homed_field, json_copy, pam_cleanup_free);
e91b05f4
ZJS
240 if (r != PAM_SUCCESS)
241 return pam_syslog_pam_error(handle, LOG_ERR, r,
242 "Failed to set PAM user record data '%s': @PAMERR@", homed_field);
26cf9fb7 243
dbe7fff4
LP
244 /* Take a second copy: for the generic data field, the one which we share with
245 * pam_systemd. While we insist on only reusing homed records, pam_systemd is fine with homed
246 * and non-homed user records. */
247 json_copy = strdup(json);
248 if (!json_copy)
249 return pam_log_oom(handle);
250
a642f9d2 251 _cleanup_free_ char *generic_field = strjoin("systemd-user-record-", username);
dbe7fff4
LP
252 if (!generic_field)
253 return pam_log_oom(handle);
26cf9fb7 254
dbe7fff4 255 r = pam_set_data(handle, generic_field, json_copy, pam_cleanup_free);
e91b05f4
ZJS
256 if (r != PAM_SUCCESS)
257 return pam_syslog_pam_error(handle, LOG_ERR, r,
1fb53bb5 258 "Failed to set PAM user record data '%s': @PAMERR@", generic_field);
dbe7fff4
LP
259
260 TAKE_PTR(json_copy);
26cf9fb7
LP
261 }
262
c747c041
LP
263 /* Let's store the area we parsed out of the name in an env var, so that pam_systemd later can honour it. */
264 if (area) {
265 r = pam_misc_setenv(handle, "XDG_AREA", area, /* readonly= */ 0);
266 if (r != PAM_SUCCESS)
267 return pam_syslog_pam_error(handle, LOG_ERR, r,
268 "Failed to set environment variable $XDG_AREA to '%s': @PAMERR@", area);
269 }
270
26cf9fb7
LP
271 if (ret_record)
272 *ret_record = TAKE_PTR(ur);
273
274 return PAM_SUCCESS;
275
276user_unknown:
277 /* Cache this, so that we don't check again */
66032ef4 278 r = pam_set_data(handle, homed_field, POINTER_MAX, NULL);
26cf9fb7 279 if (r != PAM_SUCCESS)
e91b05f4
ZJS
280 pam_syslog_pam_error(handle, LOG_ERR, r,
281 "Failed to set PAM user record data '%s' to invalid, ignoring: @PAMERR@",
282 homed_field);
26cf9fb7
LP
283
284 return PAM_USER_UNKNOWN;
285}
286
dbe7fff4
LP
287static int release_user_record(pam_handle_t *handle, const char *username) {
288 _cleanup_free_ char *homed_field = NULL, *generic_field = NULL;
26cf9fb7
LP
289 int r, k;
290
dbe7fff4
LP
291 assert(handle);
292 assert(username);
293
294 homed_field = strjoin("systemd-home-user-record-", username);
295 if (!homed_field)
296 return pam_log_oom(handle);
297
298 r = pam_set_data(handle, homed_field, NULL, NULL);
26cf9fb7 299 if (r != PAM_SUCCESS)
e91b05f4
ZJS
300 pam_syslog_pam_error(handle, LOG_ERR, r,
301 "Failed to release PAM user record data '%s': @PAMERR@", homed_field);
dbe7fff4
LP
302
303 generic_field = strjoin("systemd-user-record-", username);
304 if (!generic_field)
305 return pam_log_oom(handle);
26cf9fb7 306
dbe7fff4 307 k = pam_set_data(handle, generic_field, NULL, NULL);
26cf9fb7 308 if (k != PAM_SUCCESS)
e91b05f4
ZJS
309 pam_syslog_pam_error(handle, LOG_ERR, k,
310 "Failed to release PAM user record data '%s': @PAMERR@", generic_field);
26cf9fb7
LP
311
312 return IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA) ? k : r;
313}
314
315static void cleanup_home_fd(pam_handle_t *handle, void *data, int error_status) {
316 safe_close(PTR_TO_FD(data));
317}
318
319static int handle_generic_user_record_error(
320 pam_handle_t *handle,
321 const char *user_name,
322 UserRecord *secret,
323 int ret,
f71b55b5
DT
324 const sd_bus_error *error,
325 bool debug) {
26cf9fb7 326
3dc8b2df
LP
327 int r;
328
26cf9fb7 329 assert(user_name);
26cf9fb7
LP
330 assert(error);
331
26cf9fb7
LP
332 /* Logs about all errors, except for PAM_CONV_ERR, i.e. when requesting more info failed. */
333
334 if (sd_bus_error_has_name(error, BUS_ERROR_HOME_ABSENT)) {
52e5b950 335 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL,
4e680156 336 _("Home of user %s is currently absent, please plug in the necessary storage device or backing file system."), user_name);
e91b05f4
ZJS
337 return pam_syslog_pam_error(handle, LOG_ERR, PAM_PERM_DENIED,
338 "Failed to acquire home for user %s: %s", user_name, bus_error_message(error, ret));
26cf9fb7
LP
339
340 } else if (sd_bus_error_has_name(error, BUS_ERROR_AUTHENTICATION_LIMIT_HIT)) {
52e5b950 341 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Too frequent login attempts for user %s, try again later."), user_name);
e91b05f4
ZJS
342 return pam_syslog_pam_error(handle, LOG_ERR, PAM_MAXTRIES,
343 "Failed to acquire home for user %s: %s", user_name, bus_error_message(error, ret));
26cf9fb7
LP
344
345 } else if (sd_bus_error_has_name(error, BUS_ERROR_BAD_PASSWORD)) {
346 _cleanup_(erase_and_freep) char *newp = NULL;
347
6a09dbb8
YW
348 assert(secret);
349
26cf9fb7
LP
350 /* This didn't work? Ask for an (additional?) password */
351
352 if (strv_isempty(secret->password))
52e5b950 353 r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Password: "));
d4232943 354 else {
52e5b950
LP
355 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password incorrect or not sufficient for authentication of user %s."), user_name);
356 r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, try again: "));
d4232943 357 }
26cf9fb7
LP
358 if (r != PAM_SUCCESS)
359 return PAM_CONV_ERR; /* no logging here */
360
f71b55b5 361 if (isempty(newp)) {
27ccba26 362 pam_debug_syslog(handle, debug, "Password request aborted.");
f71b55b5
DT
363 return PAM_AUTHTOK_ERR;
364 }
26cf9fb7
LP
365
366 r = user_record_set_password(secret, STRV_MAKE(newp), true);
544ec3c0
ZJS
367 if (r < 0)
368 return pam_syslog_errno(handle, LOG_ERR, r, "Failed to store password: %m");
26cf9fb7 369
edde3a35
LP
370 } else if (sd_bus_error_has_name(error, BUS_ERROR_BAD_RECOVERY_KEY)) {
371 _cleanup_(erase_and_freep) char *newp = NULL;
372
373 assert(secret);
374
375 /* Hmm, homed asks for recovery key (because no regular password is defined maybe)? Provide it. */
376
377 if (strv_isempty(secret->password))
52e5b950 378 r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Recovery key: "));
edde3a35 379 else {
52e5b950
LP
380 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password/recovery key incorrect or not sufficient for authentication of user %s."), user_name);
381 r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, reenter recovery key: "));
edde3a35
LP
382 }
383 if (r != PAM_SUCCESS)
384 return PAM_CONV_ERR; /* no logging here */
385
f71b55b5 386 if (isempty(newp)) {
27ccba26 387 pam_debug_syslog(handle, debug, "Recovery key request aborted.");
f71b55b5
DT
388 return PAM_AUTHTOK_ERR;
389 }
edde3a35
LP
390
391 r = user_record_set_password(secret, STRV_MAKE(newp), true);
544ec3c0
ZJS
392 if (r < 0)
393 return pam_syslog_errno(handle, LOG_ERR, r, "Failed to store recovery key: %m");
edde3a35 394
26cf9fb7
LP
395 } else if (sd_bus_error_has_name(error, BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN)) {
396 _cleanup_(erase_and_freep) char *newp = NULL;
397
6a09dbb8
YW
398 assert(secret);
399
d4232943 400 if (strv_isempty(secret->password)) {
52e5b950
LP
401 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token of user %s not inserted."), user_name);
402 r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Try again with password: "));
d4232943 403 } else {
52e5b950
LP
404 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password incorrect or not sufficient, and configured security token of user %s not inserted."), user_name);
405 r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Try again with password: "));
d4232943 406 }
26cf9fb7
LP
407 if (r != PAM_SUCCESS)
408 return PAM_CONV_ERR; /* no logging here */
409
f71b55b5 410 if (isempty(newp)) {
27ccba26 411 pam_debug_syslog(handle, debug, "Password request aborted.");
f71b55b5
DT
412 return PAM_AUTHTOK_ERR;
413 }
e91b05f4 414
26cf9fb7 415 r = user_record_set_password(secret, STRV_MAKE(newp), true);
544ec3c0
ZJS
416 if (r < 0)
417 return pam_syslog_errno(handle, LOG_ERR, r, "Failed to store password: %m");
26cf9fb7
LP
418
419 } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PIN_NEEDED)) {
420 _cleanup_(erase_and_freep) char *newp = NULL;
421
6a09dbb8
YW
422 assert(secret);
423
52e5b950 424 r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Security token PIN: "));
26cf9fb7
LP
425 if (r != PAM_SUCCESS)
426 return PAM_CONV_ERR; /* no logging here */
427
f71b55b5 428 if (isempty(newp)) {
27ccba26 429 pam_debug_syslog(handle, debug, "PIN request aborted.");
f71b55b5
DT
430 return PAM_AUTHTOK_ERR;
431 }
26cf9fb7 432
c0bde0d2 433 r = user_record_set_token_pin(secret, STRV_MAKE(newp), false);
544ec3c0
ZJS
434 if (r < 0)
435 return pam_syslog_errno(handle, LOG_ERR, r, "Failed to store PIN: %m");
26cf9fb7
LP
436
437 } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED)) {
438
6a09dbb8
YW
439 assert(secret);
440
52e5b950 441 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Please authenticate physically on security token of user %s."), user_name);
26cf9fb7
LP
442
443 r = user_record_set_pkcs11_protected_authentication_path_permitted(secret, true);
544ec3c0
ZJS
444 if (r < 0)
445 return pam_syslog_errno(handle, LOG_ERR, r,
446 "Failed to set PKCS#11 protected authentication path permitted flag: %m");
26cf9fb7 447
7b78db28
LP
448 } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_USER_PRESENCE_NEEDED)) {
449
6a09dbb8
YW
450 assert(secret);
451
52e5b950 452 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Please confirm presence on security token of user %s."), user_name);
7b78db28
LP
453
454 r = user_record_set_fido2_user_presence_permitted(secret, true);
544ec3c0
ZJS
455 if (r < 0)
456 return pam_syslog_errno(handle, LOG_ERR, r,
457 "Failed to set FIDO2 user presence permitted flag: %m");
7b78db28 458
17e7561a
LP
459 } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_USER_VERIFICATION_NEEDED)) {
460
6a09dbb8
YW
461 assert(secret);
462
52e5b950 463 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Please verify user on security token of user %s."), user_name);
17e7561a
LP
464
465 r = user_record_set_fido2_user_verification_permitted(secret, true);
544ec3c0
ZJS
466 if (r < 0)
467 return pam_syslog_errno(handle, LOG_ERR, r,
468 "Failed to set FIDO2 user verification permitted flag: %m");
17e7561a 469
85b12944
LP
470 } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PIN_LOCKED)) {
471
52e5b950 472 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token PIN is locked, please unlock it first. (Hint: Removal and re-insertion might suffice.)"));
85b12944
LP
473 return PAM_SERVICE_ERR;
474
26cf9fb7
LP
475 } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN)) {
476 _cleanup_(erase_and_freep) char *newp = NULL;
477
6a09dbb8
YW
478 assert(secret);
479
52e5b950
LP
480 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token PIN incorrect for user %s."), user_name);
481 r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: "));
26cf9fb7
LP
482 if (r != PAM_SUCCESS)
483 return PAM_CONV_ERR; /* no logging here */
484
f71b55b5 485 if (isempty(newp)) {
27ccba26 486 pam_debug_syslog(handle, debug, "PIN request aborted.");
f71b55b5
DT
487 return PAM_AUTHTOK_ERR;
488 }
26cf9fb7 489
c0bde0d2 490 r = user_record_set_token_pin(secret, STRV_MAKE(newp), false);
544ec3c0
ZJS
491 if (r < 0)
492 return pam_syslog_errno(handle, LOG_ERR, r, "Failed to store PIN: %m");
26cf9fb7
LP
493
494 } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT)) {
495 _cleanup_(erase_and_freep) char *newp = NULL;
496
6a09dbb8
YW
497 assert(secret);
498
52e5b950
LP
499 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token PIN of user %s incorrect (only a few tries left!)"), user_name);
500 r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: "));
26cf9fb7
LP
501 if (r != PAM_SUCCESS)
502 return PAM_CONV_ERR; /* no logging here */
503
f71b55b5 504 if (isempty(newp)) {
27ccba26 505 pam_debug_syslog(handle, debug, "PIN request aborted.");
f71b55b5
DT
506 return PAM_AUTHTOK_ERR;
507 }
26cf9fb7 508
c0bde0d2 509 r = user_record_set_token_pin(secret, STRV_MAKE(newp), false);
544ec3c0
ZJS
510 if (r < 0)
511 return pam_syslog_errno(handle, LOG_ERR, r, "Failed to store PIN: %m");
26cf9fb7
LP
512
513 } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN_ONE_TRY_LEFT)) {
514 _cleanup_(erase_and_freep) char *newp = NULL;
515
6a09dbb8
YW
516 assert(secret);
517
52e5b950
LP
518 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token PIN of user %s incorrect (only one try left!)"), user_name);
519 r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: "));
26cf9fb7
LP
520 if (r != PAM_SUCCESS)
521 return PAM_CONV_ERR; /* no logging here */
522
f71b55b5 523 if (isempty(newp)) {
27ccba26 524 pam_debug_syslog(handle, debug, "PIN request aborted.");
f71b55b5
DT
525 return PAM_AUTHTOK_ERR;
526 }
26cf9fb7 527
c0bde0d2 528 r = user_record_set_token_pin(secret, STRV_MAKE(newp), false);
544ec3c0
ZJS
529 if (r < 0)
530 return pam_syslog_errno(handle, LOG_ERR, r, "Failed to store PIN: %m");
26cf9fb7 531
e91b05f4
ZJS
532 } else
533 return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR,
534 "Failed to acquire home for user %s: %s", user_name, bus_error_message(error, ret));
26cf9fb7
LP
535
536 return PAM_SUCCESS;
537}
538
539static int acquire_home(
540 pam_handle_t *handle,
563c5511 541 AcquireHomeFlags flags,
ba8d00e8
LP
542 bool debug,
543 PamBusData **bus_data) {
26cf9fb7
LP
544
545 _cleanup_(user_record_unrefp) UserRecord *ur = NULL, *secret = NULL;
2518230d 546 bool do_auth = FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE), home_not_active = false, home_locked = false, unrestricted = false;
254d1313 547 _cleanup_close_ int acquired_fd = -EBADF;
6c8428bb 548 _cleanup_free_ char *fd_field = NULL;
26cf9fb7 549 const void *home_fd_ptr = NULL;
6c8428bb 550 const char *username = NULL;
26cf9fb7
LP
551 unsigned n_attempts = 0;
552 int r;
553
554 assert(handle);
555
2518230d 556 /* This acquires a reference to a home directory in the following ways:
26cf9fb7 557 *
5856e869
LP
558 * 1. If ACQUIRE_MUST_AUTHENTICATE is not set, it tries to call RefHome() first — which will get us a
559 * reference to the home without authentication (which will work for homes that are not encrypted,
560 * or that already are activated). If this works, we are done. Yay!
2518230d
LP
561 *
562 * 2. Otherwise, we'll call AcquireHome() — which will try to activate the home getting us a
563 * reference. If this works, we are done. Yay!
564 *
5856e869
LP
565 * 3. if ACQUIRE_REF_ANYWAY is set, we'll call RefHomeUnrestricted() — which will give us a reference
566 * in any case (even if the activation failed!).
2518230d 567 *
5856e869
LP
568 * The idea is that ACQUIRE_MUST_AUTHENTICATE is off for the PAM session hooks (since for those
569 * authentication doesn't matter), and on for the PAM authentication hooks (since for those
570 * authentication is essential). And ACQUIRE_REF_ANYWAY should be set if we are pretty sure that we
571 * can later activate the home directory via our fallback shell logic, and hence are OK if we can't
572 * activate things here. Usecase for that are SSH logins where SSH does the authentication and thus
573 * only the session hooks are called. But from the session hooks SSH doesn't allow asking questions,
574 * hence we simply allow the login attempt to continue but then invoke our fallback shell that will
575 * prompt the user for the missing unlock credentials, and then chainload the real shell.
2518230d 576 */
26cf9fb7 577
6c8428bb 578 r = pam_get_user(handle, &username, NULL);
e91b05f4
ZJS
579 if (r != PAM_SUCCESS)
580 return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get user name: @PAMERR@");
e91b05f4
ZJS
581 if (isempty(username))
582 return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, "User name not set.");
6c8428bb 583
26cf9fb7 584 /* If we already have acquired the fd, let's shortcut this */
6c8428bb
LP
585 fd_field = strjoin("systemd-home-fd-", username);
586 if (!fd_field)
587 return pam_log_oom(handle);
588
589 r = pam_get_data(handle, fd_field, &home_fd_ptr);
e91b05f4
ZJS
590 if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
591 return pam_syslog_pam_error(handle, LOG_ERR, r,
592 "Failed to retrieve PAM home reference fd: @PAMERR@");
da4340fd 593 if (r == PAM_SUCCESS && PTR_TO_FD(home_fd_ptr) >= 0)
26cf9fb7
LP
594 return PAM_SUCCESS;
595
f71b55b5 596 r = acquire_user_record(handle, username, debug, &ur, bus_data);
26cf9fb7
LP
597 if (r != PAM_SUCCESS)
598 return r;
599
600 /* Implement our own retry loop here instead of relying on the PAM client's one. That's because it
2518230d
LP
601 * might happen that the record we stored on the host does not match the encryption password of the
602 * LUKS image in case the image was used in a different system where the password was changed. In
603 * that case it will happen that the LUKS password and the host password are different, and we handle
604 * that by collecting and passing multiple passwords in that case. Hence we treat bad passwords as a
c309b9e9 605 * request to collect one more password and pass the new and all previously used passwords again. */
26cf9fb7 606
0324e3d0 607 _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
f4092cb9 608 r = pam_acquire_bus_connection(handle, "pam-systemd-home", debug, &bus, bus_data);
0324e3d0
YW
609 if (r != PAM_SUCCESS)
610 return r;
611
26cf9fb7
LP
612 for (;;) {
613 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
614 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
2518230d 615 const char *method = NULL;
26cf9fb7
LP
616
617 if (do_auth && !secret) {
618 const char *cached_password = NULL;
619
620 secret = user_record_new();
621 if (!secret)
622 return pam_log_oom(handle);
623
624 /* If there's already a cached password, use it. But if not let's authenticate
625 * without anything, maybe some other authentication mechanism systemd-homed
626 * implements (such as PKCS#11) allows us to authenticate without anything else. */
627 r = pam_get_item(handle, PAM_AUTHTOK, (const void**) &cached_password);
e91b05f4
ZJS
628 if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
629 return pam_syslog_pam_error(handle, LOG_ERR, r,
630 "Failed to get cached password: @PAMERR@");
26cf9fb7
LP
631
632 if (!isempty(cached_password)) {
633 r = user_record_set_password(secret, STRV_MAKE(cached_password), true);
544ec3c0
ZJS
634 if (r < 0)
635 return pam_syslog_errno(handle, LOG_ERR, r, "Failed to store password: %m");
26cf9fb7
LP
636 }
637 }
638
2518230d
LP
639 if (do_auth)
640 method = "AcquireHome"; /* If we shall authenticate no matter what */
641 else if (unrestricted)
642 method = "RefHomeUnrestricted"; /* If we shall get a ref no matter what */
643 else
644 method = "RefHome"; /* If we shall get a ref (if possible) */
645
646 r = bus_message_new_method_call(bus, &m, bus_home_mgr, method);
26cf9fb7
LP
647 if (r < 0)
648 return pam_bus_log_create_error(handle, r);
649
650 r = sd_bus_message_append(m, "s", ur->user_name);
651 if (r < 0)
652 return pam_bus_log_create_error(handle, r);
653
654 if (do_auth) {
655 r = bus_message_append_secret(m, secret);
656 if (r < 0)
657 return pam_bus_log_create_error(handle, r);
658 }
659
563c5511 660 r = sd_bus_message_append(m, "b", FLAGS_SET(flags, ACQUIRE_PLEASE_SUSPEND));
26cf9fb7
LP
661 if (r < 0)
662 return pam_bus_log_create_error(handle, r);
663
664 r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply);
665 if (r < 0) {
5c291113 666 if (sd_bus_error_has_names(&error, BUS_ERROR_HOME_NOT_ACTIVE, BUS_ERROR_HOME_BUSY)) {
26cf9fb7
LP
667 /* Only on RefHome(): We can't access the home directory currently, unless
668 * it's unlocked with a password. Hence, let's try this again, this time with
669 * authentication. */
670 home_not_active = true;
2518230d
LP
671 do_auth = true;
672 } else if (sd_bus_error_has_name(&error, BUS_ERROR_HOME_LOCKED)) {
26cf9fb7 673 home_locked = true; /* Similar */
2518230d
LP
674 do_auth = true;
675 } else {
f71b55b5 676 r = handle_generic_user_record_error(handle, ur->user_name, secret, r, &error, debug);
26cf9fb7
LP
677 if (r == PAM_CONV_ERR) {
678 /* Password/PIN prompts will fail in certain environments, for example when
679 * we are called from OpenSSH's account or session hooks, or in systemd's
680 * per-service PAM logic. In that case, print a friendly message and accept
681 * failure. */
682
2518230d
LP
683 if (!FLAGS_SET(flags, ACQUIRE_REF_ANYWAY)) {
684 if (home_not_active)
685 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Home of user %s is currently not active, please log in locally first."), ur->user_name);
686 if (home_locked)
687 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Home of user %s is currently locked, please unlock locally first."), ur->user_name);
688
689 if (FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE) || debug)
690 pam_syslog(handle, FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE) ? LOG_ERR : LOG_DEBUG, "Failed to prompt for password/prompt.");
26cf9fb7 691
2518230d
LP
692 return home_not_active || home_locked ? PAM_PERM_DENIED : PAM_CONV_ERR;
693 }
26cf9fb7 694
2518230d
LP
695 /* ref_anyway is true, hence let's now get a ref no matter what. */
696 unrestricted = true;
697 do_auth = false;
698 } else if (r != PAM_SUCCESS)
26cf9fb7 699 return r;
2518230d
LP
700 else
701 do_auth = true; /* The issue was dealt with, some more information was collected. Let's try to authenticate, again. */
26cf9fb7 702 }
26cf9fb7
LP
703 } else {
704 int fd;
705
706 r = sd_bus_message_read(reply, "h", &fd);
707 if (r < 0)
708 return pam_bus_log_parse_error(handle, r);
709
710 acquired_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
544ec3c0
ZJS
711 if (acquired_fd < 0)
712 return pam_syslog_errno(handle, LOG_ERR, errno,
713 "Failed to duplicate acquired fd: %m");
26cf9fb7
LP
714 break;
715 }
716
717 if (++n_attempts >= 5) {
52e5b950 718 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL,
4e680156 719 _("Too many unsuccessful login attempts for user %s, refusing."), ur->user_name);
e91b05f4
ZJS
720 return pam_syslog_pam_error(handle, LOG_ERR, PAM_MAXTRIES,
721 "Failed to acquire home for user %s: %s", ur->user_name, bus_error_message(&error, r));
26cf9fb7 722 }
26cf9fb7
LP
723 }
724
d2e545f8 725 /* Later PAM modules may need the auth token, but only during pam_authenticate. */
563c5511 726 if (FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE) && !strv_isempty(secret->password)) {
d2e545f8 727 r = pam_set_item(handle, PAM_AUTHTOK, *secret->password);
e91b05f4
ZJS
728 if (r != PAM_SUCCESS)
729 return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set PAM auth token: @PAMERR@");
d2e545f8
CR
730 }
731
6c8428bb 732 r = pam_set_data(handle, fd_field, FD_TO_PTR(acquired_fd), cleanup_home_fd);
e91b05f4
ZJS
733 if (r != PAM_SUCCESS)
734 return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set PAM bus data: @PAMERR@");
26cf9fb7
LP
735 TAKE_FD(acquired_fd);
736
737 if (do_auth) {
738 /* We likely just activated the home directory, let's flush out the user record, since a
739 * newer embedded user record might have been acquired from the activation. */
740
dbe7fff4 741 r = release_user_record(handle, ur->user_name);
26cf9fb7
LP
742 if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
743 return r;
744 }
745
2518230d
LP
746 /* If we didn't actually manage to unlock the home directory, then we rely on the fallback-shell to
747 * unlock it for us. But until that happens we don't want that logind spawns the per-user service
748 * manager for us (since it would see an inaccessible home directory). Hence set an environment
749 * variable that pam_systemd looks for). */
750 if (unrestricted) {
751 r = pam_putenv(handle, "XDG_SESSION_INCOMPLETE=1");
752 if (r != PAM_SUCCESS)
753 return pam_syslog_pam_error(handle, LOG_WARNING, r, "Failed to set XDG_SESSION_INCOMPLETE= environment variable: @PAMERR@");
754
755 pam_syslog(handle, LOG_NOTICE, "Home for user %s acquired in incomplete mode, requires later activation.", ur->user_name);
756 } else
757 pam_syslog(handle, LOG_NOTICE, "Home for user %s successfully acquired.", ur->user_name);
758
26cf9fb7
LP
759 return PAM_SUCCESS;
760}
761
6c8428bb
LP
762static int release_home_fd(pam_handle_t *handle, const char *username) {
763 _cleanup_free_ char *fd_field = NULL;
26cf9fb7
LP
764 const void *home_fd_ptr = NULL;
765 int r;
766
6c8428bb
LP
767 assert(handle);
768 assert(username);
769
770 fd_field = strjoin("systemd-home-fd-", username);
771 if (!fd_field)
772 return pam_log_oom(handle);
773
774 r = pam_get_data(handle, fd_field, &home_fd_ptr);
775 if (r == PAM_NO_MODULE_DATA || (r == PAM_SUCCESS && PTR_TO_FD(home_fd_ptr) < 0))
26cf9fb7 776 return PAM_NO_MODULE_DATA;
e91b05f4
ZJS
777 if (r != PAM_SUCCESS)
778 return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to retrieve PAM home reference fd: @PAMERR@");
26cf9fb7 779
6c8428bb 780 r = pam_set_data(handle, fd_field, NULL, NULL);
26cf9fb7 781 if (r != PAM_SUCCESS)
e91b05f4 782 return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to release PAM home reference fd: @PAMERR@");
26cf9fb7 783
e91b05f4 784 return PAM_SUCCESS;
26cf9fb7
LP
785}
786
787_public_ PAM_EXTERN int pam_sm_authenticate(
788 pam_handle_t *handle,
563c5511 789 int sm_flags,
26cf9fb7
LP
790 int argc, const char **argv) {
791
563c5511
LP
792 AcquireHomeFlags flags = 0;
793 bool debug = false;
26cf9fb7 794
4eae58b3
DDM
795 pam_log_setup();
796
563c5511 797 if (parse_env(handle, &flags) < 0)
764ae4dd
LP
798 return PAM_AUTH_ERR;
799
26cf9fb7
LP
800 if (parse_argv(handle,
801 argc, argv,
563c5511 802 &flags,
26cf9fb7
LP
803 &debug) < 0)
804 return PAM_AUTH_ERR;
805
27ccba26 806 pam_debug_syslog(handle, debug, "pam-systemd-homed authenticating");
26cf9fb7 807
563c5511 808 return acquire_home(handle, ACQUIRE_MUST_AUTHENTICATE|flags, debug, /* bus_data= */ NULL);
26cf9fb7
LP
809}
810
563c5511 811_public_ PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int sm_flags, int argc, const char **argv) {
26cf9fb7
LP
812 return PAM_SUCCESS;
813}
814
2518230d
LP
815static int fallback_shell_can_work(
816 pam_handle_t *handle,
817 AcquireHomeFlags *flags) {
818
819 const char *tty = NULL, *display = NULL;
820 int r;
821
822 assert(handle);
823 assert(flags);
824
825 r = pam_get_item_many(
826 handle,
827 PAM_TTY, &tty,
828 PAM_XDISPLAY, &display);
829 if (r != PAM_SUCCESS)
830 return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM items: @PAMERR@");
831
832 /* The fallback shell logic only works on TTY logins, hence only allow it if there's no X11 display
833 * set, and a TTY field is set that is neither "cron" (which is what crond sets, god knows why) not
834 * contains a colon (which is what various graphical X11 logins do). Note that ssh sets the tty to
835 * "ssh" here, which we allow (I mean, ssh is after all the primary reason we do all this). */
836 if (isempty(display) &&
837 tty &&
838 !strchr(tty, ':') &&
839 !streq(tty, "cron"))
840 *flags |= ACQUIRE_REF_ANYWAY; /* Allow login even if we can only ref, not activate */
841
842 return PAM_SUCCESS;
843}
844
26cf9fb7
LP
845_public_ PAM_EXTERN int pam_sm_open_session(
846 pam_handle_t *handle,
563c5511 847 int sm_flags,
26cf9fb7
LP
848 int argc, const char **argv) {
849
ba8d00e8
LP
850 /* Let's release the D-Bus connection once this function exits, after all the session might live
851 * quite a long time, and we are not going to process the bus connection in that time, so let's
852 * better close before the daemon kicks us off because we are not processing anything. */
853 _cleanup_(pam_bus_data_disconnectp) PamBusData *d = NULL;
563c5511
LP
854 AcquireHomeFlags flags = 0;
855 bool debug = false;
26cf9fb7
LP
856 int r;
857
4eae58b3
DDM
858 pam_log_setup();
859
563c5511 860 if (parse_env(handle, &flags) < 0)
764ae4dd
LP
861 return PAM_SESSION_ERR;
862
26cf9fb7
LP
863 if (parse_argv(handle,
864 argc, argv,
563c5511 865 &flags,
26cf9fb7
LP
866 &debug) < 0)
867 return PAM_SESSION_ERR;
868
27ccba26 869 pam_debug_syslog(handle, debug, "pam-systemd-homed session start");
26cf9fb7 870
2518230d
LP
871 r = fallback_shell_can_work(handle, &flags);
872 if (r != PAM_SUCCESS)
873 return r;
874
a6a6197e
YW
875 /* Explicitly get saved PamBusData here. Otherwise, this function may succeed without setting 'd'
876 * even if there is an opened sd-bus connection, and it will be leaked. See issue #31375. */
877 r = pam_get_bus_data(handle, "pam-systemd-home", &d);
878 if (r != PAM_SUCCESS)
879 return r;
880
563c5511 881 r = acquire_home(handle, flags, debug, &d);
26cf9fb7 882 if (r == PAM_USER_UNKNOWN) /* Not managed by us? Don't complain. */
ba8d00e8 883 return PAM_SUCCESS;
26cf9fb7
LP
884 if (r != PAM_SUCCESS)
885 return r;
886
887 r = pam_putenv(handle, "SYSTEMD_HOME=1");
e91b05f4
ZJS
888 if (r != PAM_SUCCESS)
889 return pam_syslog_pam_error(handle, LOG_ERR, r,
890 "Failed to set PAM environment variable $SYSTEMD_HOME: @PAMERR@");
26cf9fb7 891
563c5511 892 r = pam_putenv(handle, FLAGS_SET(flags, ACQUIRE_PLEASE_SUSPEND) ? "SYSTEMD_HOME_SUSPEND=1" : "SYSTEMD_HOME_SUSPEND=0");
e91b05f4
ZJS
893 if (r != PAM_SUCCESS)
894 return pam_syslog_pam_error(handle, LOG_ERR, r,
895 "Failed to set PAM environment variable $SYSTEMD_HOME_SUSPEND: @PAMERR@");
764ae4dd 896
26cf9fb7
LP
897 return PAM_SUCCESS;
898}
899
900_public_ PAM_EXTERN int pam_sm_close_session(
901 pam_handle_t *handle,
563c5511 902 int sm_flags,
26cf9fb7
LP
903 int argc, const char **argv) {
904
905 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
906 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
26cf9fb7
LP
907 const char *username = NULL;
908 bool debug = false;
909 int r;
910
4eae58b3
DDM
911 pam_log_setup();
912
26cf9fb7
LP
913 if (parse_argv(handle,
914 argc, argv,
915 NULL,
916 &debug) < 0)
917 return PAM_SESSION_ERR;
918
27ccba26 919 pam_debug_syslog(handle, debug, "pam-systemd-homed session end");
26cf9fb7 920
6c8428bb 921 r = pam_get_user(handle, &username, NULL);
e91b05f4
ZJS
922 if (r != PAM_SUCCESS)
923 return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get user name: @PAMERR@");
e91b05f4
ZJS
924 if (isempty(username))
925 return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, "User name not set.");
6c8428bb 926
26cf9fb7
LP
927 /* Let's explicitly drop the reference to the homed session, so that the subsequent ReleaseHome()
928 * call will be able to do its thing. */
6c8428bb 929 r = release_home_fd(handle, username);
26cf9fb7
LP
930 if (r == PAM_NO_MODULE_DATA) /* Nothing to do, we never acquired an fd */
931 return PAM_SUCCESS;
932 if (r != PAM_SUCCESS)
933 return r;
934
0324e3d0 935 _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
f4092cb9 936 r = pam_acquire_bus_connection(handle, "pam-systemd-home", debug, &bus, NULL);
26cf9fb7
LP
937 if (r != PAM_SUCCESS)
938 return r;
939
8a1596aa 940 r = bus_message_new_method_call(bus, &m, bus_home_mgr, "ReleaseHome");
26cf9fb7
LP
941 if (r < 0)
942 return pam_bus_log_create_error(handle, r);
943
944 r = sd_bus_message_append(m, "s", username);
945 if (r < 0)
946 return pam_bus_log_create_error(handle, r);
947
948 r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
949 if (r < 0) {
27ccba26 950 if (!sd_bus_error_has_name(&error, BUS_ERROR_HOME_BUSY))
e91b05f4
ZJS
951 return pam_syslog_pam_error(handle, LOG_ERR, PAM_SESSION_ERR,
952 "Failed to release user home: %s", bus_error_message(&error, r));
27ccba26
ZJS
953
954 pam_syslog(handle, LOG_NOTICE, "Not deactivating home directory of %s, as it is still used.", username);
26cf9fb7
LP
955 }
956
957 return PAM_SUCCESS;
958}
959
960_public_ PAM_EXTERN int pam_sm_acct_mgmt(
961 pam_handle_t *handle,
563c5511 962 int sm_flags,
26cf9fb7
LP
963 int argc,
964 const char **argv) {
965
966 _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
563c5511
LP
967 AcquireHomeFlags flags = 0;
968 bool debug = false;
26cf9fb7
LP
969 usec_t t;
970 int r;
971
4eae58b3
DDM
972 pam_log_setup();
973
563c5511 974 if (parse_env(handle, &flags) < 0)
764ae4dd
LP
975 return PAM_AUTH_ERR;
976
26cf9fb7
LP
977 if (parse_argv(handle,
978 argc, argv,
563c5511 979 &flags,
26cf9fb7
LP
980 &debug) < 0)
981 return PAM_AUTH_ERR;
982
27ccba26 983 pam_debug_syslog(handle, debug, "pam-systemd-homed account management");
26cf9fb7 984
2518230d
LP
985 r = fallback_shell_can_work(handle, &flags);
986 if (r != PAM_SUCCESS)
987 return r;
988
989 r = acquire_home(handle, flags, debug, /* bus_data= */ NULL);
26cf9fb7
LP
990 if (r != PAM_SUCCESS)
991 return r;
992
30de5691 993 r = acquire_user_record(handle, /* username= */ NULL, debug, &ur, /* bus_data= */ NULL);
26cf9fb7
LP
994 if (r != PAM_SUCCESS)
995 return r;
996
997 r = user_record_test_blocked(ur);
998 switch (r) {
999
1000 case -ESTALE:
51a95db6
LP
1001 pam_syslog(handle, LOG_WARNING, "User record for '%s' is newer than current system time, assuming incorrect system clock, allowing access.", ur->user_name);
1002 break;
26cf9fb7
LP
1003
1004 case -ENOLCK:
52e5b950 1005 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record is blocked, prohibiting access."));
26cf9fb7
LP
1006 return PAM_ACCT_EXPIRED;
1007
1008 case -EL2HLT:
52e5b950 1009 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record is not valid yet, prohibiting access."));
26cf9fb7
LP
1010 return PAM_ACCT_EXPIRED;
1011
1012 case -EL3HLT:
52e5b950 1013 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record is not valid anymore, prohibiting access."));
26cf9fb7
LP
1014 return PAM_ACCT_EXPIRED;
1015
1016 default:
1017 if (r < 0) {
52e5b950 1018 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record not valid, prohibiting access."));
26cf9fb7
LP
1019 return PAM_ACCT_EXPIRED;
1020 }
26cf9fb7
LP
1021 }
1022
1023 t = user_record_ratelimit_next_try(ur);
1024 if (t != USEC_INFINITY) {
1025 usec_t n = now(CLOCK_REALTIME);
1026
1027 if (t > n) {
52e5b950 1028 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Too many logins, try again in %s."),
5291f26d 1029 FORMAT_TIMESPAN(t - n, USEC_PER_SEC));
26cf9fb7
LP
1030
1031 return PAM_MAXTRIES;
1032 }
1033 }
1034
1035 r = user_record_test_password_change_required(ur);
1036 switch (r) {
1037
1038 case -EKEYREVOKED:
52e5b950 1039 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password change required."));
26cf9fb7
LP
1040 return PAM_NEW_AUTHTOK_REQD;
1041
1042 case -EOWNERDEAD:
52e5b950 1043 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password expired, change required."));
26cf9fb7
LP
1044 return PAM_NEW_AUTHTOK_REQD;
1045
4e680156
LB
1046 /* Strictly speaking this is only about password expiration, and we might want to allow
1047 * authentication via PKCS#11 or so, but let's ignore this fine distinction for now. */
26cf9fb7 1048 case -EKEYREJECTED:
52e5b950 1049 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password is expired, but can't change, refusing login."));
26cf9fb7
LP
1050 return PAM_AUTHTOK_EXPIRED;
1051
1052 case -EKEYEXPIRED:
52e5b950 1053 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password will expire soon, please change."));
26cf9fb7 1054 break;
3e0b5486
LP
1055
1056 case -ESTALE:
1057 /* If the system clock is wrong, let's log but continue */
1058 pam_syslog(handle, LOG_WARNING, "Couldn't check if password change is required, last change is in the future, system clock likely wrong.");
1059 break;
26cf9fb7
LP
1060
1061 case -EROFS:
1062 /* All good, just means the password if we wanted to change we couldn't, but we don't need to */
1063 break;
1064
1065 default:
1066 if (r < 0) {
52e5b950 1067 (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record not valid, prohibiting access."));
26cf9fb7
LP
1068 return PAM_AUTHTOK_EXPIRED;
1069 }
26cf9fb7
LP
1070 }
1071
1072 return PAM_SUCCESS;
1073}
1074
1075_public_ PAM_EXTERN int pam_sm_chauthtok(
1076 pam_handle_t *handle,
563c5511 1077 int sm_flags,
26cf9fb7
LP
1078 int argc,
1079 const char **argv) {
1080
1081 _cleanup_(user_record_unrefp) UserRecord *ur = NULL, *old_secret = NULL, *new_secret = NULL;
1082 const char *old_password = NULL, *new_password = NULL;
26cf9fb7
LP
1083 unsigned n_attempts = 0;
1084 bool debug = false;
1085 int r;
1086
4eae58b3
DDM
1087 pam_log_setup();
1088
26cf9fb7
LP
1089 if (parse_argv(handle,
1090 argc, argv,
1091 NULL,
1092 &debug) < 0)
1093 return PAM_AUTH_ERR;
1094
27ccba26 1095 pam_debug_syslog(handle, debug, "pam-systemd-homed account management");
26cf9fb7 1096
30de5691 1097 r = acquire_user_record(handle, /* username= */ NULL, debug, &ur, /* bus_data= */ NULL);
26cf9fb7
LP
1098 if (r != PAM_SUCCESS)
1099 return r;
1100
1101 /* Start with cached credentials */
e1ccf6b2
LP
1102 r = pam_get_item_many(
1103 handle,
1104 PAM_OLDAUTHTOK, &old_password,
1105 PAM_AUTHTOK, &new_password);
1106 if (r != PAM_SUCCESS)
1107 return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get cached passwords: @PAMERR@");
26cf9fb7
LP
1108
1109 if (isempty(new_password)) {
1110 /* No, it's not cached, then let's ask for the password and its verification, and cache
1111 * it. */
1112
1113 r = pam_get_authtok_noverify(handle, &new_password, "New password: ");
e91b05f4
ZJS
1114 if (r != PAM_SUCCESS)
1115 return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get new password: @PAMERR@");
1116
f71b55b5 1117 if (isempty(new_password)) {
27ccba26 1118 pam_debug_syslog(handle, debug, "Password request aborted.");
f71b55b5
DT
1119 return PAM_AUTHTOK_ERR;
1120 }
26cf9fb7
LP
1121
1122 r = pam_get_authtok_verify(handle, &new_password, "new password: "); /* Lower case, since PAM prefixes 'Repeat' */
e91b05f4
ZJS
1123 if (r != PAM_SUCCESS)
1124 return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get password again: @PAMERR@");
26cf9fb7
LP
1125
1126 // FIXME: pam_pwquality will ask for the password a third time. It really shouldn't do
1127 // that, and instead assume the password was already verified once when it is found to be
1128 // cached already. needs to be fixed in pam_pwquality
1129 }
1130
1131 /* Now everything is cached and checked, let's exit from the preliminary check */
563c5511 1132 if (FLAGS_SET(sm_flags, PAM_PRELIM_CHECK))
26cf9fb7
LP
1133 return PAM_SUCCESS;
1134
26cf9fb7
LP
1135 old_secret = user_record_new();
1136 if (!old_secret)
1137 return pam_log_oom(handle);
1138
1139 if (!isempty(old_password)) {
1140 r = user_record_set_password(old_secret, STRV_MAKE(old_password), true);
544ec3c0
ZJS
1141 if (r < 0)
1142 return pam_syslog_errno(handle, LOG_ERR, r, "Failed to store old password: %m");
26cf9fb7
LP
1143 }
1144
1145 new_secret = user_record_new();
1146 if (!new_secret)
1147 return pam_log_oom(handle);
1148
1149 r = user_record_set_password(new_secret, STRV_MAKE(new_password), true);
544ec3c0
ZJS
1150 if (r < 0)
1151 return pam_syslog_errno(handle, LOG_ERR, r, "Failed to store new password: %m");
26cf9fb7 1152
0324e3d0 1153 _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
f4092cb9 1154 r = pam_acquire_bus_connection(handle, "pam-systemd-home", debug, &bus, NULL);
0324e3d0
YW
1155 if (r != PAM_SUCCESS)
1156 return r;
1157
26cf9fb7
LP
1158 for (;;) {
1159 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1160 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
1161
8a1596aa 1162 r = bus_message_new_method_call(bus, &m, bus_home_mgr, "ChangePasswordHome");
26cf9fb7
LP
1163 if (r < 0)
1164 return pam_bus_log_create_error(handle, r);
1165
1166 r = sd_bus_message_append(m, "s", ur->user_name);
1167 if (r < 0)
1168 return pam_bus_log_create_error(handle, r);
1169
1170 r = bus_message_append_secret(m, new_secret);
1171 if (r < 0)
1172 return pam_bus_log_create_error(handle, r);
1173
1174 r = bus_message_append_secret(m, old_secret);
1175 if (r < 0)
1176 return pam_bus_log_create_error(handle, r);
1177
1178 r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
1179 if (r < 0) {
f71b55b5 1180 r = handle_generic_user_record_error(handle, ur->user_name, old_secret, r, &error, debug);
e91b05f4
ZJS
1181 if (r == PAM_CONV_ERR)
1182 return pam_syslog_pam_error(handle, LOG_ERR, r,
1183 "Failed to prompt for password/prompt.");
26cf9fb7
LP
1184 if (r != PAM_SUCCESS)
1185 return r;
e91b05f4
ZJS
1186 } else
1187 return pam_syslog_pam_error(handle, LOG_NOTICE, PAM_SUCCESS,
1188 "Successfully changed password for user %s.", ur->user_name);
26cf9fb7
LP
1189
1190 if (++n_attempts >= 5)
1191 break;
1192
1193 /* Try again */
1194 };
1195
e91b05f4
ZJS
1196 return pam_syslog_pam_error(handle, LOG_NOTICE, PAM_MAXTRIES,
1197 "Failed to change password for user %s: @PAMERR@", ur->user_name);
26cf9fb7 1198}