]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
user-record: add fields for a fallback home dir + shell
authorLennart Poettering <lennart@poettering.net>
Fri, 13 May 2022 16:21:01 +0000 (18:21 +0200)
committerLennart Poettering <lennart@poettering.net>
Wed, 14 Feb 2024 14:01:38 +0000 (15:01 +0100)
This adds fields to the user record logic to allow a "fallback" home
directory and shell to be set as part of the "status" section of the
user record, i.e. supplied by the manager of the user record.

The idea is that if the fallback homedir/shell is set it will take
precedence over the real one in most ways.

Usecase: let's try to make ssh logins into homed directories work.
systemd-homed would set a fallback shell/homedir for inactive home dirs.
Thus, when ssh logins take place via key auth, we can allow them, and
these fallback session params would be used because the real home cannot
be activated just yet becasue we cannot acquire any password for it from
the user.

docs/USER_RECORD.md
src/shared/user-record.c
src/shared/user-record.h

index aba45c39f42bc1b9f4b13592ee7b1fe5dc6542b8..50ea58ac3182b9edae452ccfb46f56d808cb99a2 100644 (file)
@@ -914,6 +914,20 @@ itself.
 `fileSystemType` → The file system type backing the home directory: a short
 string, such as "btrfs", "ext4", "xfs".
 
+`fallbackShell`, `fallbackHomeDirectory` → These fields have the same contents
+and format as the `shell` and `homeDirectory` fields (see above). When the
+`useFallback` field (see below) is set to true, the data from these fields
+should override the fields of the same name without the `fallback` prefix.
+
+`useFallback` → A boolean that allows choosing between the regular `shell` and
+`homeDirectory` fields or the fallback fields of the same name (see above). If
+`true` the fallback fields should be used in place of the regular fields, if
+`false` or unset the regular fields should be used. This mechanism is used for
+enable subsystems such as SSH to allow logins into user accounts, whose homed
+directories need further unlocking (because the SSH native authentication
+cannot release a suitabable disk encryption key), which the fallback shell
+provides.
+
 ## Fields in the `signature` section
 
 As mentioned, the `signature` section of the user record may contain one or
