Blame SOURCES/0001-user-Introduce-user-templates-for-setting-default-se.patch

5d6ce7
From 72427bd4fcae931298c670093f9cbd34ad58f59a Mon Sep 17 00:00:00 2001
5d6ce7
From: Ray Strode <rstrode@redhat.com>
5d6ce7
Date: Wed, 4 Aug 2021 19:54:59 -0400
5d6ce7
Subject: [PATCH] user: Introduce user templates for setting default session
5d6ce7
 etc
5d6ce7
5d6ce7
At the moment there's no easy way to set a default session, or
5d6ce7
face icon or whatever for all users.  If a user has never logged in
5d6ce7
before, we just generate their cache file from hardcoded defaults.
5d6ce7
5d6ce7
This commit introduces a template system to make it possible for
5d6ce7
admins to set up defaults on their own.
5d6ce7
5d6ce7
Admins can write either
5d6ce7
/etc/accountsservice/user-templates/administrator
5d6ce7
or
5d6ce7
/etc/accountsservice/user-templates/standard
5d6ce7
5d6ce7
files.  These files follow the same format as
5d6ce7
5d6ce7
/var/lib/AccountsService/users/username
5d6ce7
5d6ce7
files, but will support substituting $HOME and $USER to the appropriate
5d6ce7
user specific values.
5d6ce7
5d6ce7
User templates also support an additional group [Template] that
5d6ce7
have an additional key EnvironmentFiles that specify a list
5d6ce7
of environment files to load (files with KEY=VALUE pairs in them).
5d6ce7
Any keys listed in those environment files will also get substituted.
5d6ce7
---
5d6ce7
 data/administrator |   6 +
5d6ce7
 data/meson.build   |  10 ++
5d6ce7
 data/standard      |   6 +
5d6ce7
 src/daemon.c       |   8 +-
5d6ce7
 src/meson.build    |   1 +
5d6ce7
 src/user.c         | 284 ++++++++++++++++++++++++++++++++++++++++++++-
5d6ce7
 src/user.h         |   3 +-
5d6ce7
 7 files changed, 305 insertions(+), 13 deletions(-)
5d6ce7
 create mode 100644 data/administrator
5d6ce7
 create mode 100644 data/standard
5d6ce7
5d6ce7
diff --git a/data/administrator b/data/administrator
5d6ce7
new file mode 100644
5d6ce7
index 0000000..ea043c9
5d6ce7
--- /dev/null
5d6ce7
+++ b/data/administrator
5d6ce7
@@ -0,0 +1,6 @@
5d6ce7
+[Template]
5d6ce7
+#EnvironmentFiles=/etc/os-release;
5d6ce7
+
5d6ce7
+[User]
5d6ce7
+Session=
5d6ce7
+Icon=${HOME}/.face
5d6ce7
diff --git a/data/meson.build b/data/meson.build
5d6ce7
index 2dc57c2..7d9bdcd 100644
5d6ce7
--- a/data/meson.build
5d6ce7
+++ b/data/meson.build
5d6ce7
@@ -22,30 +22,40 @@ service = act_namespace + '.service'
5d6ce7
 configure_file(
5d6ce7
   input: service + '.in',
5d6ce7
   output: service,
5d6ce7
   configuration: service_conf,
5d6ce7
   install: true,
5d6ce7
   install_dir: dbus_sys_dir,
5d6ce7
 )
5d6ce7
 
5d6ce7
 policy = act_namespace.to_lower() + '.policy'
5d6ce7
 
5d6ce7
 i18n.merge_file(
5d6ce7
   policy,
5d6ce7
   input: policy + '.in',
5d6ce7
   output: policy,
5d6ce7
   po_dir: po_dir,
5d6ce7
   install: true,
5d6ce7
   install_dir: policy_dir,
5d6ce7
 )
5d6ce7
 
5d6ce7
 if install_systemd_unit_dir
5d6ce7
   service = 'accounts-daemon.service'
5d6ce7
 
5d6ce7
   configure_file(
5d6ce7
     input: service + '.in',
5d6ce7
     output: service,
5d6ce7
     configuration: service_conf,
5d6ce7
     install: true,
5d6ce7
     install_dir: systemd_system_unit_dir,
5d6ce7
   )
5d6ce7
 endif
5d6ce7
+
5d6ce7
+install_data(
5d6ce7
+  'administrator',
5d6ce7
+  install_dir: join_paths(act_datadir, 'accountsservice', 'user-templates'),
5d6ce7
+)
5d6ce7
+
5d6ce7
+install_data(
5d6ce7
+  'standard',
5d6ce7
+  install_dir: join_paths(act_datadir, 'accountsservice', 'user-templates'),
5d6ce7
+)
5d6ce7
diff --git a/data/standard b/data/standard
5d6ce7
new file mode 100644
5d6ce7
index 0000000..ea043c9
5d6ce7
--- /dev/null
5d6ce7
+++ b/data/standard
5d6ce7
@@ -0,0 +1,6 @@
5d6ce7
+[Template]
5d6ce7
+#EnvironmentFiles=/etc/os-release;
5d6ce7
+
5d6ce7
+[User]
5d6ce7
+Session=
5d6ce7
+Icon=${HOME}/.face
5d6ce7
diff --git a/src/daemon.c b/src/daemon.c
5d6ce7
index 5ce0216..66ac7ba 100644
5d6ce7
--- a/src/daemon.c
5d6ce7
+++ b/src/daemon.c
5d6ce7
@@ -298,69 +298,63 @@ entry_generator_cachedir (Daemon       *daemon,
5d6ce7
                         break;
5d6ce7
 
5d6ce7
                 /* Only load files in this directory */
5d6ce7
                 filename = g_build_filename (USERDIR, name, NULL);
5d6ce7
                 regular = g_file_test (filename, G_FILE_TEST_IS_REGULAR);
5d6ce7
 
5d6ce7
                 if (regular) {
5d6ce7
                         errno = 0;
5d6ce7
                         pwent = getpwnam (name);
5d6ce7
                         if (pwent != NULL) {
5d6ce7
                                 *shadow_entry = getspnam (pwent->pw_name);
5d6ce7
 
5d6ce7
                                 return pwent;
5d6ce7
                         } else if (errno == 0) {
5d6ce7
                                 g_debug ("user '%s' in cache dir but not present on system, removing", name);
5d6ce7
                                 remove_cache_files (name);
5d6ce7
                         }
5d6ce7
                         else {
5d6ce7
                                 g_warning ("failed to check if user '%s' in cache dir is present on system: %s",
5d6ce7
                                   name, g_strerror (errno));
5d6ce7
                         }
5d6ce7
                 }
5d6ce7
         }
