summaryrefslogtreecommitdiffstats
path: root/src/home/pam_systemd_home.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/home/pam_systemd_home.c')
-rw-r--r--src/home/pam_systemd_home.c333
1 files changed, 215 insertions, 118 deletions
diff --git a/src/home/pam_systemd_home.c b/src/home/pam_systemd_home.c
index ba8d8f6..4616f08 100644
--- a/src/home/pam_systemd_home.c
+++ b/src/home/pam_systemd_home.c
@@ -20,10 +20,16 @@
#include "user-record.h"
#include "user-util.h"
+typedef enum AcquireHomeFlags {
+ ACQUIRE_MUST_AUTHENTICATE = 1 << 0,
+ ACQUIRE_PLEASE_SUSPEND = 1 << 1,
+ ACQUIRE_REF_ANYWAY = 1 << 2,
+} AcquireHomeFlags;
+
static int parse_argv(
pam_handle_t *handle,
int argc, const char **argv,
- bool *please_suspend,
+ AcquireHomeFlags *flags,
bool *debug) {
assert(argc >= 0);
@@ -38,8 +44,8 @@ static int parse_argv(
k = parse_boolean(v);
if (k < 0)
pam_syslog(handle, LOG_WARNING, "Failed to parse suspend= argument, ignoring: %s", v);
- else if (please_suspend)
- *please_suspend = k;
+ else if (flags)
+ SET_FLAG(*flags, ACQUIRE_PLEASE_SUSPEND, k);
} else if (streq(argv[i], "debug")) {
if (debug)
@@ -62,7 +68,7 @@ static int parse_argv(
static int parse_env(
pam_handle_t *handle,
- bool *please_suspend) {
+ AcquireHomeFlags *flags) {
const char *v;
int r;
@@ -83,8 +89,8 @@ static int parse_env(
r = parse_boolean(v);
if (r < 0)
pam_syslog(handle, LOG_WARNING, "Failed to parse $SYSTEMD_HOME_SUSPEND argument, ignoring: %s", v);
- else if (please_suspend)
- *please_suspend = r;
+ else if (flags)
+ SET_FLAG(*flags, ACQUIRE_PLEASE_SUSPEND, r);
return 0;
}
@@ -99,7 +105,6 @@ static int acquire_user_record(
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
- _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
_cleanup_free_ char *homed_field = NULL;
const char *json = NULL;
int r;
@@ -142,6 +147,7 @@ static int acquire_user_record(
} else {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *generic_field = NULL, *json_copy = NULL;
+ _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus, bus_data);
if (r != PAM_SUCCESS)
@@ -275,21 +281,21 @@ static int handle_generic_user_record_error(
const sd_bus_error *error,
bool debug) {
+ int r;
+
assert(user_name);
assert(error);
- int r;
-
/* Logs about all errors, except for PAM_CONV_ERR, i.e. when requesting more info failed. */
if (sd_bus_error_has_name(error, BUS_ERROR_HOME_ABSENT)) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL,
+ (void) pam_prompt_graceful(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);
return pam_syslog_pam_error(handle, LOG_ERR, PAM_PERM_DENIED,
"Failed to acquire home for user %s: %s", user_name, bus_error_message(error, ret));
} else if (sd_bus_error_has_name(error, BUS_ERROR_AUTHENTICATION_LIMIT_HIT)) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Too frequent login attempts for user %s, try again later."), user_name);
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Too frequent login attempts for user %s, try again later."), user_name);
return pam_syslog_pam_error(handle, LOG_ERR, PAM_MAXTRIES,
"Failed to acquire home for user %s: %s", user_name, bus_error_message(error, ret));
@@ -301,10 +307,10 @@ static int handle_generic_user_record_error(
/* This didn't work? Ask for an (additional?) password */
if (strv_isempty(secret->password))
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Password: "));
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Password: "));
else {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password incorrect or not sufficient for authentication of user %s."), user_name);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, try again: "));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password incorrect or not sufficient for authentication of user %s."), user_name);
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, try again: "));
}
if (r != PAM_SUCCESS)
return PAM_CONV_ERR; /* no logging here */
@@ -326,10 +332,10 @@ static int handle_generic_user_record_error(
/* Hmm, homed asks for recovery key (because no regular password is defined maybe)? Provide it. */
if (strv_isempty(secret->password))
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Recovery key: "));
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Recovery key: "));
else {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password/recovery key incorrect or not sufficient for authentication of user %s."), user_name);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, reenter recovery key: "));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password/recovery key incorrect or not sufficient for authentication of user %s."), user_name);
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, reenter recovery key: "));
}
if (r != PAM_SUCCESS)
return PAM_CONV_ERR; /* no logging here */
@@ -349,11 +355,11 @@ static int handle_generic_user_record_error(
assert(secret);
if (strv_isempty(secret->password)) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token of user %s not inserted."), user_name);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Try again with password: "));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token of user %s not inserted."), user_name);
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Try again with password: "));
} else {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password incorrect or not sufficient, and configured security token of user %s not inserted."), user_name);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Try again with password: "));
+ (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);
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Try again with password: "));
}
if (r != PAM_SUCCESS)
return PAM_CONV_ERR; /* no logging here */
@@ -363,7 +369,6 @@ static int handle_generic_user_record_error(
return PAM_AUTHTOK_ERR;
}
-
r = user_record_set_password(secret, STRV_MAKE(newp), true);
if (r < 0)
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to store password: %m");
@@ -373,7 +378,7 @@ static int handle_generic_user_record_error(
assert(secret);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Security token PIN: "));
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Security token PIN: "));
if (r != PAM_SUCCESS)
return PAM_CONV_ERR; /* no logging here */
@@ -390,7 +395,7 @@ static int handle_generic_user_record_error(
assert(secret);
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Please authenticate physically on security token of user %s."), user_name);
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Please authenticate physically on security token of user %s."), user_name);
r = user_record_set_pkcs11_protected_authentication_path_permitted(secret, true);
if (r < 0)
@@ -401,7 +406,7 @@ static int handle_generic_user_record_error(
assert(secret);
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Please confirm presence on security token of user %s."), user_name);
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Please confirm presence on security token of user %s."), user_name);
r = user_record_set_fido2_user_presence_permitted(secret, true);
if (r < 0)
@@ -412,7 +417,7 @@ static int handle_generic_user_record_error(
assert(secret);
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Please verify user on security token of user %s."), user_name);
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Please verify user on security token of user %s."), user_name);
r = user_record_set_fido2_user_verification_permitted(secret, true);
if (r < 0)
@@ -421,7 +426,7 @@ static int handle_generic_user_record_error(
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PIN_LOCKED)) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token PIN is locked, please unlock it first. (Hint: Removal and re-insertion might suffice.)"));
+ (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.)"));
return PAM_SERVICE_ERR;
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN)) {
@@ -429,8 +434,8 @@ static int handle_generic_user_record_error(
assert(secret);
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token PIN incorrect for user %s."), user_name);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: "));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token PIN incorrect for user %s."), user_name);
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: "));
if (r != PAM_SUCCESS)
return PAM_CONV_ERR; /* no logging here */
@@ -448,8 +453,8 @@ static int handle_generic_user_record_error(
assert(secret);
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token PIN of user %s incorrect (only a few tries left!)"), user_name);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: "));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token PIN of user %s incorrect (only a few tries left!)"), user_name);
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: "));
if (r != PAM_SUCCESS)
return PAM_CONV_ERR; /* no logging here */
@@ -467,8 +472,8 @@ static int handle_generic_user_record_error(
assert(secret);
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token PIN of user %s incorrect (only one try left!)"), user_name);
- r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: "));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token PIN of user %s incorrect (only one try left!)"), user_name);
+ r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: "));
if (r != PAM_SUCCESS)
return PAM_CONV_ERR; /* no logging here */
@@ -490,14 +495,12 @@ static int handle_generic_user_record_error(
static int acquire_home(
pam_handle_t *handle,
- bool please_authenticate,
- bool please_suspend,
+ AcquireHomeFlags flags,
bool debug,
PamBusData **bus_data) {
_cleanup_(user_record_unrefp) UserRecord *ur = NULL, *secret = NULL;
- bool do_auth = please_authenticate, home_not_active = false, home_locked = false;
- _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
+ bool do_auth = FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE), home_not_active = false, home_locked = false, unrestricted = false;
_cleanup_close_ int acquired_fd = -EBADF;
_cleanup_free_ char *fd_field = NULL;
const void *home_fd_ptr = NULL;
@@ -507,13 +510,27 @@ static int acquire_home(
assert(handle);
- /* This acquires a reference to a home directory in one of two ways: if please_authenticate is true,
- * then we'll call AcquireHome() after asking the user for a password. Otherwise it tries to call
- * RefHome() and if that fails queries the user for a password and uses AcquireHome().
+ /* This acquires a reference to a home directory in the following ways:
+ *
+ * 1. If please_authenticate is false, it tries to call RefHome() first — which
+ * will get us a reference to the home without authentication (which will work for homes that are
+ * not encrypted, or that already are activated). If this works, we are done. Yay!
*
- * The idea is that the PAM authentication hook sets please_authenticate and thus always
- * authenticates, while the other PAM hooks unset it so that they can a ref of their own without
- * authentication if possible, but with authentication if necessary. */
+ * 2. Otherwise, we'll call AcquireHome() — which will try to activate the home getting us a
+ * reference. If this works, we are done. Yay!
+ *
+ * 3. if ref_anyway, we'll call RefHomeUnrestricted() — which will give us a reference in any case
+ * (even if the activation failed!).
+ *
+ * The idea is that please_authenticate is set to false for the PAM session hooks (since for those
+ * authentication doesn't matter), and true for the PAM authentication hooks (since for those
+ * authentication is essential). And ref_anyway should be set if we are pretty sure that we can later
+ * activate the home directory via our fallback shell logic, and hence are OK if we can't activate
+ * things here. Usecase for that are SSH logins where SSH does the authentication and thus only the
+ * session hooks are called. But from the session hooks SSH doesn't allow asking questions, hence we
+ * simply allow the login attempt to continue but then invoke our fallback shell that will prompt the
+ * user for the missing unlock credentials, and then chainload the real shell.
+ */
r = pam_get_user(handle, &username, NULL);
if (r != PAM_SUCCESS)
@@ -534,25 +551,26 @@ static int acquire_home(
if (r == PAM_SUCCESS && PTR_TO_FD(home_fd_ptr) >= 0)
return PAM_SUCCESS;
- r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus, bus_data);
- if (r != PAM_SUCCESS)
- return r;
-
r = acquire_user_record(handle, username, debug, &ur, bus_data);
if (r != PAM_SUCCESS)
return r;
/* Implement our own retry loop here instead of relying on the PAM client's one. That's because it
- * might happen that the record we stored on the host does not match the encryption password of
- * the LUKS image in case the image was used in a different system where the password was
- * changed. In that case it will happen that the LUKS password and the host password are
- * different, and we handle that by collecting and passing multiple passwords in that case. Hence we
- * treat bad passwords as a request to collect one more password and pass the new all all previously
- * used passwords again. */
+ * might happen that the record we stored on the host does not match the encryption password of the
+ * LUKS image in case the image was used in a different system where the password was changed. In
+ * that case it will happen that the LUKS password and the host password are different, and we handle
+ * that by collecting and passing multiple passwords in that case. Hence we treat bad passwords as a
+ * request to collect one more password and pass the new and all previously used passwords again. */
+
+ _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
+ r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus, bus_data);
+ if (r != PAM_SUCCESS)
+ return r;
for (;;) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ const char *method = NULL;
if (do_auth && !secret) {
const char *cached_password = NULL;
@@ -576,7 +594,14 @@ static int acquire_home(
}
}
- r = bus_message_new_method_call(bus, &m, bus_home_mgr, do_auth ? "AcquireHome" : "RefHome");
+ if (do_auth)
+ method = "AcquireHome"; /* If we shall authenticate no matter what */
+ else if (unrestricted)
+ method = "RefHomeUnrestricted"; /* If we shall get a ref no matter what */
+ else
+ method = "RefHome"; /* If we shall get a ref (if possible) */
+
+ r = bus_message_new_method_call(bus, &m, bus_home_mgr, method);
if (r < 0)
return pam_bus_log_create_error(handle, r);
@@ -590,21 +615,22 @@ static int acquire_home(
return pam_bus_log_create_error(handle, r);
}
- r = sd_bus_message_append(m, "b", please_suspend);
+ r = sd_bus_message_append(m, "b", FLAGS_SET(flags, ACQUIRE_PLEASE_SUSPEND));
if (r < 0)
return pam_bus_log_create_error(handle, r);
r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply);
if (r < 0) {
-
- if (sd_bus_error_has_name(&error, BUS_ERROR_HOME_NOT_ACTIVE))
+ if (sd_bus_error_has_names(&error, BUS_ERROR_HOME_NOT_ACTIVE, BUS_ERROR_HOME_BUSY)) {
/* Only on RefHome(): We can't access the home directory currently, unless
* it's unlocked with a password. Hence, let's try this again, this time with
* authentication. */
home_not_active = true;
- else if (sd_bus_error_has_name(&error, BUS_ERROR_HOME_LOCKED))
+ do_auth = true;
+ } else if (sd_bus_error_has_name(&error, BUS_ERROR_HOME_LOCKED)) {
home_locked = true; /* Similar */
- else {
+ do_auth = true;
+ } else {
r = handle_generic_user_record_error(handle, ur->user_name, secret, r, &error, debug);
if (r == PAM_CONV_ERR) {
/* Password/PIN prompts will fail in certain environments, for example when
@@ -612,20 +638,26 @@ static int acquire_home(
* per-service PAM logic. In that case, print a friendly message and accept
* failure. */
- if (home_not_active)
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Home of user %s is currently not active, please log in locally first."), ur->user_name);
- if (home_locked)
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Home of user %s is currently locked, please unlock locally first."), ur->user_name);
+ if (!FLAGS_SET(flags, ACQUIRE_REF_ANYWAY)) {
+ if (home_not_active)
+ (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);
+ if (home_locked)
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Home of user %s is currently locked, please unlock locally first."), ur->user_name);
+
+ if (FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE) || debug)
+ pam_syslog(handle, FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE) ? LOG_ERR : LOG_DEBUG, "Failed to prompt for password/prompt.");
- if (please_authenticate || debug)
- pam_syslog(handle, please_authenticate ? LOG_ERR : LOG_DEBUG, "Failed to prompt for password/prompt.");
+ return home_not_active || home_locked ? PAM_PERM_DENIED : PAM_CONV_ERR;
+ }
- return home_not_active || home_locked ? PAM_PERM_DENIED : PAM_CONV_ERR;
- }
- if (r != PAM_SUCCESS)
+ /* ref_anyway is true, hence let's now get a ref no matter what. */
+ unrestricted = true;
+ do_auth = false;
+ } else if (r != PAM_SUCCESS)
return r;
+ else
+ do_auth = true; /* The issue was dealt with, some more information was collected. Let's try to authenticate, again. */
}
-
} else {
int fd;
@@ -641,18 +673,15 @@ static int acquire_home(
}
if (++n_attempts >= 5) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL,
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL,
_("Too many unsuccessful login attempts for user %s, refusing."), ur->user_name);
return pam_syslog_pam_error(handle, LOG_ERR, PAM_MAXTRIES,
"Failed to acquire home for user %s: %s", ur->user_name, bus_error_message(&error, r));
}
-
- /* Try again, this time with authentication if we didn't do that before. */
- do_auth = true;
}
/* Later PAM modules may need the auth token, but only during pam_authenticate. */
- if (please_authenticate && !strv_isempty(secret->password)) {
+ if (FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE) && !strv_isempty(secret->password)) {
r = pam_set_item(handle, PAM_AUTHTOK, *secret->password);
if (r != PAM_SUCCESS)
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set PAM auth token: @PAMERR@");
@@ -672,7 +701,19 @@ static int acquire_home(
return r;
}
- pam_syslog(handle, LOG_NOTICE, "Home for user %s successfully acquired.", ur->user_name);
+ /* If we didn't actually manage to unlock the home directory, then we rely on the fallback-shell to
+ * unlock it for us. But until that happens we don't want that logind spawns the per-user service
+ * manager for us (since it would see an inaccessible home directory). Hence set an environment
+ * variable that pam_systemd looks for). */
+ if (unrestricted) {
+ r = pam_putenv(handle, "XDG_SESSION_INCOMPLETE=1");
+ if (r != PAM_SUCCESS)
+ return pam_syslog_pam_error(handle, LOG_WARNING, r, "Failed to set XDG_SESSION_INCOMPLETE= environment variable: @PAMERR@");
+
+ pam_syslog(handle, LOG_NOTICE, "Home for user %s acquired in incomplete mode, requires later activation.", ur->user_name);
+ } else
+ pam_syslog(handle, LOG_NOTICE, "Home for user %s successfully acquired.", ur->user_name);
+
return PAM_SUCCESS;
}
@@ -703,53 +744,99 @@ static int release_home_fd(pam_handle_t *handle, const char *username) {
_public_ PAM_EXTERN int pam_sm_authenticate(
pam_handle_t *handle,
- int flags,
+ int sm_flags,
int argc, const char **argv) {
- bool debug = false, suspend_please = false;
+ AcquireHomeFlags flags = 0;
+ bool debug = false;
- if (parse_env(handle, &suspend_please) < 0)
+ pam_log_setup();
+
+ if (parse_env(handle, &flags) < 0)
return PAM_AUTH_ERR;
if (parse_argv(handle,
argc, argv,
- &suspend_please,
+ &flags,
&debug) < 0)
return PAM_AUTH_ERR;
pam_debug_syslog(handle, debug, "pam-systemd-homed authenticating");
- return acquire_home(handle, /* please_authenticate= */ true, suspend_please, debug, NULL);
+ return acquire_home(handle, ACQUIRE_MUST_AUTHENTICATE|flags, debug, /* bus_data= */ NULL);
}
-_public_ PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) {
+_public_ PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int sm_flags, int argc, const char **argv) {
+ return PAM_SUCCESS;
+}
+
+static int fallback_shell_can_work(
+ pam_handle_t *handle,
+ AcquireHomeFlags *flags) {
+
+ const char *tty = NULL, *display = NULL;
+ int r;
+
+ assert(handle);
+ assert(flags);
+
+ r = pam_get_item_many(
+ handle,
+ PAM_TTY, &tty,
+ PAM_XDISPLAY, &display);
+ if (r != PAM_SUCCESS)
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM items: @PAMERR@");
+
+ /* The fallback shell logic only works on TTY logins, hence only allow it if there's no X11 display
+ * set, and a TTY field is set that is neither "cron" (which is what crond sets, god knows why) not
+ * contains a colon (which is what various graphical X11 logins do). Note that ssh sets the tty to
+ * "ssh" here, which we allow (I mean, ssh is after all the primary reason we do all this). */
+ if (isempty(display) &&
+ tty &&
+ !strchr(tty, ':') &&
+ !streq(tty, "cron"))
+ *flags |= ACQUIRE_REF_ANYWAY; /* Allow login even if we can only ref, not activate */
+
return PAM_SUCCESS;
}
_public_ PAM_EXTERN int pam_sm_open_session(
pam_handle_t *handle,
- int flags,
+ int sm_flags,
int argc, const char **argv) {
/* Let's release the D-Bus connection once this function exits, after all the session might live
* quite a long time, and we are not going to process the bus connection in that time, so let's
* better close before the daemon kicks us off because we are not processing anything. */
_cleanup_(pam_bus_data_disconnectp) PamBusData *d = NULL;
- bool debug = false, suspend_please = false;
+ AcquireHomeFlags flags = 0;
+ bool debug = false;
int r;
- if (parse_env(handle, &suspend_please) < 0)
+ pam_log_setup();
+
+ if (parse_env(handle, &flags) < 0)
return PAM_SESSION_ERR;
if (parse_argv(handle,
argc, argv,
- &suspend_please,
+ &flags,
&debug) < 0)
return PAM_SESSION_ERR;
pam_debug_syslog(handle, debug, "pam-systemd-homed session start");
- r = acquire_home(handle, /* please_authenticate = */ false, suspend_please, debug, &d);
+ r = fallback_shell_can_work(handle, &flags);
+ if (r != PAM_SUCCESS)
+ return r;
+
+ /* Explicitly get saved PamBusData here. Otherwise, this function may succeed without setting 'd'
+ * even if there is an opened sd-bus connection, and it will be leaked. See issue #31375. */
+ r = pam_get_bus_data(handle, "pam-systemd-home", &d);
+ if (r != PAM_SUCCESS)
+ return r;
+
+ r = acquire_home(handle, flags, debug, &d);
if (r == PAM_USER_UNKNOWN) /* Not managed by us? Don't complain. */
return PAM_SUCCESS;
if (r != PAM_SUCCESS)
@@ -760,7 +847,7 @@ _public_ PAM_EXTERN int pam_sm_open_session(
return pam_syslog_pam_error(handle, LOG_ERR, r,
"Failed to set PAM environment variable $SYSTEMD_HOME: @PAMERR@");
- r = pam_putenv(handle, suspend_please ? "SYSTEMD_HOME_SUSPEND=1" : "SYSTEMD_HOME_SUSPEND=0");
+ r = pam_putenv(handle, FLAGS_SET(flags, ACQUIRE_PLEASE_SUSPEND) ? "SYSTEMD_HOME_SUSPEND=1" : "SYSTEMD_HOME_SUSPEND=0");
if (r != PAM_SUCCESS)
return pam_syslog_pam_error(handle, LOG_ERR, r,
"Failed to set PAM environment variable $SYSTEMD_HOME_SUSPEND: @PAMERR@");
@@ -770,16 +857,17 @@ _public_ PAM_EXTERN int pam_sm_open_session(
_public_ PAM_EXTERN int pam_sm_close_session(
pam_handle_t *handle,
- int flags,
+ int sm_flags,
int argc, const char **argv) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
- _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
const char *username = NULL;
bool debug = false;
int r;
+ pam_log_setup();
+
if (parse_argv(handle,
argc, argv,
NULL,
@@ -803,6 +891,7 @@ _public_ PAM_EXTERN int pam_sm_close_session(
if (r != PAM_SUCCESS)
return r;
+ _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus, NULL);
if (r != PAM_SUCCESS)
return r;
@@ -829,27 +918,34 @@ _public_ PAM_EXTERN int pam_sm_close_session(
_public_ PAM_EXTERN int pam_sm_acct_mgmt(
pam_handle_t *handle,
- int flags,
+ int sm_flags,
int argc,
const char **argv) {
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
- bool debug = false, please_suspend = false;
+ AcquireHomeFlags flags = 0;
+ bool debug = false;
usec_t t;
int r;
- if (parse_env(handle, &please_suspend) < 0)
+ pam_log_setup();
+
+ if (parse_env(handle, &flags) < 0)
return PAM_AUTH_ERR;
if (parse_argv(handle,
argc, argv,
- &please_suspend,
+ &flags,
&debug) < 0)
return PAM_AUTH_ERR;
pam_debug_syslog(handle, debug, "pam-systemd-homed account management");
- r = acquire_home(handle, /* please_authenticate = */ false, please_suspend, debug, NULL);
+ r = fallback_shell_can_work(handle, &flags);
+ if (r != PAM_SUCCESS)
+ return r;
+
+ r = acquire_home(handle, flags, debug, /* bus_data= */ NULL);
if (r != PAM_SUCCESS)
return r;
@@ -865,20 +961,20 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt(
break;
case -ENOLCK:
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record is blocked, prohibiting access."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record is blocked, prohibiting access."));
return PAM_ACCT_EXPIRED;
case -EL2HLT:
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record is not valid yet, prohibiting access."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record is not valid yet, prohibiting access."));
return PAM_ACCT_EXPIRED;
case -EL3HLT:
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record is not valid anymore, prohibiting access."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record is not valid anymore, prohibiting access."));
return PAM_ACCT_EXPIRED;
default:
if (r < 0) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record not valid, prohibiting access."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record not valid, prohibiting access."));
return PAM_ACCT_EXPIRED;
}
@@ -890,7 +986,7 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt(
usec_t n = now(CLOCK_REALTIME);
if (t > n) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Too many logins, try again in %s."),
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Too many logins, try again in %s."),
FORMAT_TIMESPAN(t - n, USEC_PER_SEC));
return PAM_MAXTRIES;
@@ -901,21 +997,21 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt(
switch (r) {
case -EKEYREVOKED:
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password change required."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password change required."));
return PAM_NEW_AUTHTOK_REQD;
case -EOWNERDEAD:
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password expired, change required."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password expired, change required."));
return PAM_NEW_AUTHTOK_REQD;
/* Strictly speaking this is only about password expiration, and we might want to allow
* authentication via PKCS#11 or so, but let's ignore this fine distinction for now. */
case -EKEYREJECTED:
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password is expired, but can't change, refusing login."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password is expired, but can't change, refusing login."));
return PAM_AUTHTOK_EXPIRED;
case -EKEYEXPIRED:
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password will expire soon, please change."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password will expire soon, please change."));
break;
case -ESTALE:
@@ -929,7 +1025,7 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt(
default:
if (r < 0) {
- (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record not valid, prohibiting access."));
+ (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record not valid, prohibiting access."));
return PAM_AUTHTOK_EXPIRED;
}
@@ -941,17 +1037,18 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt(
_public_ PAM_EXTERN int pam_sm_chauthtok(
pam_handle_t *handle,
- int flags,
+ int sm_flags,
int argc,
const char **argv) {
_cleanup_(user_record_unrefp) UserRecord *ur = NULL, *old_secret = NULL, *new_secret = NULL;
const char *old_password = NULL, *new_password = NULL;
- _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
unsigned n_attempts = 0;
bool debug = false;
int r;
+ pam_log_setup();
+
if (parse_argv(handle,
argc, argv,
NULL,
@@ -960,22 +1057,17 @@ _public_ PAM_EXTERN int pam_sm_chauthtok(
pam_debug_syslog(handle, debug, "pam-systemd-homed account management");
- r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus, NULL);
- if (r != PAM_SUCCESS)
- return r;
-
r = acquire_user_record(handle, NULL, debug, &ur, NULL);
if (r != PAM_SUCCESS)
return r;
/* Start with cached credentials */
- r = pam_get_item(handle, PAM_OLDAUTHTOK, (const void**) &old_password);
- if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get old password: @PAMERR@");
-
- r = pam_get_item(handle, PAM_AUTHTOK, (const void**) &new_password);
- if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get cached password: @PAMERR@");
+ r = pam_get_item_many(
+ handle,
+ PAM_OLDAUTHTOK, &old_password,
+ PAM_AUTHTOK, &new_password);
+ if (r != PAM_SUCCESS)
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get cached passwords: @PAMERR@");
if (isempty(new_password)) {
/* No, it's not cached, then let's ask for the password and its verification, and cache
@@ -1000,7 +1092,7 @@ _public_ PAM_EXTERN int pam_sm_chauthtok(
}
/* Now everything is cached and checked, let's exit from the preliminary check */
- if (FLAGS_SET(flags, PAM_PRELIM_CHECK))
+ if (FLAGS_SET(sm_flags, PAM_PRELIM_CHECK))
return PAM_SUCCESS;
old_secret = user_record_new();
@@ -1021,6 +1113,11 @@ _public_ PAM_EXTERN int pam_sm_chauthtok(
if (r < 0)
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to store new password: %m");
+ _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
+ r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus, NULL);
+ if (r != PAM_SUCCESS)
+ return r;
+
for (;;) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;