index 966abc5c42142cfa3aad407bba23b1330fe032fa..38e5f01c2364424212000b6ca3de6176b2981a2a 100644 (file)
@@ -167,6 +167,9 @@ static UserRecord* user_record_free(UserRecord *h) {
         free(h->home_directory);
         free(h->home_directory_auto);
 
+        free(h->fallback_shell);
+        free(h->fallback_home_directory);
+
         strv_free(h->member_of);
         strv_free(h->capability_bounding_set);
         strv_free(h->capability_ambient_set);
@@ -1325,23 +1328,26 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp
 static int dispatch_status(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
 
         static const JsonDispatch status_dispatch_table[] = {
-                { "diskUsage",                  _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64,      offsetof(UserRecord, disk_usage),                    0         },
-                { "diskFree",                   _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64,      offsetof(UserRecord, disk_free),                     0         },
-                { "diskSize",                   _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64,      offsetof(UserRecord, disk_size),                     0         },
-                { "diskCeiling",                _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64,      offsetof(UserRecord, disk_ceiling),                  0         },
-                { "diskFloor",                  _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64,      offsetof(UserRecord, disk_floor),                    0         },
-                { "state",                      JSON_VARIANT_STRING,        json_dispatch_string,      offsetof(UserRecord, state),                         JSON_SAFE },
-                { "service",                    JSON_VARIANT_STRING,        json_dispatch_string,      offsetof(UserRecord, service),                       JSON_SAFE },
-                { "signedLocally",              _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate,    offsetof(UserRecord, signed_locally),                0         },
-                { "goodAuthenticationCounter",  _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64,      offsetof(UserRecord, good_authentication_counter),   0         },
-                { "badAuthenticationCounter",   _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64,      offsetof(UserRecord, bad_authentication_counter),    0         },
-                { "lastGoodAuthenticationUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64,      offsetof(UserRecord, last_good_authentication_usec), 0         },
-                { "lastBadAuthenticationUSec",  _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64,      offsetof(UserRecord, last_bad_authentication_usec),  0         },
-                { "rateLimitBeginUSec",         _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64,      offsetof(UserRecord, ratelimit_begin_usec),          0         },
-                { "rateLimitCount",             _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64,      offsetof(UserRecord, ratelimit_count),               0         },
-                { "removable",                  JSON_VARIANT_BOOLEAN,       json_dispatch_boolean,     offsetof(UserRecord, removable),                     0         },
-                { "accessMode",                 JSON_VARIANT_UNSIGNED,      json_dispatch_access_mode, offsetof(UserRecord, access_mode),                   0         },
-                { "fileSystemType",             JSON_VARIANT_STRING,        json_dispatch_string,      offsetof(UserRecord, file_system_type),              JSON_SAFE },
+                { "diskUsage",                  _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64,           offsetof(UserRecord, disk_usage),                    0         },
+                { "diskFree",                   _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64,           offsetof(UserRecord, disk_free),                     0         },
+                { "diskSize",                   _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64,           offsetof(UserRecord, disk_size),                     0         },
+                { "diskCeiling",                _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64,           offsetof(UserRecord, disk_ceiling),                  0         },
+                { "diskFloor",                  _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64,           offsetof(UserRecord, disk_floor),                    0         },
+                { "state",                      JSON_VARIANT_STRING,        json_dispatch_string,           offsetof(UserRecord, state),                         JSON_SAFE },
+                { "service",                    JSON_VARIANT_STRING,        json_dispatch_string,           offsetof(UserRecord, service),                       JSON_SAFE },
+                { "signedLocally",              _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate,         offsetof(UserRecord, signed_locally),                0         },
+                { "goodAuthenticationCounter",  _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64,           offsetof(UserRecord, good_authentication_counter),   0         },
+                { "badAuthenticationCounter",   _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64,           offsetof(UserRecord, bad_authentication_counter),    0         },
+                { "lastGoodAuthenticationUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64,           offsetof(UserRecord, last_good_authentication_usec), 0         },
+                { "lastBadAuthenticationUSec",  _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64,           offsetof(UserRecord, last_bad_authentication_usec),  0         },
+                { "rateLimitBeginUSec",         _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64,           offsetof(UserRecord, ratelimit_begin_usec),          0         },
+                { "rateLimitCount",             _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64,           offsetof(UserRecord, ratelimit_count),               0         },
+                { "removable",                  JSON_VARIANT_BOOLEAN,       json_dispatch_boolean,          offsetof(UserRecord, removable),                     0         },
+                { "accessMode",                 JSON_VARIANT_UNSIGNED,      json_dispatch_access_mode,      offsetof(UserRecord, access_mode),                   0         },
+                { "fileSystemType",             JSON_VARIANT_STRING,        json_dispatch_string,           offsetof(UserRecord, file_system_type),              JSON_SAFE },
+                { "fallbackShell",              JSON_VARIANT_STRING,        json_dispatch_filename_or_path, offsetof(UserRecord, fallback_shell),                0         },
+                { "fallbackHomeDirectory",      JSON_VARIANT_STRING,        json_dispatch_home_directory,   offsetof(UserRecord, fallback_home_directory),       0         },
+                { "useFallback",                JSON_VARIANT_BOOLEAN,       json_dispatch_boolean,          offsetof(UserRecord, use_fallback),                  0         },
                 {},
         };
 
@@ -1752,7 +1758,7 @@ mode_t user_record_access_mode(UserRecord *h) {
         return h->access_mode != MODE_INVALID ? h->access_mode : 0700;
 }
 
-const char* user_record_home_directory(UserRecord *h) {
+static const char *user_record_home_directory_real(UserRecord *h) {
         assert(h);
 
         if (h->home_directory)
@@ -1767,6 +1773,15 @@ const char* user_record_home_directory(UserRecord *h) {
         return "/";
 }
 
+const char* user_record_home_directory(UserRecord *h) {
+        assert(h);
+
+        if (h->use_fallback && h->fallback_home_directory)
+                return h->fallback_home_directory;
+
+        return user_record_home_directory_real(h);
+}
+
 const char *user_record_image_path(UserRecord *h) {
         assert(h);
 
@@ -1775,7 +1790,9 @@ const char *user_record_image_path(UserRecord *h) {
         if (h->image_path_auto)
                 return h->image_path_auto;
 
-        return IN_SET(user_record_storage(h), USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT) ? user_record_home_directory(h) : NULL;
+        /* For some storage types the image is the home directory itself. (But let's ignore the fallback logic for it) */
+        return IN_SET(user_record_storage(h), USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT) ?
+                user_record_home_directory_real(h) : NULL;
 }
 
 const char *user_record_cifs_user_name(UserRecord *h) {
@@ -1792,7 +1809,7 @@ unsigned long user_record_mount_flags(UserRecord *h) {
                 (h->nodev ? MS_NODEV : 0);
 }
 
-const char *user_record_shell(UserRecord *h) {
+static const char *user_record_shell_real(UserRecord *h) {
         assert(h);
 
         if (h->shell)
@@ -1807,6 +1824,21 @@ const char *user_record_shell(UserRecord *h) {
         return NOLOGIN;
 }
 
+const char *user_record_shell(UserRecord *h) {
+        const char *shell;
+
+        assert(h);
+
+        shell = user_record_shell_real(h);
+
+        /* Return fallback shall if we are told so — except if the primary shell is already a nologin shell,
+         * then let's not risk anything. */
+        if (h->use_fallback && h->fallback_shell)
+                return is_nologin_shell(shell) ? NOLOGIN : h->fallback_shell;
+
+        return shell;
+}
+
 const char *user_record_real_name(UserRecord *h) {
         assert(h);
 
index 0afbc796d2809ec16e6c67026f0f6bc655a7ca2a..ee63a5364c2836780e887cdb04eec9eb21f7fd93 100644 (file)
@@ -293,6 +293,10 @@ typedef struct UserRecord {
         char *home_directory;
         char *home_directory_auto; /* when none is set explicitly, this is where we place the implicit home directory */
 
+        /* fallback shell and home dir */
+        char *fallback_shell;
+        char *fallback_home_directory;
+
         uid_t uid;
         gid_t gid;
 
@@ -322,6 +326,8 @@ typedef struct UserRecord {
         uint64_t disk_ceiling;
         uint64_t disk_floor;
 
+        bool use_fallback; /* if true → use fallback_shell + fallback_home_directory instead of the regular ones */
+
         char *state;
         char *service;
         int signed_locally;