5d6ce7
 
5d6ce7
         /* Last iteration */
5d6ce7
         g_dir_close (dir);
5d6ce7
 
5d6ce7
         /* Update all the users from the files in the cache dir */
5d6ce7
         g_hash_table_iter_init (&iter, users);
5d6ce7
         while (g_hash_table_iter_next (&iter, &key, &value)) {
5d6ce7
-                const gchar *name = key;
5d6ce7
                 User *user = value;
5d6ce7
-                g_autofree gchar *filename = NULL;
5d6ce7
-                g_autoptr(GKeyFile) key_file = NULL;
5d6ce7
 
5d6ce7
-                filename = g_build_filename (USERDIR, name, NULL);
5d6ce7
-                key_file = g_key_file_new ();
5d6ce7
-                if (g_key_file_load_from_file (key_file, filename, 0, NULL))
5d6ce7
-                        user_update_from_keyfile (user, key_file);
5d6ce7
+                user_update_from_cache (user);
5d6ce7
         }
5d6ce7
 
5d6ce7
         *state = NULL;
5d6ce7
         return NULL;
5d6ce7
 }
5d6ce7
 
5d6ce7
 static struct passwd *
5d6ce7
 entry_generator_requested_users (Daemon       *daemon,
5d6ce7
                                  GHashTable   *users,
5d6ce7
                                  gpointer     *state,
5d6ce7
                                  struct spwd **shadow_entry)
5d6ce7
 {
5d6ce7
         DaemonPrivate *priv = daemon_get_instance_private (daemon);
5d6ce7
         struct passwd *pwent;
5d6ce7
         GList *node;
5d6ce7
 
5d6ce7
         /* First iteration */
5d6ce7
         if (*state == NULL) {
5d6ce7
                 *state = priv->explicitly_requested_users;
5d6ce7
         }
5d6ce7
 
5d6ce7
         /* Every iteration */
5d6ce7
 
5d6ce7
         if (g_hash_table_size (users) < MAX_LOCAL_USERS) {
5d6ce7
                 node = *state;
5d6ce7
                 while (node != NULL) {
5d6ce7
                         const char *name;
5d6ce7
 
5d6ce7
                         name = node->data;
5d6ce7
                         node = node->next;
5d6ce7
diff --git a/src/meson.build b/src/meson.build
5d6ce7
index 3970749..d3b0cb9 100644
5d6ce7
--- a/src/meson.build
5d6ce7
+++ b/src/meson.build
5d6ce7
@@ -1,59 +1,60 @@
5d6ce7
 sources = []
5d6ce7
 
5d6ce7
 gdbus_headers = []
5d6ce7
 
5d6ce7
 ifaces = [
5d6ce7
   ['accounts-generated', 'org.freedesktop.', 'Accounts'],
5d6ce7
   ['accounts-user-generated', act_namespace + '.', 'User'],
5d6ce7
   ['realmd-generated', 'org.freedesktop.', 'realmd'],
5d6ce7
 ]
5d6ce7
 
5d6ce7
 foreach iface: ifaces
5d6ce7
   gdbus_sources = gnome.gdbus_codegen(
5d6ce7
     iface[0],
5d6ce7
     join_paths(data_dir, iface[1] + iface[2] + '.xml'),
5d6ce7
     interface_prefix: iface[1],
5d6ce7
     namespace: 'Accounts',
5d6ce7
   )
5d6ce7
   sources += gdbus_sources
5d6ce7
   gdbus_headers += gdbus_sources[1]
5d6ce7
 endforeach
5d6ce7
 
5d6ce7
 deps = [
5d6ce7
   gio_dep,
5d6ce7
   gio_unix_dep,
5d6ce7
 ]
5d6ce7
 
5d6ce7
 cflags = [
5d6ce7
   '-DLOCALSTATEDIR="@0@"'.format(act_localstatedir),
5d6ce7
   '-DDATADIR="@0@"'.format(act_datadir),
5d6ce7
+  '-DSYSCONFDIR="@0@"'.format(act_sysconfdir),
5d6ce7
   '-DICONDIR="@0@"'.format(join_paths(act_localstatedir, 'lib', 'AccountsService', 'icons')),
5d6ce7
   '-DUSERDIR="@0@"'.format(join_paths(act_localstatedir, 'lib', 'AccountsService', 'users')),
5d6ce7
 ]
5d6ce7
 
5d6ce7
 libaccounts_generated = static_library(
5d6ce7
   'accounts-generated',
5d6ce7
   sources: sources,
5d6ce7
   include_directories: top_inc,
5d6ce7
   dependencies: deps,
5d6ce7
   c_args: cflags,
5d6ce7
 )
5d6ce7
 
5d6ce7
 libaccounts_generated_dep = declare_dependency(
5d6ce7
   sources: gdbus_headers,
5d6ce7
   include_directories: include_directories('.'),
5d6ce7
   dependencies: gio_dep,
5d6ce7
   link_with: libaccounts_generated,
5d6ce7
 )
5d6ce7
 
5d6ce7
 sources = files(
5d6ce7
   'daemon.c',
5d6ce7
   'extensions.c',
5d6ce7
   'main.c',
5d6ce7
   'user.c',
5d6ce7
   'user-classify.c',
5d6ce7
   'util.c',
5d6ce7
   'wtmp-helper.c',
5d6ce7
 )
5d6ce7
 
5d6ce7
 deps = [
5d6ce7
diff --git a/src/user.c b/src/user.c
5d6ce7
index 9f57af5..16c7721 100644
5d6ce7
--- a/src/user.c
5d6ce7
+++ b/src/user.c
5d6ce7
@@ -43,127 +43,384 @@
5d6ce7
 #include <polkit/polkit.h>
5d6ce7
 
5d6ce7
 #include "user-classify.h"
5d6ce7
 #include "daemon.h"
5d6ce7
 #include "user.h"
5d6ce7
 #include "accounts-user-generated.h"
5d6ce7
 #include "util.h"
5d6ce7
 
5d6ce7
 struct User {
5d6ce7
         AccountsUserSkeleton parent;
5d6ce7
 
5d6ce7
         GDBusConnection *system_bus_connection;
5d6ce7
         gchar *object_path;
5d6ce7
 
5d6ce7
         Daemon       *daemon;
5d6ce7
 
5d6ce7
         GKeyFile     *keyfile;
5d6ce7
 
5d6ce7
         gid_t         gid;
5d6ce7
         gint64        expiration_time;
5d6ce7
         gint64        last_change_time;
5d6ce7
         gint64        min_days_between_changes;
5d6ce7
         gint64        max_days_between_changes;
5d6ce7
         gint64        days_to_warn;
5d6ce7
         gint64        days_after_expiration_until_lock;
5d6ce7
         GVariant     *login_history;
5d6ce7
         gchar        *icon_file;
5d6ce7
         gchar        *default_icon_file;
5d6ce7
         gboolean      account_expiration_policy_known;
5d6ce7
         gboolean      cached;
5d6ce7
+        gboolean      template_loaded;
5d6ce7
 
5d6ce7
         guint        *extension_ids;
5d6ce7
         guint         n_extension_ids;
5d6ce7
 
5d6ce7
         guint         changed_timeout_id;
5d6ce7
 };
5d6ce7
 
5d6ce7
 typedef struct UserClass
5d6ce7
 {
5d6ce7
         AccountsUserSkeletonClass parent_class;
5d6ce7
 } UserClass;
5d6ce7
 
5d6ce7
 static void user_accounts_user_iface_init (AccountsUserIface *iface);
5d6ce7
+static void user_update_from_keyfile (User *user, GKeyFile *keyfile);
5d6ce7
 
5d6ce7
 G_DEFINE_TYPE_WITH_CODE (User, user, ACCOUNTS_TYPE_USER_SKELETON, G_IMPLEMENT_INTERFACE (ACCOUNTS_TYPE_USER, user_accounts_user_iface_init));
5d6ce7
 
5d6ce7
 static gint
5d6ce7
 account_type_from_pwent (struct passwd *pwent)
5d6ce7
 {
5d6ce7
         struct group *grp;
5d6ce7
         gint i;
5d6ce7
 
5d6ce7
         if (pwent->pw_uid == 0) {
5d6ce7
                 g_debug ("user is root so account type is administrator");
5d6ce7
                 return ACCOUNT_TYPE_ADMINISTRATOR;
5d6ce7
         }
5d6ce7
 
5d6ce7
         grp = getgrnam (ADMIN_GROUP);
5d6ce7
         if (grp == NULL) {
5d6ce7
                 g_debug (ADMIN_GROUP " group not found");
5d6ce7
                 return ACCOUNT_TYPE_STANDARD;
5d6ce7
         }
5d6ce7
 
5d6ce7
         for (i = 0; grp->gr_mem[i] != NULL; i++) {
5d6ce7
                 if (g_strcmp0 (grp->gr_mem[i], pwent->pw_name) == 0) {
5d6ce7
                         return ACCOUNT_TYPE_ADMINISTRATOR;
5d6ce7
                 }
5d6ce7
         }
5d6ce7
 
5d6ce7
         return ACCOUNT_TYPE_STANDARD;
5d6ce7
 }
5d6ce7
 
5d6ce7
 static void
5d6ce7
 user_reset_icon_file (User *user)
5d6ce7
 {
5d6ce7
         const char *icon_file;
5d6ce7
         gboolean    icon_is_default;
5d6ce7
         const char *home_dir;
5d6ce7
 
5d6ce7
         icon_file = accounts_user_get_icon_file (ACCOUNTS_USER (user));
5d6ce7
 
5d6ce7
         if (icon_file == NULL || g_strcmp0 (icon_file, user->default_icon_file) == 0) {
5d6ce7
                 icon_is_default = TRUE;
5d6ce7
         } else {
5d6ce7
                 icon_is_default = FALSE;
5d6ce7
         }
5d6ce7
 
5d6ce7
         g_free (user->default_icon_file);
5d6ce7
         home_dir = accounts_user_get_home_directory (ACCOUNTS_USER (user));
5d6ce7
 
5d6ce7
         user->default_icon_file = g_build_filename (home_dir, ".face", NULL);
5d6ce7
 
5d6ce7
         if (icon_is_default) {
5d6ce7
                 accounts_user_set_icon_file (ACCOUNTS_USER (user), user->default_icon_file);
5d6ce7
         }
5d6ce7
 }
5d6ce7
 
5d6ce7
+static gboolean
5d6ce7
+user_has_cache_file (User *user)
5d6ce7
+{
5d6ce7
+        g_autofree char *filename = NULL;
5d6ce7
+
5d6ce7
+        filename = g_build_filename (USERDIR, user_get_user_name (user), NULL);
5d6ce7
+
5d6ce7
+        return g_file_test (filename, G_FILE_TEST_EXISTS);
5d6ce7
+}
5d6ce7
+
5d6ce7
+static gboolean
5d6ce7
+is_valid_shell_identifier_character (char     c,
5d6ce7
+                                     gboolean first)
5d6ce7
+{
5d6ce7
+        return (!first && g_ascii_isdigit (c)) ||
5d6ce7
+                c == '_' ||
5d6ce7
+                g_ascii_isalpha (c);
5d6ce7
+}
5d6ce7
+
5d6ce7
+static char *
5d6ce7
+expand_template_variables (User       *user,
5d6ce7
+                           GHashTable *template_variables,
5d6ce7
+                           const char *str)
5d6ce7
+{
5d6ce7
+        GString *s = g_string_new ("");
5d6ce7
+        const char *p, *start;
5d6ce7
+        char c;
5d6ce7
+
5d6ce7
+        p = str;
5d6ce7
+        while (*p) {
5d6ce7
+                c = *p;
5d6ce7
+                if (c == '\\') {
5d6ce7
+                        p++;
5d6ce7
+                        c = *p;
5d6ce7
+                        if (c != '\0') {
5d6ce7
+                                p++;
5d6ce7
+                                switch (c) {
5d6ce7
+                                case '\\':
5d6ce7
+                                        g_string_append_c (s, '\\');
5d6ce7
+                                        break;
5d6ce7
+                                case '$':
5d6ce7
+                                        g_string_append_c (s, '$');
5d6ce7
+                                        break;
5d6ce7
+                                default:
5d6ce7
+                                        g_string_append_c (s, '\\');
5d6ce7
+                                        g_string_append_c (s, c);
5d6ce7
+                                        break;
5d6ce7
+                                }
5d6ce7
+                        }
5d6ce7
+                } else if (c == '$') {
5d6ce7
+                        gboolean brackets = FALSE;
5d6ce7
+                        p++;
5d6ce7
+                        if (*p == '{') {
5d6ce7
+                                brackets = TRUE;
5d6ce7
+                                p++;
5d6ce7
+                        }
5d6ce7
+                        start = p;
5d6ce7
+                        while (*p != '\0' &&
5d6ce7
+                               is_valid_shell_identifier_character (*p, p == start))
5d6ce7
+                                p++;
5d6ce7
+                        if (p == start || (brackets && *p != '}')) {
5d6ce7
+                                g_string_append_c (s, '$');
5d6ce7
+                                if (brackets)
5d6ce7
+                                        g_string_append_c (s, '{');
5d6ce7
+                                g_string_append_len (s, start, p - start);
5d6ce7
+                        } else {
5d6ce7
+                                g_autofree char *variable = NULL;
5d6ce7
+                                const char *value;
5d6ce7
+
5d6ce7
+                                if (brackets && *p == '}')
5d6ce7
+                                        p++;
5d6ce7
+
5d6ce7
+                                variable = g_strndup (start, p - start - 1);
5d6ce7
+
5d6ce7
+                                value = g_hash_table_lookup (template_variables, variable);
5d6ce7
+                                if (value) {
5d6ce7
+                                        g_string_append (s, value);
5d6ce7
+                                }
5d6ce7
+                        }
5d6ce7
+                } else {
5d6ce7
+                        p++;
5d6ce7
+                        g_string_append_c (s, c);
5d6ce7
+                }
5d6ce7
+        }
5d6ce7
+        return g_string_free (s, FALSE);
5d6ce7
+}
5d6ce7
+
5d6ce7
+static void
5d6ce7
+load_template_environment_file (User       *user,
5d6ce7
+                                GHashTable *variables,
5d6ce7
+                                const char *file)
5d6ce7
+{
5d6ce7
+        g_autofree char *contents = NULL;
5d6ce7
+        g_auto (GStrv) lines = NULL;
5d6ce7
+        g_autoptr (GError) error = NULL;
5d6ce7
+        gboolean file_loaded;
5d6ce7
+        size_t i;
5d6ce7
+
5d6ce7
+        file_loaded = g_file_get_contents (file, &contents, NULL, &error);
5d6ce7
+
5d6ce7
+        if (!file_loaded) {
5d6ce7
+                g_debug ("Couldn't load template environment file %s: %s",
5d6ce7
+                         file, error->message);
5d6ce7
+                return;
5d6ce7
+        }
5d6ce7
+
5d6ce7
+        lines = g_strsplit (contents, "\n", -1);
5d6ce7
+
5d6ce7
+        for (i = 0; lines[i] != NULL; i++) {
5d6ce7
+                char *p;
5d6ce7
+                char *variable_end;
5d6ce7
+                const char *variable;
5d6ce7
+                const char *value;
5d6ce7
+
5d6ce7
+                p = lines[i];
5d6ce7
+                while (g_ascii_isspace (*p))
5d6ce7
+                        p++;
5d6ce7
+                if (*p == '#' || *p == '\0')
5d6ce7
+                        continue;
5d6ce7
+                variable = p;
5d6ce7
+                while (is_valid_shell_identifier_character (*p, p == variable))
5d6ce7
+                        p++;
5d6ce7
+                variable_end = p;
5d6ce7
+                while (g_ascii_isspace (*p))
5d6ce7
+                        p++;
5d6ce7
+                if (variable_end == variable || *p != '=') {
5d6ce7
+                        g_debug ("template environment file %s has invalid line '%s'\n", file, lines[i]);
5d6ce7
+                        continue;
5d6ce7
+                }
5d6ce7
+                *variable_end = '\0';
5d6ce7
+                p++;
5d6ce7
+                while (g_ascii_isspace (*p))
5d6ce7
+                        p++;
5d6ce7
+                value = p;
5d6ce7
+
5d6ce7
+                if (g_hash_table_lookup (variables, variable) == NULL) {
5d6ce7
+                        g_hash_table_insert (variables,
5d6ce7
+                                             g_strdup (variable),
5d6ce7
+                                             g_strdup (value));
5d6ce7
+                }
5d6ce7
+
5d6ce7
+        }
5d6ce7
+}
5d6ce7
+
5d6ce7
+static void
5d6ce7
+initialize_template_environment (User               *user,
5d6ce7
+                                 GHashTable         *variables,
5d6ce7
+                                 const char * const *files)
5d6ce7
+{
5d6ce7
+        size_t i;
5d6ce7
+
5d6ce7
+        g_hash_table_insert (variables, g_strdup ("HOME"), g_strdup (accounts_user_get_home_directory (ACCOUNTS_USER (user))));
5d6ce7
+        g_hash_table_insert (variables, g_strdup ("USER"), g_strdup (user_get_user_name (user)));
5d6ce7
+
5d6ce7
+        if (files == NULL)
5d6ce7
+                return;
5d6ce7
+
5d6ce7
+        for (i = 0; files[i] != NULL; i++) {
5d6ce7
+                load_template_environment_file (user, variables, files[i]);
5d6ce7
+        }
5d6ce7
+}
5d6ce7
+
5d6ce7
+static void
5d6ce7
+user_update_from_template (User *user)
5d6ce7
+{
5d6ce7
+        g_autofree char *filename = NULL;
5d6ce7
+        g_autoptr (GKeyFile) key_file = NULL;
5d6ce7
+        g_autoptr (GError) error = NULL;
5d6ce7
+        g_autoptr (GHashTable) template_variables = NULL;
5d6ce7
+        g_auto (GStrv) template_environment_files = NULL;
5d6ce7
+        gboolean key_file_loaded = FALSE;
5d6ce7
+        const char * const *system_dirs[] = {
5d6ce7
+                (const char *[]) { "/run", SYSCONFDIR, NULL },
5d6ce7
+                g_get_system_data_dirs (),
5d6ce7
+                NULL
5d6ce7
+        };
5d6ce7
+        g_autoptr (GPtrArray) dirs = NULL;
5d6ce7
+        AccountType account_type;
5d6ce7
+        const char *account_type_string;
5d6ce7
+        size_t i, j;
5d6ce7
+        g_autofree char *contents = NULL;
5d6ce7
+        g_autofree char *expanded = NULL;
5d6ce7
+        g_auto (GStrv) lines = NULL;
5d6ce7
+
5d6ce7
+        if (user->template_loaded)
5d6ce7
+                return;
5d6ce7
+
5d6ce7
+        filename = g_build_filename (USERDIR,
5d6ce7
+                                     accounts_user_get_user_name (ACCOUNTS_USER (user)),
5d6ce7
+                                     NULL);
5d6ce7
+
5d6ce7
+        account_type = accounts_user_get_account_type (ACCOUNTS_USER (user));
5d6ce7
+        if (account_type == ACCOUNT_TYPE_ADMINISTRATOR)
5d6ce7
+                account_type_string = "administrator";
5d6ce7
+        else
5d6ce7
+                account_type_string = "standard";
5d6ce7
+
5d6ce7
+        dirs = g_ptr_array_new ();
5d6ce7
+        for (i = 0; system_dirs[i] != NULL; i++) {
5d6ce7
+                for (j = 0; system_dirs[i][j] != NULL; j++) {
5d6ce7
+                        char *dir;
5d6ce7
+
5d6ce7
+                        dir = g_build_filename (system_dirs[i][j],
5d6ce7
+                                                "accountsservice",
5d6ce7
+                                                "user-templates",
5d6ce7
+                                                NULL);
5d6ce7
+                        g_ptr_array_add (dirs, dir);
5d6ce7
+                }
5d6ce7
+        }
5d6ce7
+        g_ptr_array_add (dirs, NULL);
5d6ce7
+
5d6ce7
+        key_file = g_key_file_new ();
5d6ce7
+        key_file_loaded = g_key_file_load_from_dirs (key_file,
5d6ce7
+                                                     account_type_string,
5d6ce7
+                                                     (const char **) dirs->pdata,
5d6ce7
+                                                     NULL,
5d6ce7
+                                                     G_KEY_FILE_KEEP_COMMENTS,
5d6ce7
+                                                     &error);
5d6ce7
+
5d6ce7
+        if (!key_file_loaded) {
5d6ce7
+                g_debug ("failed to load user template: %s", error->message);
5d6ce7
+                return;
5d6ce7
+        }
5d6ce7
+
5d6ce7
+        template_variables = g_hash_table_new_full (g_str_hash,
5d6ce7
+                                                    g_str_equal,
5d6ce7
+                                                    g_free,
5d6ce7
+                                                    g_free);
5d6ce7
+
5d6ce7
+        template_environment_files = g_key_file_get_string_list (key_file,
5d6ce7
+                                                                 "Template",
5d6ce7
+                                                                 "EnvironmentFiles",
5d6ce7
+                                                                 NULL,
5d6ce7
+                                                                 NULL);
5d6ce7
+
5d6ce7
+        initialize_template_environment (user, template_variables, (const char * const *) template_environment_files);
5d6ce7
+
5d6ce7
+        g_key_file_remove_group (key_file, "Template", NULL);
5d6ce7
+        contents = g_key_file_to_data (key_file, NULL, NULL);
5d6ce7
+        lines = g_strsplit (contents, "\n", -1);
5d6ce7
+
5d6ce7
+        expanded = expand_template_variables (user, template_variables, contents);
5d6ce7
+
5d6ce7
+        key_file_loaded = g_key_file_load_from_data (key_file,
5d6ce7
+                                                     expanded,
5d6ce7
+                                                     strlen (expanded),
5d6ce7
+                                                     G_KEY_FILE_KEEP_COMMENTS,
5d6ce7
+                                                     &error);
5d6ce7
+
5d6ce7
+        if (key_file_loaded)
5d6ce7
+                user_update_from_keyfile (user, key_file);
5d6ce7
+
5d6ce7
+        user->template_loaded = key_file_loaded;
5d6ce7
+}
5d6ce7
+
5d6ce7
 void
5d6ce7
 user_update_from_pwent (User          *user,
5d6ce7
                         struct passwd *pwent,
5d6ce7
                         struct spwd   *spent)
5d6ce7
 {
5d6ce7
         g_autofree gchar *real_name = NULL;
5d6ce7
         gboolean is_system_account;
5d6ce7
         const gchar *passwd;
5d6ce7
         gboolean locked;
5d6ce7
         PasswordMode mode;
5d6ce7
         AccountType account_type;
5d6ce7
 
5d6ce7
         g_object_freeze_notify (G_OBJECT (user));
5d6ce7
 
5d6ce7
         if (pwent->pw_gecos && pwent->pw_gecos[0] != '\0') {
5d6ce7
                 gchar *first_comma = NULL;
5d6ce7
                 gchar *valid_utf8_name = NULL;
5d6ce7
 
5d6ce7
                 if (g_utf8_validate (pwent->pw_gecos, -1, NULL)) {
5d6ce7
                         valid_utf8_name = pwent->pw_gecos;
5d6ce7
                         first_comma = g_utf8_strchr (valid_utf8_name, -1, ',');
5d6ce7
                 }
5d6ce7
                 else {
5d6ce7
                         g_warning ("User %s has invalid UTF-8 in GECOS field. "
5d6ce7
                                    "It would be a good thing to check /etc/passwd.",
5d6ce7
                                    pwent->pw_name ? pwent->pw_name : "");
5d6ce7
                 }
5d6ce7
 
5d6ce7
                 if (first_comma) {
5d6ce7
                         real_name = g_strndup (valid_utf8_name,
5d6ce7
@@ -212,134 +469,150 @@ user_update_from_pwent (User          *user,
5d6ce7
         accounts_user_set_locked (ACCOUNTS_USER (user), locked);
5d6ce7
 
5d6ce7
         if (passwd == NULL || passwd[0] != 0) {
5d6ce7
                 mode = PASSWORD_MODE_REGULAR;
5d6ce7
         }
5d6ce7
         else {
5d6ce7
                 mode = PASSWORD_MODE_NONE;
5d6ce7
         }
5d6ce7
 
5d6ce7
         if (spent) {
5d6ce7
                 if (spent->sp_lstchg == 0) {
5d6ce7
                         mode = PASSWORD_MODE_SET_AT_LOGIN;
5d6ce7
                 }
5d6ce7
 
5d6ce7
                 user->expiration_time = spent->sp_expire;
5d6ce7
                 user->last_change_time  = spent->sp_lstchg;
5d6ce7
                 user->min_days_between_changes = spent->sp_min;
5d6ce7
                 user->max_days_between_changes = spent->sp_max;
5d6ce7
                 user->days_to_warn  = spent->sp_warn;
5d6ce7
                 user->days_after_expiration_until_lock = spent->sp_inact;
5d6ce7
                 user->account_expiration_policy_known = TRUE;
5d6ce7
         }
5d6ce7
 
5d6ce7
         accounts_user_set_password_mode (ACCOUNTS_USER (user), mode);
5d6ce7
         is_system_account = !user_classify_is_human (accounts_user_get_uid (ACCOUNTS_USER (user)),
5d6ce7
                                                      accounts_user_get_user_name (ACCOUNTS_USER (user)),
5d6ce7
                                                      accounts_user_get_shell (ACCOUNTS_USER (user)),
5d6ce7
                                                      passwd);
5d6ce7
         accounts_user_set_system_account (ACCOUNTS_USER (user), is_system_account);
5d6ce7
 
5d6ce7
+        if (!user_has_cache_file (user))
5d6ce7
+                user_update_from_template (user);
5d6ce7
         g_object_thaw_notify (G_OBJECT (user));
5d6ce7
 }
5d6ce7
 
5d6ce7
-void
5d6ce7
+static void
5d6ce7
 user_update_from_keyfile (User     *user,
5d6ce7
                           GKeyFile *keyfile)
5d6ce7
 {
5d6ce7
         gchar *s;
5d6ce7
 
5d6ce7
-        g_object_freeze_notify (G_OBJECT (user));
5d6ce7
-
5d6ce7
         s = g_key_file_get_string (keyfile, "User", "Language", NULL);
5d6ce7
         if (s != NULL) {
5d6ce7
                 accounts_user_set_language (ACCOUNTS_USER (user), s);
5d6ce7
                 g_clear_pointer (&s, g_free);
5d6ce7
         }
5d6ce7
 
5d6ce7
         s = g_key_file_get_string (keyfile, "User", "XSession", NULL);
5d6ce7
         if (s != NULL) {
5d6ce7
                 accounts_user_set_xsession (ACCOUNTS_USER (user), s);
5d6ce7
 
5d6ce7
                 /* for backward compat */
5d6ce7
                 accounts_user_set_session (ACCOUNTS_USER (user), s);
5d6ce7
                 g_clear_pointer (&s, g_free);
5d6ce7
         }
5d6ce7
 
5d6ce7
         s = g_key_file_get_string (keyfile, "User", "Session", NULL);
5d6ce7
         if (s != NULL) {
5d6ce7
                 accounts_user_set_session (ACCOUNTS_USER (user), s);
5d6ce7
                 g_clear_pointer (&s, g_free);
5d6ce7
         }
5d6ce7
 
5d6ce7
         s = g_key_file_get_string (keyfile, "User", "SessionType", NULL);
5d6ce7
         if (s != NULL) {
5d6ce7
                 accounts_user_set_session_type (ACCOUNTS_USER (user), s);
5d6ce7
                 g_clear_pointer (&s, g_free);
5d6ce7
         }
5d6ce7
 
5d6ce7
         s = g_key_file_get_string (keyfile, "User", "Email", NULL);
5d6ce7
         if (s != NULL) {
5d6ce7
                 accounts_user_set_email (ACCOUNTS_USER (user), s);
5d6ce7
                 g_clear_pointer (&s, g_free);
5d6ce7
         }
5d6ce7
 
5d6ce7
         s = g_key_file_get_string (keyfile, "User", "Location", NULL);
5d6ce7
         if (s != NULL) {
5d6ce7
                 accounts_user_set_location (ACCOUNTS_USER (user), s);
5d6ce7
                 g_clear_pointer (&s, g_free);
5d6ce7
         }
5d6ce7
 
5d6ce7
         s = g_key_file_get_string (keyfile, "User", "PasswordHint", NULL);
5d6ce7
         if (s != NULL) {
5d6ce7
                 accounts_user_set_password_hint (ACCOUNTS_USER (user), s);
5d6ce7
                 g_clear_pointer (&s, g_free);
5d6ce7
         }
5d6ce7
 
5d6ce7
         s = g_key_file_get_string (keyfile, "User", "Icon", NULL);
5d6ce7
         if (s != NULL) {
5d6ce7
                 accounts_user_set_icon_file (ACCOUNTS_USER (user), s);
5d6ce7
                 g_clear_pointer (&s, g_free);
5d6ce7
         }
5d6ce7
 
5d6ce7
         if (g_key_file_has_key (keyfile, "User", "SystemAccount", NULL)) {
5d6ce7
             gboolean system_account;
5d6ce7
 
5d6ce7
             system_account = g_key_file_get_boolean (keyfile, "User", "SystemAccount", NULL);
5d6ce7
             accounts_user_set_system_account (ACCOUNTS_USER (user), system_account);
5d6ce7
         }
5d6ce7
 
5d6ce7
         g_clear_pointer (&user->keyfile, g_key_file_unref);
5d6ce7
         user->keyfile = g_key_file_ref (keyfile);
5d6ce7
+}
5d6ce7
+
5d6ce7
+void
5d6ce7
+user_update_from_cache (User *user)
5d6ce7
+{
5d6ce7
+        g_autofree gchar *filename = NULL;
5d6ce7
+        g_autoptr(GKeyFile) key_file = NULL;
5d6ce7
+
5d6ce7
+        filename = g_build_filename (USERDIR, accounts_user_get_user_name (ACCOUNTS_USER (user)), NULL);
5d6ce7
+
5d6ce7
+        key_file = g_key_file_new ();
5d6ce7
+
5d6ce7
+        if (!g_key_file_load_from_file (key_file, filename, 0, NULL))
5d6ce7
+                return;
5d6ce7
+
5d6ce7
+        g_object_freeze_notify (G_OBJECT (user));
5d6ce7
+        user_update_from_keyfile (user, key_file);
5d6ce7
         user_set_cached (user, TRUE);
5d6ce7
         user_set_saved (user, TRUE);
5d6ce7
-
5d6ce7
         g_object_thaw_notify (G_OBJECT (user));
5d6ce7
 }
5d6ce7
 
5d6ce7
 void
5d6ce7
 user_update_local_account_property (User          *user,
5d6ce7
                                     gboolean       local)
5d6ce7
 {
5d6ce7
         accounts_user_set_local_account (ACCOUNTS_USER (user), local);
5d6ce7
 }
5d6ce7
 
5d6ce7
 void
5d6ce7
 user_update_system_account_property (User          *user,
5d6ce7
                                      gboolean       system)
5d6ce7
 {
5d6ce7
         accounts_user_set_system_account (ACCOUNTS_USER (user), system);
5d6ce7
 }
5d6ce7
 
5d6ce7
 static void
5d6ce7
 user_save_to_keyfile (User     *user,
5d6ce7
                       GKeyFile *keyfile)
5d6ce7
 {
5d6ce7
         g_key_file_remove_group (keyfile, "User", NULL);
5d6ce7
 
5d6ce7
         if (accounts_user_get_email (ACCOUNTS_USER (user)))
5d6ce7
                 g_key_file_set_string (keyfile, "User", "Email", accounts_user_get_email (ACCOUNTS_USER (user)));
5d6ce7
 
5d6ce7
         if (accounts_user_get_language (ACCOUNTS_USER (user)))
5d6ce7
                 g_key_file_set_string (keyfile, "User", "Language", accounts_user_get_language (ACCOUNTS_USER (user)));
5d6ce7
 
5d6ce7
         if (accounts_user_get_session (ACCOUNTS_USER (user)))
5d6ce7
@@ -509,60 +782,63 @@ user_extension_set_property (User                  *user,
5d6ce7
         if (!prev || !g_str_equal (printed, prev)) {
5d6ce7
                 g_key_file_set_value (user->keyfile, interface->name, property->name, printed);
5d6ce7
 
5d6ce7
                 /* Emit a change signal.  Use invalidation
5d6ce7
                  * because the data may not be world-readable.
5d6ce7
                  */
5d6ce7
                 g_dbus_connection_emit_signal (g_dbus_method_invocation_get_connection (invocation),
5d6ce7
                                                NULL, /* destination_bus_name */
5d6ce7
                                                g_dbus_method_invocation_get_object_path (invocation),
5d6ce7
                                                "org.freedesktop.DBus.Properties", "PropertiesChanged",
5d6ce7
                                                g_variant_new_parsed ("( %s, %a{sv}, [ %s ] )",
5d6ce7
                                                                      interface->name, NULL, property->name),
5d6ce7
                                                NULL);
5d6ce7
 
5d6ce7
                 accounts_user_emit_changed (ACCOUNTS_USER (user));
5d6ce7
                 save_extra_data (user);
5d6ce7
         }
5d6ce7
 
5d6ce7
         g_dbus_method_invocation_return_value (invocation, g_variant_new ("()"));
5d6ce7
 }
5d6ce7
 
5d6ce7
 static void
5d6ce7
 user_extension_authentication_done (Daemon                *daemon,
5d6ce7
                                     User                  *user,
5d6ce7
                                     GDBusMethodInvocation *invocation,
5d6ce7
                                     gpointer               user_data)
5d6ce7
 {
5d6ce7
         GDBusInterfaceInfo *interface = user_data;
5d6ce7
         const gchar *method_name;
5d6ce7
 
5d6ce7
+        if (!user_has_cache_file (user))
5d6ce7
+                user_update_from_template (user);
5d6ce7
+
5d6ce7
         method_name = g_dbus_method_invocation_get_method_name (invocation);
5d6ce7
 
5d6ce7
         if (g_str_equal (method_name, "Get"))
5d6ce7
                 user_extension_get_property (user, daemon, interface, invocation);
5d6ce7
         else if (g_str_equal (method_name, "GetAll"))
5d6ce7
                 user_extension_get_all_properties (user, daemon, interface, invocation);
5d6ce7
         else if (g_str_equal (method_name, "Set"))
5d6ce7
                 user_extension_set_property (user, daemon, interface, invocation);
5d6ce7
         else
5d6ce7
                 g_assert_not_reached ();
5d6ce7
 }
5d6ce7
 
5d6ce7
 static void
5d6ce7
 user_extension_method_call (GDBusConnection       *connection,
5d6ce7
                             const gchar           *sender,
5d6ce7
                             const gchar           *object_path,
5d6ce7
                             const gchar           *interface_name,
5d6ce7
                             const gchar           *method_name,
5d6ce7
                             GVariant              *parameters,
5d6ce7
                             GDBusMethodInvocation *invocation,
5d6ce7
                             gpointer               user_data)
5d6ce7
 {
5d6ce7
         User *user = user_data;
5d6ce7
         GDBusInterfaceInfo *iface_info;
5d6ce7
         const gchar *annotation_name;
5d6ce7
         const gchar *action_id;
5d6ce7
         gint uid;
5d6ce7
         gint i;
5d6ce7
 
5d6ce7
         /* We don't allow method calls on extension interfaces, so we
5d6ce7
diff --git a/src/user.h b/src/user.h
5d6ce7
index b3b3380..eb81918 100644
5d6ce7
--- a/src/user.h
5d6ce7
+++ b/src/user.h
5d6ce7
@@ -30,58 +30,57 @@
5d6ce7
 #include "types.h"
5d6ce7
 
5d6ce7
 G_BEGIN_DECLS
5d6ce7
 
5d6ce7
 #define TYPE_USER (user_get_type ())
5d6ce7
 #define USER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), TYPE_USER, User))
5d6ce7
 #define IS_USER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), TYPE_USER))
5d6ce7
 
5d6ce7
 typedef enum {
5d6ce7
         ACCOUNT_TYPE_STANDARD,
5d6ce7
         ACCOUNT_TYPE_ADMINISTRATOR,
5d6ce7
 #define ACCOUNT_TYPE_LAST ACCOUNT_TYPE_ADMINISTRATOR
5d6ce7
 } AccountType;
5d6ce7
 
5d6ce7
 typedef enum {
5d6ce7
         PASSWORD_MODE_REGULAR,
5d6ce7
         PASSWORD_MODE_SET_AT_LOGIN,
5d6ce7
         PASSWORD_MODE_NONE,
5d6ce7
 #define PASSWORD_MODE_LAST PASSWORD_MODE_NONE
5d6ce7
 } PasswordMode;
5d6ce7
 
5d6ce7
 /* local methods */
5d6ce7
 
5d6ce7
 GType          user_get_type                (void) G_GNUC_CONST;
5d6ce7
 User *         user_new                     (Daemon        *daemon,
5d6ce7
                                              uid_t          uid);
5d6ce7
 
5d6ce7
 void           user_update_from_pwent       (User          *user,
5d6ce7
                                              struct passwd *pwent,
5d6ce7
                                              struct spwd   *spent);
5d6ce7
-void           user_update_from_keyfile     (User          *user,
5d6ce7
-                                             GKeyFile      *keyfile);
5d6ce7
+void           user_update_from_cache       (User *user);
5d6ce7
 void           user_update_local_account_property (User          *user,
5d6ce7
                                                    gboolean       local);
5d6ce7
 void           user_update_system_account_property (User          *user,
5d6ce7
                                                     gboolean       system);
5d6ce7
 gboolean       user_get_cached              (User          *user);
5d6ce7
 void           user_set_cached              (User          *user,
5d6ce7
                                              gboolean       cached);
5d6ce7
 void           user_set_saved               (User          *user,
5d6ce7
                                              gboolean       saved);
5d6ce7
 
5d6ce7
 void           user_register                (User          *user);
5d6ce7
 void           user_unregister              (User          *user);
5d6ce7
 void           user_changed                 (User          *user);
5d6ce7
 
5d6ce7
 void           user_save                    (User          *user);
5d6ce7
 
5d6ce7
 const gchar *  user_get_user_name           (User          *user);
5d6ce7
 gboolean       user_get_system_account      (User          *user);
5d6ce7
 gboolean       user_get_local_account       (User          *user);
5d6ce7
 const gchar *  user_get_object_path         (User          *user);
5d6ce7
 uid_t          user_get_uid                 (User          *user);
5d6ce7
 const gchar *  user_get_shell               (User          *user);
5d6ce7
 
5d6ce7
 G_END_DECLS
5d6ce7
 
5d6ce7
 #endif
5d6ce7
-- 
5d6ce7
2.27.0
5d6ce7