diff --git a/.accountsservice.metadata b/.accountsservice.metadata new file mode 100644 index 0000000..6799259 --- /dev/null +++ b/.accountsservice.metadata @@ -0,0 +1 @@ +bab4f37144196d8ba06195bc72b4a9937c62b9fd SOURCES/accountsservice-0.6.55.tar.xz diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..37d0a71 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/accountsservice-0.6.55.tar.xz diff --git a/SOURCES/0001-daemon-Allow-SystemAccount-false-to-be-set-in-cache-.patch b/SOURCES/0001-daemon-Allow-SystemAccount-false-to-be-set-in-cache-.patch new file mode 100644 index 0000000..84d5265 --- /dev/null +++ b/SOURCES/0001-daemon-Allow-SystemAccount-false-to-be-set-in-cache-.patch @@ -0,0 +1,304 @@ +From 14c902f42a4ea74ce9450eb53817e1bf5be05d26 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 8 Sep 2021 16:38:17 -0400 +Subject: [PATCH 1/2] daemon: Allow SystemAccount=false to be set in cache file + +At the moment we do dodgy checks based on uid to decide whether or not +an account is a system account. + +For legacy reasons, sometimes normal users have really low UIDs. + +This commit reshuffles things, so the cache file "wins" for deciding +whether or not a user is a system user. +--- + src/daemon.c | 24 ++++++++++++------------ + 1 file changed, 12 insertions(+), 12 deletions(-) + +diff --git a/src/daemon.c b/src/daemon.c +index 66ac7ba..2b6650b 100644 +--- a/src/daemon.c ++++ b/src/daemon.c +@@ -219,60 +219,68 @@ entry_generator_fgetpwent (Daemon *daemon, + if (g_hash_table_size (shadow_users) == 0) { + g_clear_pointer (&shadow_users, g_hash_table_unref); + return NULL; + } + + fp = fopen (PATH_PASSWD, "r"); + if (fp == NULL) { + g_clear_pointer (&shadow_users, g_hash_table_unref); + g_warning ("Unable to open %s: %s", PATH_PASSWD, g_strerror (errno)); + return NULL; + } + + generator_state = g_malloc0 (sizeof (*generator_state)); + generator_state->fp = fp; + generator_state->users = shadow_users; + + *state = generator_state; + } + + /* Every iteration */ + generator_state = *state; + + if (g_hash_table_size (users) < MAX_LOCAL_USERS) { + pwent = fgetpwent (generator_state->fp); + if (pwent != NULL) { + shadow_entry_buffers = g_hash_table_lookup (generator_state->users, pwent->pw_name); + + if (shadow_entry_buffers != NULL) { + *spent = &shadow_entry_buffers->spbuf; + } ++ ++ /* Skip system users... */ ++ if (!user_classify_is_human (pwent->pw_uid, pwent->pw_name, pwent->pw_shell, (*spent)? (*spent)->sp_pwdp : NULL)) { ++ g_debug ("skipping user: %s", pwent->pw_name); ++ ++ return entry_generator_fgetpwent (daemon, users, state, spent); ++ } ++ + return pwent; + } + } + + /* Last iteration */ + fclose (generator_state->fp); + g_hash_table_unref (generator_state->users); + g_free (generator_state); + *state = NULL; + + return NULL; + } + + static struct passwd * + entry_generator_cachedir (Daemon *daemon, + GHashTable *users, + gpointer *state, + struct spwd **shadow_entry) + { + struct passwd *pwent; + g_autoptr(GError) error = NULL; + gboolean regular; + GHashTableIter iter; + gpointer key, value; + GDir *dir; + + /* First iteration */ + if (*state == NULL) { + *state = g_dir_open (USERDIR, 0, &error); + if (error != NULL) { +@@ -373,66 +381,60 @@ entry_generator_requested_users (Daemon *daemon, + } + } + } + + /* Last iteration */ + + *state = NULL; + return NULL; + } + + static void + load_entries (Daemon *daemon, + GHashTable *users, + gboolean explicitly_requested, + EntryGeneratorFunc entry_generator) + { + DaemonPrivate *priv = daemon_get_instance_private (daemon); + gpointer generator_state = NULL; + struct passwd *pwent; + struct spwd *spent = NULL; + User *user = NULL; + + g_assert (entry_generator != NULL); + + for (;;) { + spent = NULL; + pwent = entry_generator (daemon, users, &generator_state, &spent); + if (pwent == NULL) + break; + +- /* Skip system users... */ +- if (!explicitly_requested && !user_classify_is_human (pwent->pw_uid, pwent->pw_name, pwent->pw_shell, spent? spent->sp_pwdp : NULL)) { +- g_debug ("skipping user: %s", pwent->pw_name); +- continue; +- } +- + /* Only process users that haven't been processed yet. + * We do always make sure entries get promoted + * to "cached" status if they are supposed to be + */ + + user = g_hash_table_lookup (users, pwent->pw_name); + + if (user == NULL) { + user = g_hash_table_lookup (priv->users, pwent->pw_name); + if (user == NULL) { + user = user_new (daemon, pwent->pw_uid); + } else { + g_object_ref (user); + } + + /* freeze & update users not already in the new list */ + g_object_freeze_notify (G_OBJECT (user)); + user_update_from_pwent (user, pwent, spent); + + g_hash_table_insert (users, g_strdup (user_get_user_name (user)), user); + g_debug ("loaded user: %s", user_get_user_name (user)); + } + + if (!explicitly_requested) { + user_set_cached (user, TRUE); + } + } + + /* Generator should have cleaned up */ + g_assert (generator_state == NULL); +@@ -501,66 +503,66 @@ has_network_realms (Daemon *daemon) + + static void + reload_users (Daemon *daemon) + { + DaemonPrivate *priv = daemon_get_instance_private (daemon); + AccountsAccounts *accounts = ACCOUNTS_ACCOUNTS (daemon); + gboolean had_no_users, has_no_users, had_multiple_users, has_multiple_users; + GHashTable *users; + GHashTable *old_users; + GHashTable *local; + GHashTableIter iter; + gsize number_of_normal_users = 0; + gpointer name, value; + + /* Track the users that we saw during our (re)load */ + users = create_users_hash_table (); + + /* + * NOTE: As we load data from all the sources, notifies are + * frozen in load_entries() and then thawed as we process + * them below. + */ + + /* Load the local users into our hash table */ + load_entries (daemon, users, FALSE, entry_generator_fgetpwent); + local = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_iter_init (&iter, users); + while (g_hash_table_iter_next (&iter, &name, NULL)) + g_hash_table_add (local, name); + +- /* and add users to hash table that were explicitly requested */ +- load_entries (daemon, users, TRUE, entry_generator_requested_users); +- + /* Now add/update users from other sources, possibly non-local */ + load_entries (daemon, users, FALSE, entry_generator_cachedir); + ++ /* and add users to hash table that were explicitly requested */ ++ load_entries (daemon, users, TRUE, entry_generator_requested_users); ++ + wtmp_helper_update_login_frequencies (users); + + /* Count the non-system users. Mark which users are local, which are not. */ + g_hash_table_iter_init (&iter, users); + while (g_hash_table_iter_next (&iter, &name, &value)) { + User *user = value; + if (!user_get_system_account (user)) + number_of_normal_users++; + user_update_local_account_property (user, g_hash_table_lookup (local, name) != NULL); + } + g_hash_table_destroy (local); + + had_no_users = accounts_accounts_get_has_no_users (accounts); + has_no_users = number_of_normal_users == 0; + + if (has_no_users && has_network_realms (daemon)) { + g_debug ("No local users, but network realms detected, presuming there are remote users"); + has_no_users = FALSE; + } + + if (had_no_users != has_no_users) + accounts_accounts_set_has_no_users (accounts, has_no_users); + + had_multiple_users = accounts_accounts_get_has_multiple_users (accounts); + has_multiple_users = number_of_normal_users > 1; + + if (had_multiple_users != has_multiple_users) + accounts_accounts_set_has_multiple_users (accounts, has_multiple_users); + + /* Swap out the users */ +@@ -1017,73 +1019,71 @@ daemon_find_user_by_name (AccountsAccounts *accounts, + + static ListUserData * + list_user_data_new (Daemon *daemon, + GDBusMethodInvocation *context) + { + ListUserData *data; + + data = g_new0 (ListUserData, 1); + + data->daemon = g_object_ref (daemon); + data->context = context; + + return data; + } + + static void + list_user_data_free (ListUserData *data) + { + g_object_unref (data->daemon); + g_free (data); + } + + static void + finish_list_cached_users (ListUserData *data) + { + DaemonPrivate *priv = daemon_get_instance_private (data->daemon); + g_autoptr(GPtrArray) object_paths = NULL; + GHashTableIter iter; + gpointer key, value; + uid_t uid; +- const gchar *shell; + + object_paths = g_ptr_array_new (); + + g_hash_table_iter_init (&iter, priv->users); + while (g_hash_table_iter_next (&iter, &key, &value)) { + const gchar *name = key; + User *user = value; + + uid = user_get_uid (user); +- shell = user_get_shell (user); + +- if (!user_classify_is_human (uid, name, shell, NULL)) { ++ if (user_get_system_account (user)) { + g_debug ("user %s %ld excluded", name, (long) uid); + continue; + } + + if (!user_get_cached (user)) { + g_debug ("user %s %ld not cached", name, (long) uid); + continue; + } + + g_debug ("user %s %ld not excluded", name, (long) uid); + g_ptr_array_add (object_paths, (gpointer) user_get_object_path (user)); + } + g_ptr_array_add (object_paths, NULL); + + accounts_accounts_complete_list_cached_users (NULL, data->context, (const gchar * const *) object_paths->pdata); + + list_user_data_free (data); + } + + static gboolean + daemon_list_cached_users (AccountsAccounts *accounts, + GDBusMethodInvocation *context) + { + Daemon *daemon = (Daemon*)accounts; + DaemonPrivate *priv = daemon_get_instance_private (daemon); + ListUserData *data; + + data = list_user_data_new (daemon, context); + + if (priv->reload_id > 0) { +-- +2.31.1 + diff --git a/SOURCES/0001-daemon-if-no-local-users-check-if-machine-is-enrolle.patch b/SOURCES/0001-daemon-if-no-local-users-check-if-machine-is-enrolle.patch new file mode 100644 index 0000000..5185345 --- /dev/null +++ b/SOURCES/0001-daemon-if-no-local-users-check-if-machine-is-enrolle.patch @@ -0,0 +1,1839 @@ +From 54b207649979475ea7f1fa5eaaea94be31d20935 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Fri, 13 Dec 2019 15:16:06 -0500 +Subject: [PATCH] daemon: if no local users, check if machine is enrolled in + network + +GDM will show gnome initial-setup if a machine has no local users. +But it's totally possible that a machine has only remote users, +and shouldn't have a local user. + +This commit detects that case, and avoids setting the HasNoUsers +property. +--- + data/org.freedesktop.realmd.xml | 730 ++++++++++++++++++++++++++++++++ + src/daemon.c | 63 ++- + src/meson.build | 1 + + src/org.freedesktop.realmd.xml | 730 ++++++++++++++++++++++++++++++++ + 4 files changed, 1520 insertions(+), 4 deletions(-) + create mode 100644 data/org.freedesktop.realmd.xml + create mode 100644 src/org.freedesktop.realmd.xml + +diff --git a/data/org.freedesktop.realmd.xml b/data/org.freedesktop.realmd.xml +new file mode 100644 +index 0000000..c34a47a +--- /dev/null ++++ b/data/org.freedesktop.realmd.xml +@@ -0,0 +1,730 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/src/daemon.c b/src/daemon.c +index c52bda3..5ce0216 100644 +--- a/src/daemon.c ++++ b/src/daemon.c +@@ -20,60 +20,61 @@ + * Written by: Matthias Clasen + */ + + #include "config.h" + + #include + #include + #include + #include + #include + #include + #include + #ifdef HAVE_SHADOW_H + #include + #endif + #include + #include + #include + + #include + #include + #include + #include + #include + #include + + #include "user-classify.h" + #include "wtmp-helper.h" + #include "daemon.h" + #include "util.h" ++#include "realmd-generated.h" + + #define PATH_PASSWD "/etc/passwd" + #define PATH_SHADOW "/etc/shadow" + #define PATH_GROUP "/etc/group" + + enum { + PROP_0, + PROP_DAEMON_VERSION + }; + + typedef struct { + GDBusConnection *bus_connection; + + GHashTable *users; + gsize number_of_normal_users; + GList *explicitly_requested_users; + + User *autologin; + + GFileMonitor *passwd_monitor; + GFileMonitor *shadow_monitor; + GFileMonitor *group_monitor; + GFileMonitor *gdm_monitor; + GFileMonitor *wtmp_monitor; + + GQueue *pending_list_cached_users; + + guint reload_id; + guint autologin_id; + +@@ -425,110 +426,167 @@ load_entries (Daemon *daemon, + } else { + g_object_ref (user); + } + + /* freeze & update users not already in the new list */ + g_object_freeze_notify (G_OBJECT (user)); + user_update_from_pwent (user, pwent, spent); + + g_hash_table_insert (users, g_strdup (user_get_user_name (user)), user); + g_debug ("loaded user: %s", user_get_user_name (user)); + } + + if (!explicitly_requested) { + user_set_cached (user, TRUE); + } + } + + /* Generator should have cleaned up */ + g_assert (generator_state == NULL); + } + + static GHashTable * + create_users_hash_table (void) + { + return g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); + } + ++static gboolean ++ensure_bus_connection (Daemon *daemon) ++{ ++ DaemonPrivate *priv = daemon_get_instance_private (daemon); ++ g_autoptr (GError) error = NULL; ++ ++ if (priv->bus_connection != NULL) ++ return TRUE; ++ ++ priv->bus_connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); ++ if (priv->bus_connection == NULL) { ++ if (error != NULL) ++ g_critical ("error getting system bus: %s", error->message); ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++static gboolean ++has_network_realms (Daemon *daemon) ++{ ++ DaemonPrivate *priv = daemon_get_instance_private (daemon); ++ g_autoptr (AccountsRealmdProvider) realmd_provider = NULL; ++ g_autoptr (GError) error = NULL; ++ const char *const *realms = NULL; ++ ++ if (!ensure_bus_connection (daemon)) { ++ return FALSE; ++ } ++ ++ realmd_provider = accounts_realmd_provider_proxy_new_sync (priv->bus_connection, ++ G_DBUS_PROXY_FLAGS_NONE, ++ "org.freedesktop.realmd", ++ "/org/freedesktop/realmd", ++ NULL, ++ &error); ++ if (realmd_provider == NULL) { ++ g_debug ("failed to contact realmd: %s", error->message); ++ return FALSE; ++ } ++ ++ realms = accounts_realmd_provider_get_realms (realmd_provider); ++ ++ if (!realms) { ++ g_debug("realmd provider 'Realms' property is unset"); ++ return FALSE; ++ } ++ ++ return realms[0] != NULL; ++} ++ + static void + reload_users (Daemon *daemon) + { + DaemonPrivate *priv = daemon_get_instance_private (daemon); + AccountsAccounts *accounts = ACCOUNTS_ACCOUNTS (daemon); + gboolean had_no_users, has_no_users, had_multiple_users, has_multiple_users; + GHashTable *users; + GHashTable *old_users; + GHashTable *local; + GHashTableIter iter; + gsize number_of_normal_users = 0; + gpointer name, value; + + /* Track the users that we saw during our (re)load */ + users = create_users_hash_table (); + + /* + * NOTE: As we load data from all the sources, notifies are + * frozen in load_entries() and then thawed as we process + * them below. + */ + + /* Load the local users into our hash table */ + load_entries (daemon, users, FALSE, entry_generator_fgetpwent); + local = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_iter_init (&iter, users); + while (g_hash_table_iter_next (&iter, &name, NULL)) + g_hash_table_add (local, name); + + /* and add users to hash table that were explicitly requested */ + load_entries (daemon, users, TRUE, entry_generator_requested_users); + + /* Now add/update users from other sources, possibly non-local */ + load_entries (daemon, users, FALSE, entry_generator_cachedir); + + wtmp_helper_update_login_frequencies (users); + + /* Count the non-system users. Mark which users are local, which are not. */ + g_hash_table_iter_init (&iter, users); + while (g_hash_table_iter_next (&iter, &name, &value)) { + User *user = value; + if (!user_get_system_account (user)) + number_of_normal_users++; + user_update_local_account_property (user, g_hash_table_lookup (local, name) != NULL); + } + g_hash_table_destroy (local); + + had_no_users = accounts_accounts_get_has_no_users (accounts); + has_no_users = number_of_normal_users == 0; + ++ if (has_no_users && has_network_realms (daemon)) { ++ g_debug ("No local users, but network realms detected, presuming there are remote users"); ++ has_no_users = FALSE; ++ } ++ + if (had_no_users != has_no_users) + accounts_accounts_set_has_no_users (accounts, has_no_users); + + had_multiple_users = accounts_accounts_get_has_multiple_users (accounts); + has_multiple_users = number_of_normal_users > 1; + + if (had_multiple_users != has_multiple_users) + accounts_accounts_set_has_multiple_users (accounts, has_multiple_users); + + /* Swap out the users */ + old_users = priv->users; + priv->users = users; + + /* Remove all the old users */ + g_hash_table_iter_init (&iter, old_users); + while (g_hash_table_iter_next (&iter, &name, &value)) { + User *user = value; + User *refreshed_user; + + refreshed_user = g_hash_table_lookup (users, name); + + if (!refreshed_user || (user_get_cached (user) && !user_get_cached (refreshed_user))) { + accounts_accounts_emit_user_deleted (ACCOUNTS_ACCOUNTS (daemon), + user_get_object_path (user)); + user_unregister (user); + } + } + + /* Register all the new users */ + g_hash_table_iter_init (&iter, users); +@@ -766,64 +824,61 @@ daemon_finalize (GObject *object) + priv = daemon_get_instance_private (daemon); + + if (priv->bus_connection != NULL) + g_object_unref (priv->bus_connection); + + g_queue_free_full (priv->pending_list_cached_users, + (GDestroyNotify) list_user_data_free); + + g_list_free_full (priv->explicitly_requested_users, g_free); + + g_hash_table_destroy (priv->users); + + g_hash_table_unref (priv->extension_ifaces); + + G_OBJECT_CLASS (daemon_parent_class)->finalize (object); + } + + static gboolean + register_accounts_daemon (Daemon *daemon) + { + DaemonPrivate *priv = daemon_get_instance_private (daemon); + g_autoptr(GError) error = NULL; + + priv->authority = polkit_authority_get_sync (NULL, &error); + if (priv->authority == NULL) { + if (error != NULL) + g_critical ("error getting polkit authority: %s", error->message); + return FALSE; + } + +- priv->bus_connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); +- if (priv->bus_connection == NULL) { +- if (error != NULL) +- g_critical ("error getting system bus: %s", error->message); ++ if (!ensure_bus_connection (daemon)) { + return FALSE; + } + + if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (daemon), + priv->bus_connection, + "/org/freedesktop/Accounts", + &error)) { + if (error != NULL) + g_critical ("error exporting interface: %s", error->message); + return FALSE; + } + + return TRUE; + } + + Daemon * + daemon_new (void) + { + g_autoptr(Daemon) daemon = NULL; + + daemon = DAEMON (g_object_new (TYPE_DAEMON, NULL)); + + if (!register_accounts_daemon (DAEMON (daemon))) { + return NULL; + } + + return g_steal_pointer (&daemon); + } + + static void +diff --git a/src/meson.build b/src/meson.build +index 20d5276..3970749 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -1,37 +1,38 @@ + sources = [] + + gdbus_headers = [] + + ifaces = [ + ['accounts-generated', 'org.freedesktop.', 'Accounts'], + ['accounts-user-generated', act_namespace + '.', 'User'], ++ ['realmd-generated', 'org.freedesktop.', 'realmd'], + ] + + foreach iface: ifaces + gdbus_sources = gnome.gdbus_codegen( + iface[0], + join_paths(data_dir, iface[1] + iface[2] + '.xml'), + interface_prefix: iface[1], + namespace: 'Accounts', + ) + sources += gdbus_sources + gdbus_headers += gdbus_sources[1] + endforeach + + deps = [ + gio_dep, + gio_unix_dep, + ] + + cflags = [ + '-DLOCALSTATEDIR="@0@"'.format(act_localstatedir), + '-DDATADIR="@0@"'.format(act_datadir), + '-DICONDIR="@0@"'.format(join_paths(act_localstatedir, 'lib', 'AccountsService', 'icons')), + '-DUSERDIR="@0@"'.format(join_paths(act_localstatedir, 'lib', 'AccountsService', 'users')), + ] + + libaccounts_generated = static_library( + 'accounts-generated', + sources: sources, + include_directories: top_inc, + dependencies: deps, +diff --git a/src/org.freedesktop.realmd.xml b/src/org.freedesktop.realmd.xml +new file mode 100644 +index 0000000..c34a47a +--- /dev/null ++++ b/src/org.freedesktop.realmd.xml +@@ -0,0 +1,730 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +-- +2.27.0 + diff --git a/SOURCES/0001-data-don-t-send-change-updates-for-login-history.patch b/SOURCES/0001-data-don-t-send-change-updates-for-login-history.patch new file mode 100644 index 0000000..94d6c7a --- /dev/null +++ b/SOURCES/0001-data-don-t-send-change-updates-for-login-history.patch @@ -0,0 +1,88 @@ +From 50edc5e45bb984576506e7b2bfb4c267ac566099 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 9 May 2019 14:58:34 -0400 +Subject: [PATCH] data: don't send change updates for login-history + +The login-history property of user objects can be quite large. +If wtmp is changed frequently, that can lead to memory fragmentation +in clients. + +Furthermore, most clients never check login-history, so it's +wasted memory and wasted cpu. + +This commit disables change notification for that property. If +a client really needs to get updates, they can manually refresh +their cache when appropriate. +--- + data/org.freedesktop.Accounts.User.xml | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/data/org.freedesktop.Accounts.User.xml b/data/org.freedesktop.Accounts.User.xml +index 8d3fe1c..3b839a3 100644 +--- a/data/org.freedesktop.Accounts.User.xml ++++ b/data/org.freedesktop.Accounts.User.xml +@@ -785,60 +785,61 @@ + + + + The users location. + + + + + + + + + + How often the user has logged in. + + + + + + + + + + The last login time. + + + + + + ++ + + + + The login history for this user. + Each entry in the array represents a login session. The first two + members are the login time and logout time, as timestamps (seconds since the epoch). If the session is still running, the logout time + is 0. + + + The a{sv} member is a dictionary containing additional information + about the session. Possible members include 'type' (with values like ':0', 'tty0', 'pts/0' etc). + + + + + + + + + + The filename of a png file containing the users icon. + + + + + + + + + +-- +2.27.0 + diff --git a/SOURCES/0001-lib-save-os-when-creating-user.patch b/SOURCES/0001-lib-save-os-when-creating-user.patch new file mode 100644 index 0000000..08d953d --- /dev/null +++ b/SOURCES/0001-lib-save-os-when-creating-user.patch @@ -0,0 +1,894 @@ +From 5124220f12a157ff285072a4f786db2c94c3ca8a Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 14 Jan 2021 15:07:34 -0500 +Subject: [PATCH] lib: save os when creating user + +In order to identify that a user has upgraded from rhel 7 to +rhel 8, we need to know what os they were using when created. + +This commit saves that information using a red hat specific +extension to accountsservice. +--- + .../com.redhat.AccountsServiceUser.System.xml | 10 ++ + data/meson.build | 1 + + meson.build | 1 + + meson_post_install.py | 15 +++ + src/libaccountsservice/act-user-manager.c | 123 ++++++++++++++++++ + src/libaccountsservice/meson.build | 7 + + 6 files changed, 157 insertions(+) + create mode 100644 data/com.redhat.AccountsServiceUser.System.xml + +diff --git a/data/com.redhat.AccountsServiceUser.System.xml b/data/com.redhat.AccountsServiceUser.System.xml +new file mode 100644 +index 0000000..67f5f30 +--- /dev/null ++++ b/data/com.redhat.AccountsServiceUser.System.xml +@@ -0,0 +1,10 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/data/meson.build b/data/meson.build +index 4987937..2dc57c2 100644 +--- a/data/meson.build ++++ b/data/meson.build +@@ -1,33 +1,34 @@ + ifaces = files( + act_namespace + '.xml', + act_namespace + '.User.xml', ++ 'com.redhat.AccountsServiceUser.System.xml', + ) + + install_data( + ifaces, + install_dir: dbus_ifaces_dir, + ) + + install_data( + act_namespace + '.conf', + install_dir: dbus_conf_dir, + ) + + service_conf = configuration_data() + service_conf.set('libexecdir', act_libexecdir) + + service = act_namespace + '.service' + + configure_file( + input: service + '.in', + output: service, + configuration: service_conf, + install: true, + install_dir: dbus_sys_dir, + ) + + policy = act_namespace.to_lower() + '.policy' + + i18n.merge_file( + policy, + input: policy + '.in', +diff --git a/meson.build b/meson.build +index 4465a26..e37c451 100644 +--- a/meson.build ++++ b/meson.build +@@ -174,38 +174,39 @@ assert(not enable_systemd or not enable_elogind, 'systemd and elogind support re + if enable_systemd + logind_dep = dependency('libsystemd', version: '>= 186') + endif + + if enable_elogind + logind_dep = dependency('libelogind', version: '>= 229.4') + endif + config_h.set('WITH_SYSTEMD', enable_systemd or enable_elogind) + + subdir('data') + subdir('src') + subdir('po') + + enable_docbook = get_option('docbook') + if enable_docbook + subdir('doc/dbus') + endif + + if get_option('gtk_doc') + subdir('doc/libaccountsservice') + endif + + configure_file( + output: 'config.h', + configuration: config_h, + ) + + meson.add_install_script( + 'meson_post_install.py', + act_localstatedir, ++ act_datadir, + ) + + output = '\n' + meson.project_name() + ' was configured with the following options:\n' + output += '** DocBook documentation build: ' + enable_docbook.to_string() + '\n' + output += '** Administrator group: ' + admin_group + '\n' + output += '** Extra administrator groups: ' + extra_admin_groups + '\n' + output += '** GDM configuration: ' + gdm_conf_file + message(output) +diff --git a/meson_post_install.py b/meson_post_install.py +index 5cc2dc4..e1d5a71 100644 +--- a/meson_post_install.py ++++ b/meson_post_install.py +@@ -1,18 +1,33 @@ + #!/usr/bin/env python3 + + import os + import sys + + destdir = os.environ.get('DESTDIR', '') + localstatedir = os.path.normpath(destdir + os.sep + sys.argv[1]) ++datadir = os.path.normpath(destdir + os.sep + sys.argv[2]) ++interfacedir = os.path.join(datadir, 'accountsservice', 'interfaces') + + # FIXME: meson will not track the creation of these directories + # https://github.com/mesonbuild/meson/blob/master/mesonbuild/scripts/uninstall.py#L39 + dst_dirs = [ + (os.path.join(localstatedir, 'lib', 'AccountsService', 'icons'), 0o775), + (os.path.join(localstatedir, 'lib', 'AccountsService', 'users'), 0o700), ++ (interfacedir, 0o775), + ] + + for (dst_dir, dst_dir_mode) in dst_dirs: + if not os.path.exists(dst_dir): + os.makedirs(dst_dir, mode=dst_dir_mode) ++ ++interface_files = [ ++ 'com.redhat.AccountsServiceUser.System.xml', ++] ++ ++for interface_file in interface_files: ++ src_path = os.path.join('../../dbus-1/interfaces', interface_file) ++ dst_path = os.path.join(interfacedir, interface_file) ++ if not os.path.exists(dst_path): ++ os.symlink(src_path, dst_path) ++ ++ +diff --git a/src/libaccountsservice/act-user-manager.c b/src/libaccountsservice/act-user-manager.c +index 1b5298d..f4598c4 100644 +--- a/src/libaccountsservice/act-user-manager.c ++++ b/src/libaccountsservice/act-user-manager.c +@@ -27,60 +27,61 @@ + #include + #include + #include + #include + #include + + #ifdef HAVE_PATHS_H + #include + #endif /* HAVE_PATHS_H */ + + #include + #include + #include + #include + #include + #include + + #ifdef WITH_SYSTEMD + #include + + /* check if logind is running */ + #define LOGIND_RUNNING() (access("/run/systemd/seats/", F_OK) >= 0) + #endif + + #include "act-user-manager.h" + #include "act-user-private.h" + #include "accounts-generated.h" + #include "ck-manager-generated.h" + #include "ck-seat-generated.h" + #include "ck-session-generated.h" ++#include "com.redhat.AccountsServiceUser.System.h" + + /** + * SECTION:act-user-manager + * @title: ActUserManager + * @short_description: manages ActUser objects + * + * ActUserManager is a manager object that gives access to user + * creation, deletion, enumeration, etc. + * + * There is typically a singleton ActUserManager object, which + * can be obtained by act_user_manager_get_default(). + */ + + /** + * ActUserManager: + * + * A user manager object. + */ + + /** + * ACT_USER_MANAGER_ERROR: + * + * The GError domain for #ActUserManagerError errors + */ + + /** + * ActUserManagerError: + * @ACT_USER_MANAGER_ERROR_FAILED: Generic failure + * @ACT_USER_MANAGER_ERROR_USER_EXISTS: The user already exists + * @ACT_USER_MANAGER_ERROR_USER_DOES_NOT_EXIST: The user does not exist +@@ -165,60 +166,63 @@ typedef struct + ActUser *user; + ActUserManagerFetchUserRequestType type; + union { + char *username; + uid_t uid; + }; + char *object_path; + char *description; + } ActUserManagerFetchUserRequest; + + typedef struct + { + GHashTable *normal_users_by_name; + GHashTable *system_users_by_name; + GHashTable *users_by_object_path; + GHashTable *sessions; + GDBusConnection *connection; + AccountsAccounts *accounts_proxy; + ConsoleKitManager *ck_manager_proxy; + + ActUserManagerSeat seat; + + GSList *new_sessions; + GSList *new_users; + GSList *new_users_inhibiting_load; + GSList *fetch_user_requests; + + GSList *exclude_usernames; + GSList *include_usernames; + ++ char *os_id; ++ char *os_version_id; ++ + guint load_id; + + gboolean is_loaded; + gboolean has_multiple_users; + gboolean getting_sessions; + gboolean list_cached_users_done; + } ActUserManagerPrivate; + + enum { + PROP_0, + PROP_INCLUDE_USERNAMES_LIST, + PROP_EXCLUDE_USERNAMES_LIST, + PROP_IS_LOADED, + PROP_HAS_MULTIPLE_USERS + }; + + enum { + USER_ADDED, + USER_REMOVED, + USER_IS_LOGGED_IN_CHANGED, + USER_CHANGED, + LAST_SIGNAL + }; + + static guint signals [LAST_SIGNAL] = { 0, }; + + static void act_user_manager_class_init (ActUserManagerClass *klass); + static void act_user_manager_init (ActUserManager *user_manager); + static void act_user_manager_finalize (GObject *object); + +@@ -2942,100 +2946,173 @@ ensure_accounts_proxy (ActUserManager *manager) + G_DBUS_PROXY_FLAGS_NONE, + ACCOUNTS_NAME, + ACCOUNTS_PATH, + NULL, + &error); + if (error != NULL) { + g_debug ("ActUserManager: getting account proxy failed: %s", error->message); + return FALSE; + } + + g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (priv->accounts_proxy), G_MAXINT); + + g_object_bind_property (G_OBJECT (priv->accounts_proxy), + "has-multiple-users", + G_OBJECT (manager), + "has-multiple-users", + G_BINDING_SYNC_CREATE); + + g_signal_connect (priv->accounts_proxy, + "user-added", + G_CALLBACK (on_new_user_in_accounts_service), + manager); + g_signal_connect (priv->accounts_proxy, + "user-deleted", + G_CALLBACK (on_user_removed_in_accounts_service), + manager); + + return TRUE; + } + ++static inline gboolean ++is_valid_char (gchar c, ++ gboolean first) ++{ ++ return (!first && g_ascii_isdigit (c)) || ++ c == '_' || ++ g_ascii_isalpha (c); ++} ++ ++static void ++load_os_release (ActUserManager *manager) ++{ ++ ActUserManagerPrivate *priv = act_user_manager_get_instance_private (manager); ++ g_autoptr(GFile) file = NULL; ++ g_autoptr(GError) error = NULL; ++ g_autofree char *contents = NULL; ++ g_auto(GStrv) lines = NULL; ++ size_t i; ++ ++ file = g_file_new_for_path ("/etc/os-release"); ++ ++ if (!g_file_load_contents (file, NULL, &contents, NULL, NULL, &error)) { ++ g_debug ("ActUserManager: couldn't load /etc/os-release: %s", error->message); ++ return; ++ } ++ ++ lines = g_strsplit (contents, "\n", -1); ++ for (i = 0; lines[i] != NULL; i++) { ++ char *p, *name, *name_end, *value, *value_end; ++ ++ p = lines[i]; ++ ++ while (g_ascii_isspace (*p)) ++ p++; ++ ++ if (*p == '#' || *p == '\0') ++ continue; ++ name = p; ++ while (is_valid_char (*p, p == name)) ++ p++; ++ name_end = p; ++ while (g_ascii_isspace (*p)) ++ p++; ++ if (name == name_end || *p != '=') { ++ continue; ++ } ++ *name_end = '\0'; ++ ++ p++; ++ ++ while (g_ascii_isspace (*p)) ++ p++; ++ ++ value = p; ++ value_end = value + strlen (value) - 1; ++ ++ if (value != value_end && *value == '"' && *value_end == '"') { ++ value++; ++ *value_end = '\0'; ++ } ++ ++ if (strcmp (name, "ID") == 0) { ++ g_debug ("ActUserManager: system OS is '%s'", value); ++ priv->os_id = g_strdup (value); ++ } else if (strcmp (name, "VERSION_ID") == 0) { ++ g_debug ("ActUserManager: system OS version is '%s'", value); ++ priv->os_version_id = g_strdup (value); ++ } ++ } ++} ++ + static void + act_user_manager_init (ActUserManager *manager) + { + ActUserManagerPrivate *priv = act_user_manager_get_instance_private (manager); + g_autoptr(GError) error = NULL; + + act_user_manager_error_quark (); /* register dbus errors */ + + /* sessions */ + priv->sessions = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); + + /* users */ + priv->normal_users_by_name = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); + priv->system_users_by_name = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); + priv->users_by_object_path = g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + g_object_unref); + + priv->connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (priv->connection == NULL) { + if (error != NULL) { + g_warning ("Failed to connect to the D-Bus daemon: %s", error->message); + } else { + g_warning ("Failed to connect to the D-Bus daemon"); + } + return; + } + + ensure_accounts_proxy (manager); + ++ load_os_release (manager); ++ + priv->seat.state = ACT_USER_MANAGER_SEAT_STATE_UNLOADED; + } + + static void + act_user_manager_finalize (GObject *object) + { + ActUserManager *manager = ACT_USER_MANAGER (object); + ActUserManagerPrivate *priv = act_user_manager_get_instance_private (manager); + GSList *node; + + g_debug ("ActUserManager: finalizing user manager"); + + g_slist_foreach (priv->new_sessions, + (GFunc) unload_new_session, NULL); + g_slist_free (priv->new_sessions); + + g_slist_foreach (priv->fetch_user_requests, + (GFunc) free_fetch_user_request, NULL); + g_slist_free (priv->fetch_user_requests); + + g_slist_free (priv->new_users_inhibiting_load); + + node = priv->new_users; + while (node != NULL) { + ActUser *user; + GSList *next_node; + + user = ACT_USER (node->data); + next_node = node->next; + +@@ -3071,143 +3148,181 @@ act_user_manager_finalize (GObject *object) + + #ifdef WITH_SYSTEMD + if (priv->seat.session_monitor != NULL) { + sd_login_monitor_unref (priv->seat.session_monitor); + } + + if (priv->seat.session_monitor_stream != NULL) { + g_object_unref (priv->seat.session_monitor_stream); + } + + if (priv->seat.session_monitor_source_id != 0) { + g_source_remove (priv->seat.session_monitor_source_id); + } + #endif + + if (priv->accounts_proxy != NULL) { + g_object_unref (priv->accounts_proxy); + } + + if (priv->load_id > 0) { + g_source_remove (priv->load_id); + priv->load_id = 0; + } + + g_hash_table_destroy (priv->sessions); + + g_hash_table_destroy (priv->normal_users_by_name); + g_hash_table_destroy (priv->system_users_by_name); + g_hash_table_destroy (priv->users_by_object_path); + ++ g_free (priv->os_id); ++ g_free (priv->os_version_id); ++ + G_OBJECT_CLASS (act_user_manager_parent_class)->finalize (object); + } + + /** + * act_user_manager_get_default: + * + * Returns the user manager singleton instance. Calling this function will + * automatically being loading the user list if it isn't loaded already. + * The #ActUserManager:is-loaded property will be set to %TRUE when the users + * are finished loading and then act_user_manager_list_users() can be called. + * + * Returns: (transfer none): user manager object + */ + ActUserManager * + act_user_manager_get_default (void) + { + if (user_manager_object == NULL) { + user_manager_object = g_object_new (ACT_TYPE_USER_MANAGER, NULL); + g_object_add_weak_pointer (user_manager_object, + (gpointer *) &user_manager_object); + act_user_manager_queue_load (user_manager_object); + } + + return ACT_USER_MANAGER (user_manager_object); + } + + /** + * act_user_manager_no_service: + * @manager: a #ActUserManager + * + * Check whether or not the accounts service is running. + * + * Returns: whether or not accounts service is running + */ + gboolean + act_user_manager_no_service (ActUserManager *manager) + { + ActUserManagerPrivate *priv = act_user_manager_get_instance_private (manager); + return priv->accounts_proxy == NULL; + } + ++static void ++save_system_info (ActUserManager *manager, ++ const char *user_path) ++{ ++ ActUserManagerPrivate *priv = act_user_manager_get_instance_private (manager); ++ ActUserSystem *user_system_proxy = NULL; ++ g_autoptr(GError) error = NULL; ++ ++ if (priv->os_id == NULL && priv->os_version_id == NULL) ++ return; ++ ++ user_system_proxy = act_user_system_proxy_new_sync (priv->connection, ++ G_DBUS_PROXY_FLAGS_NONE, ++ ACCOUNTS_NAME, ++ user_path, ++ NULL, ++ &error); ++ if (user_system_proxy != NULL) { ++ if (priv->os_id != NULL) ++ act_user_system_set_id (user_system_proxy, priv->os_id); ++ ++ if (priv->os_version_id != NULL) ++ act_user_system_set_version_id (user_system_proxy, priv->os_version_id); ++ } else { ++ /* probably means accountsservice and lib are out of sync */ ++ g_debug ("ActUserManager: failed to create user system proxy: %s", ++ error? error->message: ""); ++ g_clear_error (&error); ++ } ++ ++ g_clear_object (&user_system_proxy); ++} ++ + /** + * act_user_manager_create_user: + * @manager: a #ActUserManager + * @username: a unix user name + * @fullname: a unix GECOS value + * @accounttype: a #ActUserAccountType + * @error: a #GError + * + * Creates a user account on the system. + * + * Returns: (transfer full): user object + */ + ActUser * + act_user_manager_create_user (ActUserManager *manager, + const char *username, + const char *fullname, + ActUserAccountType accounttype, + GError **error) + { + ActUserManagerPrivate *priv = act_user_manager_get_instance_private (manager); + GError *local_error = NULL; + gboolean res; + g_autofree gchar *path = NULL; + ActUser *user; + + g_debug ("ActUserManager: Creating user '%s', '%s', %d", + username, fullname, accounttype); + + g_assert (priv->accounts_proxy != NULL); + + res = accounts_accounts_call_create_user_sync (priv->accounts_proxy, + username, + fullname, + accounttype, + &path, + NULL, + &local_error); + if (!res) { + g_propagate_error (error, local_error); + return NULL; + } + ++ save_system_info (manager, path); ++ + user = add_new_user_for_object_path (path, manager); + + return user; + } + + static void + act_user_manager_async_complete_handler (GObject *source, + GAsyncResult *result, + gpointer user_data) + { + GTask *task = user_data; + + g_task_return_pointer (task, g_object_ref (result), g_object_unref); + g_object_unref (task); + } + + /** + * act_user_manager_create_user_async: + * @manager: a #ActUserManager + * @username: a unix user name + * @fullname: a unix GECOS value + * @accounttype: a #ActUserAccountType + * @cancellable: (allow-none): optional #GCancellable object, + * %NULL to ignore + * @callback: (scope async): a #GAsyncReadyCallback to call + * when the request is satisfied + * @user_data: (closure): the data to pass to @callback + * + * Asynchronously creates a user account on the system. + * +@@ -3253,106 +3368,111 @@ act_user_manager_create_user_async (ActUserManager *manager, + * @manager: a #ActUserManager + * @result: a #GAsyncResult + * @error: a #GError + * + * Finishes an asynchronous user creation. + * + * See act_user_manager_create_user_async(). + * + * Returns: (transfer full): user object + * + * Since: 0.6.27 + */ + ActUser * + act_user_manager_create_user_finish (ActUserManager *manager, + GAsyncResult *result, + GError **error) + { + ActUserManagerPrivate *priv = act_user_manager_get_instance_private (manager); + GAsyncResult *inner_result; + ActUser *user = NULL; + g_autofree gchar *path = NULL; + GError *remote_error = NULL; + + inner_result = g_task_propagate_pointer (G_TASK (result), error); + if (inner_result == NULL) { + return FALSE; + } + + if (accounts_accounts_call_create_user_finish (priv->accounts_proxy, + &path, inner_result, &remote_error)) { ++ ++ save_system_info (manager, path); ++ + user = add_new_user_for_object_path (path, manager); + } + + if (remote_error) { + g_dbus_error_strip_remote_error (remote_error); + g_propagate_error (error, remote_error); + } + + return user; + } + + /** + * act_user_manager_cache_user: + * @manager: a #ActUserManager + * @username: a user name + * @error: a #GError + * + * Caches a user account so it shows up via act_user_manager_list_users(). + * + * Returns: (transfer full): user object + */ + ActUser * + act_user_manager_cache_user (ActUserManager *manager, + const char *username, + GError **error) + { + ActUserManagerPrivate *priv = act_user_manager_get_instance_private (manager); + GError *local_error = NULL; + gboolean res; + g_autofree gchar *path = NULL; + + g_debug ("ActUserManager: Caching user '%s'", + username); + + g_assert (priv->accounts_proxy != NULL); + + res = accounts_accounts_call_cache_user_sync (priv->accounts_proxy, + username, + &path, + NULL, + &local_error); + if (!res) { + g_propagate_error (error, local_error); + return NULL; + } + ++ save_system_info (manager, path); ++ + return add_new_user_for_object_path (path, manager); + } + + + /** + * act_user_manager_cache_user_async: + * @manager: a #ActUserManager + * @username: a unix user name + * @cancellable: (allow-none): optional #GCancellable object, + * %NULL to ignore + * @callback: (scope async): a #GAsyncReadyCallback to call + * when the request is satisfied + * @user_data: (closure): the data to pass to @callback + * + * Asynchronously caches a user account so it shows up via + * act_user_manager_list_users(). + * + * For more details, see act_user_manager_cache_user(), which + * is the synchronous version of this call. + * + * Since: 0.6.27 + */ + void + act_user_manager_cache_user_async (ActUserManager *manager, + const char *username, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) + { + ActUserManagerPrivate *priv = act_user_manager_get_instance_private (manager); +@@ -3378,60 +3498,63 @@ act_user_manager_cache_user_async (ActUserManager *manager, + * @manager: a #ActUserManager + * @result: a #GAsyncResult + * @error: a #GError + * + * Finishes an asynchronous user caching. + * + * See act_user_manager_cache_user_async(). + * + * Returns: (transfer full): user object + * + * Since: 0.6.27 + */ + ActUser * + act_user_manager_cache_user_finish (ActUserManager *manager, + GAsyncResult *result, + GError **error) + { + ActUserManagerPrivate *priv = act_user_manager_get_instance_private (manager); + GAsyncResult *inner_result; + ActUser *user = NULL; + g_autofree gchar *path = NULL; + GError *remote_error = NULL; + + inner_result = g_task_propagate_pointer (G_TASK (result), error); + if (inner_result == NULL) { + return FALSE; + } + + if (accounts_accounts_call_cache_user_finish (priv->accounts_proxy, + &path, inner_result, &remote_error)) { ++ ++ save_system_info (manager, path); ++ + user = add_new_user_for_object_path (path, manager); + } + + if (remote_error) { + g_dbus_error_strip_remote_error (remote_error); + g_propagate_error (error, remote_error); + } + + return user; + } + + /** + * act_user_manager_uncache_user: + * @manager: a #ActUserManager + * @username: a user name + * @error: a #GError + * + * Releases all metadata about a user account, including icon, + * language and session. If the user account is from a remote + * server and the user has never logged in before, then that + * account will no longer show up in ListCachedUsers() output. + * + * Returns: %TRUE if successful, otherwise %FALSE + */ + gboolean + act_user_manager_uncache_user (ActUserManager *manager, + const char *username, + GError **error) + { + ActUserManagerPrivate *priv = act_user_manager_get_instance_private (manager); +diff --git a/src/libaccountsservice/meson.build b/src/libaccountsservice/meson.build +index 4e134db..9e85bba 100644 +--- a/src/libaccountsservice/meson.build ++++ b/src/libaccountsservice/meson.build +@@ -21,60 +21,67 @@ enum_types = 'act-user-enum-types' + + enum_sources = gnome.mkenums( + enum_types, + sources: headers, + c_template: enum_types + '.c.template', + h_template: enum_types + '.h.template', + install_header: true, + install_dir: join_paths(act_pkgincludedir, subdir), + ) + + dbus_sources = [] + + ifaces = [ + 'Manager', + 'Seat', + 'Session', + ] + + namespace = 'ConsoleKit' + prefix = 'org.freedesktop.' + namespace + + foreach iface: ifaces + dbus_sources += gnome.gdbus_codegen( + 'ck-@0@-generated'.format(iface.to_lower()), + '@0@.@1@.xml'.format(prefix, iface), + interface_prefix: prefix, + namespace: namespace, + ) + endforeach + ++dbus_sources += gnome.gdbus_codegen( ++ 'com.redhat.AccountsServiceUser.System', ++ join_paths(data_dir, 'com.redhat.AccountsServiceUser.System.xml'), ++ interface_prefix: 'com.redhat.AccountsService', ++ namespace: 'Act', ++) ++ + deps = [ + crypt_dep, + gio_unix_dep, + glib_dep, + libaccounts_generated_dep, + ] + + symbol_map = join_paths(meson.current_source_dir(), 'symbol.map') + ldflags = cc.get_supported_link_arguments('-Wl,--version-script,@0@'.format(symbol_map)) + + if enable_systemd or enable_elogind + deps += logind_dep + endif + + libaccountsservice = shared_library( + act_name, + sources: sources + enum_sources + dbus_sources, + version: libversion, + include_directories: top_inc, + dependencies: deps, + c_args: '-DG_LOG_DOMAIN="@0@"'.format(meson.project_name()), + link_args: ldflags, + link_depends: symbol_map, + install: true, + ) + + libaccountsservice_dep = declare_dependency( + sources: enum_sources[1], + include_directories: include_directories('.'), + dependencies: [gio_dep, glib_dep], +-- +2.27.0 + diff --git a/SOURCES/0001-user-Introduce-user-templates-for-setting-default-se.patch b/SOURCES/0001-user-Introduce-user-templates-for-setting-default-se.patch new file mode 100644 index 0000000..001de67 --- /dev/null +++ b/SOURCES/0001-user-Introduce-user-templates-for-setting-default-se.patch @@ -0,0 +1,925 @@ +From 72427bd4fcae931298c670093f9cbd34ad58f59a Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 4 Aug 2021 19:54:59 -0400 +Subject: [PATCH] user: Introduce user templates for setting default session + etc + +At the moment there's no easy way to set a default session, or +face icon or whatever for all users. If a user has never logged in +before, we just generate their cache file from hardcoded defaults. + +This commit introduces a template system to make it possible for +admins to set up defaults on their own. + +Admins can write either +/etc/accountsservice/user-templates/administrator +or +/etc/accountsservice/user-templates/standard + +files. These files follow the same format as + +/var/lib/AccountsService/users/username + +files, but will support substituting $HOME and $USER to the appropriate +user specific values. + +User templates also support an additional group [Template] that +have an additional key EnvironmentFiles that specify a list +of environment files to load (files with KEY=VALUE pairs in them). +Any keys listed in those environment files will also get substituted. +--- + data/administrator | 6 + + data/meson.build | 10 ++ + data/standard | 6 + + src/daemon.c | 8 +- + src/meson.build | 1 + + src/user.c | 284 ++++++++++++++++++++++++++++++++++++++++++++- + src/user.h | 3 +- + 7 files changed, 305 insertions(+), 13 deletions(-) + create mode 100644 data/administrator + create mode 100644 data/standard + +diff --git a/data/administrator b/data/administrator +new file mode 100644 +index 0000000..ea043c9 +--- /dev/null ++++ b/data/administrator +@@ -0,0 +1,6 @@ ++[Template] ++#EnvironmentFiles=/etc/os-release; ++ ++[User] ++Session= ++Icon=${HOME}/.face +diff --git a/data/meson.build b/data/meson.build +index 2dc57c2..7d9bdcd 100644 +--- a/data/meson.build ++++ b/data/meson.build +@@ -22,30 +22,40 @@ service = act_namespace + '.service' + configure_file( + input: service + '.in', + output: service, + configuration: service_conf, + install: true, + install_dir: dbus_sys_dir, + ) + + policy = act_namespace.to_lower() + '.policy' + + i18n.merge_file( + policy, + input: policy + '.in', + output: policy, + po_dir: po_dir, + install: true, + install_dir: policy_dir, + ) + + if install_systemd_unit_dir + service = 'accounts-daemon.service' + + configure_file( + input: service + '.in', + output: service, + configuration: service_conf, + install: true, + install_dir: systemd_system_unit_dir, + ) + endif ++ ++install_data( ++ 'administrator', ++ install_dir: join_paths(act_datadir, 'accountsservice', 'user-templates'), ++) ++ ++install_data( ++ 'standard', ++ install_dir: join_paths(act_datadir, 'accountsservice', 'user-templates'), ++) +diff --git a/data/standard b/data/standard +new file mode 100644 +index 0000000..ea043c9 +--- /dev/null ++++ b/data/standard +@@ -0,0 +1,6 @@ ++[Template] ++#EnvironmentFiles=/etc/os-release; ++ ++[User] ++Session= ++Icon=${HOME}/.face +diff --git a/src/daemon.c b/src/daemon.c +index 5ce0216..66ac7ba 100644 +--- a/src/daemon.c ++++ b/src/daemon.c +@@ -298,69 +298,63 @@ entry_generator_cachedir (Daemon *daemon, + break; + + /* Only load files in this directory */ + filename = g_build_filename (USERDIR, name, NULL); + regular = g_file_test (filename, G_FILE_TEST_IS_REGULAR); + + if (regular) { + errno = 0; + pwent = getpwnam (name); + if (pwent != NULL) { + *shadow_entry = getspnam (pwent->pw_name); + + return pwent; + } else if (errno == 0) { + g_debug ("user '%s' in cache dir but not present on system, removing", name); + remove_cache_files (name); + } + else { + g_warning ("failed to check if user '%s' in cache dir is present on system: %s", + name, g_strerror (errno)); + } + } + } + + /* Last iteration */ + g_dir_close (dir); + + /* Update all the users from the files in the cache dir */ + g_hash_table_iter_init (&iter, users); + while (g_hash_table_iter_next (&iter, &key, &value)) { +- const gchar *name = key; + User *user = value; +- g_autofree gchar *filename = NULL; +- g_autoptr(GKeyFile) key_file = NULL; + +- filename = g_build_filename (USERDIR, name, NULL); +- key_file = g_key_file_new (); +- if (g_key_file_load_from_file (key_file, filename, 0, NULL)) +- user_update_from_keyfile (user, key_file); ++ user_update_from_cache (user); + } + + *state = NULL; + return NULL; + } + + static struct passwd * + entry_generator_requested_users (Daemon *daemon, + GHashTable *users, + gpointer *state, + struct spwd **shadow_entry) + { + DaemonPrivate *priv = daemon_get_instance_private (daemon); + struct passwd *pwent; + GList *node; + + /* First iteration */ + if (*state == NULL) { + *state = priv->explicitly_requested_users; + } + + /* Every iteration */ + + if (g_hash_table_size (users) < MAX_LOCAL_USERS) { + node = *state; + while (node != NULL) { + const char *name; + + name = node->data; + node = node->next; +diff --git a/src/meson.build b/src/meson.build +index 3970749..d3b0cb9 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -1,59 +1,60 @@ + sources = [] + + gdbus_headers = [] + + ifaces = [ + ['accounts-generated', 'org.freedesktop.', 'Accounts'], + ['accounts-user-generated', act_namespace + '.', 'User'], + ['realmd-generated', 'org.freedesktop.', 'realmd'], + ] + + foreach iface: ifaces + gdbus_sources = gnome.gdbus_codegen( + iface[0], + join_paths(data_dir, iface[1] + iface[2] + '.xml'), + interface_prefix: iface[1], + namespace: 'Accounts', + ) + sources += gdbus_sources + gdbus_headers += gdbus_sources[1] + endforeach + + deps = [ + gio_dep, + gio_unix_dep, + ] + + cflags = [ + '-DLOCALSTATEDIR="@0@"'.format(act_localstatedir), + '-DDATADIR="@0@"'.format(act_datadir), ++ '-DSYSCONFDIR="@0@"'.format(act_sysconfdir), + '-DICONDIR="@0@"'.format(join_paths(act_localstatedir, 'lib', 'AccountsService', 'icons')), + '-DUSERDIR="@0@"'.format(join_paths(act_localstatedir, 'lib', 'AccountsService', 'users')), + ] + + libaccounts_generated = static_library( + 'accounts-generated', + sources: sources, + include_directories: top_inc, + dependencies: deps, + c_args: cflags, + ) + + libaccounts_generated_dep = declare_dependency( + sources: gdbus_headers, + include_directories: include_directories('.'), + dependencies: gio_dep, + link_with: libaccounts_generated, + ) + + sources = files( + 'daemon.c', + 'extensions.c', + 'main.c', + 'user.c', + 'user-classify.c', + 'util.c', + 'wtmp-helper.c', + ) + + deps = [ +diff --git a/src/user.c b/src/user.c +index 9f57af5..16c7721 100644 +--- a/src/user.c ++++ b/src/user.c +@@ -43,127 +43,384 @@ + #include + + #include "user-classify.h" + #include "daemon.h" + #include "user.h" + #include "accounts-user-generated.h" + #include "util.h" + + struct User { + AccountsUserSkeleton parent; + + GDBusConnection *system_bus_connection; + gchar *object_path; + + Daemon *daemon; + + GKeyFile *keyfile; + + gid_t gid; + gint64 expiration_time; + gint64 last_change_time; + gint64 min_days_between_changes; + gint64 max_days_between_changes; + gint64 days_to_warn; + gint64 days_after_expiration_until_lock; + GVariant *login_history; + gchar *icon_file; + gchar *default_icon_file; + gboolean account_expiration_policy_known; + gboolean cached; ++ gboolean template_loaded; + + guint *extension_ids; + guint n_extension_ids; + + guint changed_timeout_id; + }; + + typedef struct UserClass + { + AccountsUserSkeletonClass parent_class; + } UserClass; + + static void user_accounts_user_iface_init (AccountsUserIface *iface); ++static void user_update_from_keyfile (User *user, GKeyFile *keyfile); + + G_DEFINE_TYPE_WITH_CODE (User, user, ACCOUNTS_TYPE_USER_SKELETON, G_IMPLEMENT_INTERFACE (ACCOUNTS_TYPE_USER, user_accounts_user_iface_init)); + + static gint + account_type_from_pwent (struct passwd *pwent) + { + struct group *grp; + gint i; + + if (pwent->pw_uid == 0) { + g_debug ("user is root so account type is administrator"); + return ACCOUNT_TYPE_ADMINISTRATOR; + } + + grp = getgrnam (ADMIN_GROUP); + if (grp == NULL) { + g_debug (ADMIN_GROUP " group not found"); + return ACCOUNT_TYPE_STANDARD; + } + + for (i = 0; grp->gr_mem[i] != NULL; i++) { + if (g_strcmp0 (grp->gr_mem[i], pwent->pw_name) == 0) { + return ACCOUNT_TYPE_ADMINISTRATOR; + } + } + + return ACCOUNT_TYPE_STANDARD; + } + + static void + user_reset_icon_file (User *user) + { + const char *icon_file; + gboolean icon_is_default; + const char *home_dir; + + icon_file = accounts_user_get_icon_file (ACCOUNTS_USER (user)); + + if (icon_file == NULL || g_strcmp0 (icon_file, user->default_icon_file) == 0) { + icon_is_default = TRUE; + } else { + icon_is_default = FALSE; + } + + g_free (user->default_icon_file); + home_dir = accounts_user_get_home_directory (ACCOUNTS_USER (user)); + + user->default_icon_file = g_build_filename (home_dir, ".face", NULL); + + if (icon_is_default) { + accounts_user_set_icon_file (ACCOUNTS_USER (user), user->default_icon_file); + } + } + ++static gboolean ++user_has_cache_file (User *user) ++{ ++ g_autofree char *filename = NULL; ++ ++ filename = g_build_filename (USERDIR, user_get_user_name (user), NULL); ++ ++ return g_file_test (filename, G_FILE_TEST_EXISTS); ++} ++ ++static gboolean ++is_valid_shell_identifier_character (char c, ++ gboolean first) ++{ ++ return (!first && g_ascii_isdigit (c)) || ++ c == '_' || ++ g_ascii_isalpha (c); ++} ++ ++static char * ++expand_template_variables (User *user, ++ GHashTable *template_variables, ++ const char *str) ++{ ++ GString *s = g_string_new (""); ++ const char *p, *start; ++ char c; ++ ++ p = str; ++ while (*p) { ++ c = *p; ++ if (c == '\\') { ++ p++; ++ c = *p; ++ if (c != '\0') { ++ p++; ++ switch (c) { ++ case '\\': ++ g_string_append_c (s, '\\'); ++ break; ++ case '$': ++ g_string_append_c (s, '$'); ++ break; ++ default: ++ g_string_append_c (s, '\\'); ++ g_string_append_c (s, c); ++ break; ++ } ++ } ++ } else if (c == '$') { ++ gboolean brackets = FALSE; ++ p++; ++ if (*p == '{') { ++ brackets = TRUE; ++ p++; ++ } ++ start = p; ++ while (*p != '\0' && ++ is_valid_shell_identifier_character (*p, p == start)) ++ p++; ++ if (p == start || (brackets && *p != '}')) { ++ g_string_append_c (s, '$'); ++ if (brackets) ++ g_string_append_c (s, '{'); ++ g_string_append_len (s, start, p - start); ++ } else { ++ g_autofree char *variable = NULL; ++ const char *value; ++ ++ if (brackets && *p == '}') ++ p++; ++ ++ variable = g_strndup (start, p - start - 1); ++ ++ value = g_hash_table_lookup (template_variables, variable); ++ if (value) { ++ g_string_append (s, value); ++ } ++ } ++ } else { ++ p++; ++ g_string_append_c (s, c); ++ } ++ } ++ return g_string_free (s, FALSE); ++} ++ ++static void ++load_template_environment_file (User *user, ++ GHashTable *variables, ++ const char *file) ++{ ++ g_autofree char *contents = NULL; ++ g_auto (GStrv) lines = NULL; ++ g_autoptr (GError) error = NULL; ++ gboolean file_loaded; ++ size_t i; ++ ++ file_loaded = g_file_get_contents (file, &contents, NULL, &error); ++ ++ if (!file_loaded) { ++ g_debug ("Couldn't load template environment file %s: %s", ++ file, error->message); ++ return; ++ } ++ ++ lines = g_strsplit (contents, "\n", -1); ++ ++ for (i = 0; lines[i] != NULL; i++) { ++ char *p; ++ char *variable_end; ++ const char *variable; ++ const char *value; ++ ++ p = lines[i]; ++ while (g_ascii_isspace (*p)) ++ p++; ++ if (*p == '#' || *p == '\0') ++ continue; ++ variable = p; ++ while (is_valid_shell_identifier_character (*p, p == variable)) ++ p++; ++ variable_end = p; ++ while (g_ascii_isspace (*p)) ++ p++; ++ if (variable_end == variable || *p != '=') { ++ g_debug ("template environment file %s has invalid line '%s'\n", file, lines[i]); ++ continue; ++ } ++ *variable_end = '\0'; ++ p++; ++ while (g_ascii_isspace (*p)) ++ p++; ++ value = p; ++ ++ if (g_hash_table_lookup (variables, variable) == NULL) { ++ g_hash_table_insert (variables, ++ g_strdup (variable), ++ g_strdup (value)); ++ } ++ ++ } ++} ++ ++static void ++initialize_template_environment (User *user, ++ GHashTable *variables, ++ const char * const *files) ++{ ++ size_t i; ++ ++ g_hash_table_insert (variables, g_strdup ("HOME"), g_strdup (accounts_user_get_home_directory (ACCOUNTS_USER (user)))); ++ g_hash_table_insert (variables, g_strdup ("USER"), g_strdup (user_get_user_name (user))); ++ ++ if (files == NULL) ++ return; ++ ++ for (i = 0; files[i] != NULL; i++) { ++ load_template_environment_file (user, variables, files[i]); ++ } ++} ++ ++static void ++user_update_from_template (User *user) ++{ ++ g_autofree char *filename = NULL; ++ g_autoptr (GKeyFile) key_file = NULL; ++ g_autoptr (GError) error = NULL; ++ g_autoptr (GHashTable) template_variables = NULL; ++ g_auto (GStrv) template_environment_files = NULL; ++ gboolean key_file_loaded = FALSE; ++ const char * const *system_dirs[] = { ++ (const char *[]) { "/run", SYSCONFDIR, NULL }, ++ g_get_system_data_dirs (), ++ NULL ++ }; ++ g_autoptr (GPtrArray) dirs = NULL; ++ AccountType account_type; ++ const char *account_type_string; ++ size_t i, j; ++ g_autofree char *contents = NULL; ++ g_autofree char *expanded = NULL; ++ g_auto (GStrv) lines = NULL; ++ ++ if (user->template_loaded) ++ return; ++ ++ filename = g_build_filename (USERDIR, ++ accounts_user_get_user_name (ACCOUNTS_USER (user)), ++ NULL); ++ ++ account_type = accounts_user_get_account_type (ACCOUNTS_USER (user)); ++ if (account_type == ACCOUNT_TYPE_ADMINISTRATOR) ++ account_type_string = "administrator"; ++ else ++ account_type_string = "standard"; ++ ++ dirs = g_ptr_array_new (); ++ for (i = 0; system_dirs[i] != NULL; i++) { ++ for (j = 0; system_dirs[i][j] != NULL; j++) { ++ char *dir; ++ ++ dir = g_build_filename (system_dirs[i][j], ++ "accountsservice", ++ "user-templates", ++ NULL); ++ g_ptr_array_add (dirs, dir); ++ } ++ } ++ g_ptr_array_add (dirs, NULL); ++ ++ key_file = g_key_file_new (); ++ key_file_loaded = g_key_file_load_from_dirs (key_file, ++ account_type_string, ++ (const char **) dirs->pdata, ++ NULL, ++ G_KEY_FILE_KEEP_COMMENTS, ++ &error); ++ ++ if (!key_file_loaded) { ++ g_debug ("failed to load user template: %s", error->message); ++ return; ++ } ++ ++ template_variables = g_hash_table_new_full (g_str_hash, ++ g_str_equal, ++ g_free, ++ g_free); ++ ++ template_environment_files = g_key_file_get_string_list (key_file, ++ "Template", ++ "EnvironmentFiles", ++ NULL, ++ NULL); ++ ++ initialize_template_environment (user, template_variables, (const char * const *) template_environment_files); ++ ++ g_key_file_remove_group (key_file, "Template", NULL); ++ contents = g_key_file_to_data (key_file, NULL, NULL); ++ lines = g_strsplit (contents, "\n", -1); ++ ++ expanded = expand_template_variables (user, template_variables, contents); ++ ++ key_file_loaded = g_key_file_load_from_data (key_file, ++ expanded, ++ strlen (expanded), ++ G_KEY_FILE_KEEP_COMMENTS, ++ &error); ++ ++ if (key_file_loaded) ++ user_update_from_keyfile (user, key_file); ++ ++ user->template_loaded = key_file_loaded; ++} ++ + void + user_update_from_pwent (User *user, + struct passwd *pwent, + struct spwd *spent) + { + g_autofree gchar *real_name = NULL; + gboolean is_system_account; + const gchar *passwd; + gboolean locked; + PasswordMode mode; + AccountType account_type; + + g_object_freeze_notify (G_OBJECT (user)); + + if (pwent->pw_gecos && pwent->pw_gecos[0] != '\0') { + gchar *first_comma = NULL; + gchar *valid_utf8_name = NULL; + + if (g_utf8_validate (pwent->pw_gecos, -1, NULL)) { + valid_utf8_name = pwent->pw_gecos; + first_comma = g_utf8_strchr (valid_utf8_name, -1, ','); + } + else { + g_warning ("User %s has invalid UTF-8 in GECOS field. " + "It would be a good thing to check /etc/passwd.", + pwent->pw_name ? pwent->pw_name : ""); + } + + if (first_comma) { + real_name = g_strndup (valid_utf8_name, +@@ -212,134 +469,150 @@ user_update_from_pwent (User *user, + accounts_user_set_locked (ACCOUNTS_USER (user), locked); + + if (passwd == NULL || passwd[0] != 0) { + mode = PASSWORD_MODE_REGULAR; + } + else { + mode = PASSWORD_MODE_NONE; + } + + if (spent) { + if (spent->sp_lstchg == 0) { + mode = PASSWORD_MODE_SET_AT_LOGIN; + } + + user->expiration_time = spent->sp_expire; + user->last_change_time = spent->sp_lstchg; + user->min_days_between_changes = spent->sp_min; + user->max_days_between_changes = spent->sp_max; + user->days_to_warn = spent->sp_warn; + user->days_after_expiration_until_lock = spent->sp_inact; + user->account_expiration_policy_known = TRUE; + } + + accounts_user_set_password_mode (ACCOUNTS_USER (user), mode); + is_system_account = !user_classify_is_human (accounts_user_get_uid (ACCOUNTS_USER (user)), + accounts_user_get_user_name (ACCOUNTS_USER (user)), + accounts_user_get_shell (ACCOUNTS_USER (user)), + passwd); + accounts_user_set_system_account (ACCOUNTS_USER (user), is_system_account); + ++ if (!user_has_cache_file (user)) ++ user_update_from_template (user); + g_object_thaw_notify (G_OBJECT (user)); + } + +-void ++static void + user_update_from_keyfile (User *user, + GKeyFile *keyfile) + { + gchar *s; + +- g_object_freeze_notify (G_OBJECT (user)); +- + s = g_key_file_get_string (keyfile, "User", "Language", NULL); + if (s != NULL) { + accounts_user_set_language (ACCOUNTS_USER (user), s); + g_clear_pointer (&s, g_free); + } + + s = g_key_file_get_string (keyfile, "User", "XSession", NULL); + if (s != NULL) { + accounts_user_set_xsession (ACCOUNTS_USER (user), s); + + /* for backward compat */ + accounts_user_set_session (ACCOUNTS_USER (user), s); + g_clear_pointer (&s, g_free); + } + + s = g_key_file_get_string (keyfile, "User", "Session", NULL); + if (s != NULL) { + accounts_user_set_session (ACCOUNTS_USER (user), s); + g_clear_pointer (&s, g_free); + } + + s = g_key_file_get_string (keyfile, "User", "SessionType", NULL); + if (s != NULL) { + accounts_user_set_session_type (ACCOUNTS_USER (user), s); + g_clear_pointer (&s, g_free); + } + + s = g_key_file_get_string (keyfile, "User", "Email", NULL); + if (s != NULL) { + accounts_user_set_email (ACCOUNTS_USER (user), s); + g_clear_pointer (&s, g_free); + } + + s = g_key_file_get_string (keyfile, "User", "Location", NULL); + if (s != NULL) { + accounts_user_set_location (ACCOUNTS_USER (user), s); + g_clear_pointer (&s, g_free); + } + + s = g_key_file_get_string (keyfile, "User", "PasswordHint", NULL); + if (s != NULL) { + accounts_user_set_password_hint (ACCOUNTS_USER (user), s); + g_clear_pointer (&s, g_free); + } + + s = g_key_file_get_string (keyfile, "User", "Icon", NULL); + if (s != NULL) { + accounts_user_set_icon_file (ACCOUNTS_USER (user), s); + g_clear_pointer (&s, g_free); + } + + if (g_key_file_has_key (keyfile, "User", "SystemAccount", NULL)) { + gboolean system_account; + + system_account = g_key_file_get_boolean (keyfile, "User", "SystemAccount", NULL); + accounts_user_set_system_account (ACCOUNTS_USER (user), system_account); + } + + g_clear_pointer (&user->keyfile, g_key_file_unref); + user->keyfile = g_key_file_ref (keyfile); ++} ++ ++void ++user_update_from_cache (User *user) ++{ ++ g_autofree gchar *filename = NULL; ++ g_autoptr(GKeyFile) key_file = NULL; ++ ++ filename = g_build_filename (USERDIR, accounts_user_get_user_name (ACCOUNTS_USER (user)), NULL); ++ ++ key_file = g_key_file_new (); ++ ++ if (!g_key_file_load_from_file (key_file, filename, 0, NULL)) ++ return; ++ ++ g_object_freeze_notify (G_OBJECT (user)); ++ user_update_from_keyfile (user, key_file); + user_set_cached (user, TRUE); + user_set_saved (user, TRUE); +- + g_object_thaw_notify (G_OBJECT (user)); + } + + void + user_update_local_account_property (User *user, + gboolean local) + { + accounts_user_set_local_account (ACCOUNTS_USER (user), local); + } + + void + user_update_system_account_property (User *user, + gboolean system) + { + accounts_user_set_system_account (ACCOUNTS_USER (user), system); + } + + static void + user_save_to_keyfile (User *user, + GKeyFile *keyfile) + { + g_key_file_remove_group (keyfile, "User", NULL); + + if (accounts_user_get_email (ACCOUNTS_USER (user))) + g_key_file_set_string (keyfile, "User", "Email", accounts_user_get_email (ACCOUNTS_USER (user))); + + if (accounts_user_get_language (ACCOUNTS_USER (user))) + g_key_file_set_string (keyfile, "User", "Language", accounts_user_get_language (ACCOUNTS_USER (user))); + + if (accounts_user_get_session (ACCOUNTS_USER (user))) +@@ -509,60 +782,63 @@ user_extension_set_property (User *user, + if (!prev || !g_str_equal (printed, prev)) { + g_key_file_set_value (user->keyfile, interface->name, property->name, printed); + + /* Emit a change signal. Use invalidation + * because the data may not be world-readable. + */ + g_dbus_connection_emit_signal (g_dbus_method_invocation_get_connection (invocation), + NULL, /* destination_bus_name */ + g_dbus_method_invocation_get_object_path (invocation), + "org.freedesktop.DBus.Properties", "PropertiesChanged", + g_variant_new_parsed ("( %s, %a{sv}, [ %s ] )", + interface->name, NULL, property->name), + NULL); + + accounts_user_emit_changed (ACCOUNTS_USER (user)); + save_extra_data (user); + } + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("()")); + } + + static void + user_extension_authentication_done (Daemon *daemon, + User *user, + GDBusMethodInvocation *invocation, + gpointer user_data) + { + GDBusInterfaceInfo *interface = user_data; + const gchar *method_name; + ++ if (!user_has_cache_file (user)) ++ user_update_from_template (user); ++ + method_name = g_dbus_method_invocation_get_method_name (invocation); + + if (g_str_equal (method_name, "Get")) + user_extension_get_property (user, daemon, interface, invocation); + else if (g_str_equal (method_name, "GetAll")) + user_extension_get_all_properties (user, daemon, interface, invocation); + else if (g_str_equal (method_name, "Set")) + user_extension_set_property (user, daemon, interface, invocation); + else + g_assert_not_reached (); + } + + static void + user_extension_method_call (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) + { + User *user = user_data; + GDBusInterfaceInfo *iface_info; + const gchar *annotation_name; + const gchar *action_id; + gint uid; + gint i; + + /* We don't allow method calls on extension interfaces, so we +diff --git a/src/user.h b/src/user.h +index b3b3380..eb81918 100644 +--- a/src/user.h ++++ b/src/user.h +@@ -30,58 +30,57 @@ + #include "types.h" + + G_BEGIN_DECLS + + #define TYPE_USER (user_get_type ()) + #define USER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), TYPE_USER, User)) + #define IS_USER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), TYPE_USER)) + + typedef enum { + ACCOUNT_TYPE_STANDARD, + ACCOUNT_TYPE_ADMINISTRATOR, + #define ACCOUNT_TYPE_LAST ACCOUNT_TYPE_ADMINISTRATOR + } AccountType; + + typedef enum { + PASSWORD_MODE_REGULAR, + PASSWORD_MODE_SET_AT_LOGIN, + PASSWORD_MODE_NONE, + #define PASSWORD_MODE_LAST PASSWORD_MODE_NONE + } PasswordMode; + + /* local methods */ + + GType user_get_type (void) G_GNUC_CONST; + User * user_new (Daemon *daemon, + uid_t uid); + + void user_update_from_pwent (User *user, + struct passwd *pwent, + struct spwd *spent); +-void user_update_from_keyfile (User *user, +- GKeyFile *keyfile); ++void user_update_from_cache (User *user); + void user_update_local_account_property (User *user, + gboolean local); + void user_update_system_account_property (User *user, + gboolean system); + gboolean user_get_cached (User *user); + void user_set_cached (User *user, + gboolean cached); + void user_set_saved (User *user, + gboolean saved); + + void user_register (User *user); + void user_unregister (User *user); + void user_changed (User *user); + + void user_save (User *user); + + const gchar * user_get_user_name (User *user); + gboolean user_get_system_account (User *user); + gboolean user_get_local_account (User *user); + const gchar * user_get_object_path (User *user); + uid_t user_get_uid (User *user); + const gchar * user_get_shell (User *user); + + G_END_DECLS + + #endif +-- +2.27.0 + diff --git a/SOURCES/0002-main-Allow-cache-files-to-be-marked-immutable.patch b/SOURCES/0002-main-Allow-cache-files-to-be-marked-immutable.patch new file mode 100644 index 0000000..991a879 --- /dev/null +++ b/SOURCES/0002-main-Allow-cache-files-to-be-marked-immutable.patch @@ -0,0 +1,195 @@ +From 12127d9c04e8151c51bd14114dce424ff8448345 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 9 Sep 2021 09:40:49 -0400 +Subject: [PATCH 2/2] main: Allow cache files to be marked immutable + +At the moment, at start up we unconditionally reset permission of all +cache files in /var/lib/AccountsService/users. If the mode of the files +can't be reset, accountsservice fails to start. + +But there's a situation where we should proceed anyway: If the +mode is already correct, and the file is read-only, there is no reason +to refuse to proceed. + +This commit changes the code to explicitly validate the permissions of +the file before failing. +--- + src/main.c | 29 +++++++++++++++++++++++++---- + 1 file changed, 25 insertions(+), 4 deletions(-) + +diff --git a/src/main.c b/src/main.c +index 01cb617..36a2d7e 100644 +--- a/src/main.c ++++ b/src/main.c +@@ -16,143 +16,164 @@ + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Written by: Matthias Clasen + */ + + #include "config.h" + + #include + #include + #include + #include + #include + #include + #include + + #include + #include + #include + #include + + #include "daemon.h" + + #define NAME_TO_CLAIM "org.freedesktop.Accounts" + + static gboolean + ensure_directory (const char *path, + gint mode, + GError **error) + { ++ GStatBuf stat_buffer = { 0 }; ++ + if (g_mkdir_with_parents (path, mode) < 0) { + g_set_error (error, + G_FILE_ERROR, + g_file_error_from_errno (errno), + "Failed to create directory %s: %m", + path); + return FALSE; + } + +- if (g_chmod (path, mode) < 0) { ++ g_chmod (path, mode); ++ ++ if (g_stat (path, &stat_buffer) < 0) { ++ g_clear_error (error); ++ + g_set_error (error, + G_FILE_ERROR, + g_file_error_from_errno (errno), +- "Failed to change permissions of directory %s: %m", ++ "Failed to validate permissions of directory %s: %m", + path); + return FALSE; + } + ++ if ((stat_buffer.st_mode & ~S_IFMT) != mode) { ++ g_set_error (error, ++ G_FILE_ERROR, ++ g_file_error_from_errno (errno), ++ "Directory %s has wrong mode %o; it should be %o", ++ path, stat_buffer.st_mode, mode); ++ return FALSE; ++ } ++ + return TRUE; + } + + static gboolean + ensure_file_permissions (const char *dir_path, + gint file_mode, + GError **error) + { + GDir *dir = NULL; + const gchar *filename; + gint errsv = 0; + + dir = g_dir_open (dir_path, 0, error); + if (dir == NULL) + return FALSE; + + while ((filename = g_dir_read_name (dir)) != NULL) { ++ GStatBuf stat_buffer = { 0 }; ++ + gchar *file_path = g_build_filename (dir_path, filename, NULL); + + g_debug ("Changing permission of %s to %04o", file_path, file_mode); +- if (g_chmod (file_path, file_mode) < 0) ++ g_chmod (file_path, file_mode); ++ ++ if (g_stat (file_path, &stat_buffer) < 0) + errsv = errno; + ++ if ((stat_buffer.st_mode & ~S_IFMT) != file_mode) ++ errsv = EACCES; ++ + g_free (file_path); + } + + g_dir_close (dir); + + /* Report any errors after all chmod()s have been attempted. */ + if (errsv != 0) { + g_set_error (error, + G_FILE_ERROR, + g_file_error_from_errno (errsv), + "Failed to change permissions of files in directory %s: %m", + dir_path); + return FALSE; + } + + return TRUE; + } + + static void + on_bus_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) + { + GMainLoop *loop = user_data; + Daemon *daemon; + g_autoptr(GError) error = NULL; + + if (!ensure_directory (ICONDIR, 0775, &error) || + !ensure_directory (USERDIR, 0700, &error) || + !ensure_file_permissions (USERDIR, 0600, &error)) { + g_printerr ("%s\n", error->message); + g_main_loop_quit (loop); + return; + } + + daemon = daemon_new (); + if (daemon == NULL) { + g_printerr ("Failed to initialize daemon\n"); + g_main_loop_quit (loop); + return; + } +- + openlog ("accounts-daemon", LOG_PID, LOG_DAEMON); + syslog (LOG_INFO, "started daemon version %s", VERSION); + closelog (); + openlog ("accounts-daemon", 0, LOG_AUTHPRIV); + } + + static void + on_name_lost (GDBusConnection *connection, + const gchar *name, + gpointer user_data) + { + GMainLoop *loop = user_data; + + g_debug ("got NameLost, exiting"); + g_main_loop_quit (loop); + } + + static gboolean debug; + + static void + on_log_debug (const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer user_data) + { + g_autoptr(GString) string = NULL; + const gchar *progname; + int ret G_GNUC_UNUSED; + + string = g_string_new (NULL); +-- +2.31.1 + diff --git a/SOURCES/user-template b/SOURCES/user-template new file mode 100644 index 0000000..d36c8e9 --- /dev/null +++ b/SOURCES/user-template @@ -0,0 +1,13 @@ +# This file contains defaults for new users. To edit, first +# copy it to /etc/accountsservice/user-templates and make changes +# there +[Template] +EnvironmentFiles=/etc/os-release; + +[com.redhat.AccountsServiceUser.System] +id='${ID}' +version-id='${VERSION_ID}' + +[User] +Session=gnome +Icon=${HOME}/.face diff --git a/SPECS/accountsservice.spec b/SPECS/accountsservice.spec new file mode 100644 index 0000000..175f26e --- /dev/null +++ b/SPECS/accountsservice.spec @@ -0,0 +1,417 @@ +%global _hardened_build 1 + +Name: accountsservice +Version: 0.6.55 +Release: 10%{?dist} +Summary: D-Bus interfaces for querying and manipulating user account information +License: GPLv3+ +URL: https://www.freedesktop.org/wiki/Software/AccountsService/ + +#VCS: git:git://git.freedesktop.org/accountsservice +Source0: http://www.freedesktop.org/software/accountsservice/accountsservice-%{version}.tar.xz +Source1: user-template + +BuildRequires: gettext-devel +BuildRequires: pkgconfig(dbus-1) +BuildRequires: glib2-devel +BuildRequires: polkit-devel +BuildRequires: systemd +BuildRequires: systemd-devel +BuildRequires: gobject-introspection-devel +BuildRequires: gtk-doc +BuildRequires: git +BuildRequires: meson + +Requires: polkit +Requires: shadow-utils +%{?systemd_requires} + +Patch10001: 0001-data-don-t-send-change-updates-for-login-history.patch + +Patch20001: 0001-daemon-if-no-local-users-check-if-machine-is-enrolle.patch + +Patch30001: 0001-lib-save-os-when-creating-user.patch + +Patch40001: 0001-user-Introduce-user-templates-for-setting-default-se.patch + +Patch50001: 0001-daemon-Allow-SystemAccount-false-to-be-set-in-cache-.patch +Patch50002: 0002-main-Allow-cache-files-to-be-marked-immutable.patch + +%description +The accountsservice project provides a set of D-Bus interfaces for +querying and manipulating user account information and an implementation +of these interfaces, based on the useradd, usermod and userdel commands. + +%package libs +Summary: Client-side library to talk to accountsservice +Requires: %{name} = %{version}-%{release} + +%description libs +The accountsservice-libs package contains a library that can +be used by applications that want to interact with the accountsservice +daemon. + +%package devel +Summary: Development files for accountsservice-libs +Requires: %{name}-libs = %{version}-%{release} + +%description devel +The accountsservice-devel package contains headers and other +files needed to build applications that use accountsservice-libs. + + +%prep +%autosetup -S git + +%build +%meson -Dgtk_doc=true -Dsystemd=true -Duser_heuristics=true +%meson_build + +%install +%meson_install + +mkdir -p $RPM_BUILD_ROOT%{_datadir}/accountsservice/interfaces/ +mkdir -p $RPM_BUILD_ROOT%{_datadir}/accountsservice/user-templates $RPM_BUILD_ROOT%{_sysconfdir}/accountsservice/user-templates +cp $RPM_SOURCE_DIR/user-template $RPM_BUILD_ROOT%{_datadir}/accountsservice/user-templates/standard +cp $RPM_SOURCE_DIR/user-template $RPM_BUILD_ROOT%{_datadir}/accountsservice/user-templates/administrator + + +%find_lang accounts-service + +%ldconfig_scriptlets libs + +%post +%systemd_post accounts-daemon.service + +%preun +%systemd_preun accounts-daemon.service + +%postun +%systemd_postun accounts-daemon.service + +%files -f accounts-service.lang +%license COPYING +%doc README.md AUTHORS +%dir %{_sysconfdir}/accountsservice/user-templates +%dir %{_sysconfdir}/accountsservice +%{_sysconfdir}/dbus-1/system.d/org.freedesktop.Accounts.conf +%{_libexecdir}/accounts-daemon +%dir %{_datadir}/accountsservice/ +%dir %{_datadir}/accountsservice/interfaces/ +%{_datadir}/dbus-1/interfaces/org.freedesktop.Accounts.xml +%{_datadir}/dbus-1/interfaces/org.freedesktop.Accounts.User.xml +%{_datadir}/dbus-1/system-services/org.freedesktop.Accounts.service +%{_datadir}/polkit-1/actions/org.freedesktop.accounts.policy +%{_datadir}/accountsservice/interfaces/com.redhat.AccountsServiceUser.System.xml +%{_datadir}/accountsservice/user-templates/administrator +%{_datadir}/accountsservice/user-templates/standard +%{_datadir}/dbus-1/interfaces/com.redhat.AccountsServiceUser.System.xml +%dir %{_localstatedir}/lib/AccountsService/ +%attr(0700, root, root) %dir %{_localstatedir}/lib/AccountsService/users +%attr(0775, root, root) %dir %{_localstatedir}/lib/AccountsService/icons +%{_unitdir}/accounts-daemon.service + +%files libs +%{_libdir}/libaccountsservice.so.* +%{_libdir}/girepository-1.0/AccountsService-1.0.typelib + +%files devel +%{_includedir}/accountsservice-1.0 +%{_libdir}/libaccountsservice.so +%{_libdir}/pkgconfig/accountsservice.pc +%{_datadir}/gir-1.0/AccountsService-1.0.gir +%dir %{_datadir}/gtk-doc/html/libaccountsservice +%{_datadir}/gtk-doc/html/libaccountsservice/* + +%changelog +* Thu Feb 24 2022 Ray Strode - 0.6.55-10 +- Synchronize permissions and group ownership for icon and users dirs + between rpm file manifest and daemon expectations. + Resolves: #2057576 + +* Mon Oct 25 2021 Ray Strode - 0.6.55-9 +- Bring in RHEL-8 patches + Resolves: #2014692 + +* Mon Aug 09 2021 Mohan Boddu - 0.6.55-8 +- Rebuilt for IMA sigs, glibc 2.34, aarch64 flags + Related: rhbz#1991688 + +* Thu Apr 15 2021 Mohan Boddu - 0.6.55-7 +- Rebuilt for RHEL 9 BETA on Apr 15th 2021. Related: rhbz#1947937 + +* Mon Jan 25 2021 Fedora Release Engineering - 0.6.55-6 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild + +* Fri Sep 04 2020 Bastien Nocera - 0.6.55-5 ++ accountsservice-0.6.55-5 +- Own /usr/share/accountsservice + +* Fri Jul 31 2020 Fedora Release Engineering - 0.6.55-4 +- Second attempt - Rebuilt for + https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild + +* Mon Jul 27 2020 Fedora Release Engineering - 0.6.55-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild + +* Tue Jan 28 2020 Fedora Release Engineering - 0.6.55-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild + +* Thu Sep 26 2019 Benjamin Berg - 0.6.55-1 +- Update to 0.6.55 + Resolves: #1755838 + +* Wed Jul 24 2019 Fedora Release Engineering - 0.6.54-6 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild + +* Thu Jan 31 2019 Fedora Release Engineering - 0.6.54-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild + +* Mon Jan 21 2019 Alexandru-Sever Horin - 0.6.54-4 +- Add patch from upstream to fix UID detection + Resolves: #1646418 + +* Thu Jan 17 2019 Adam Williamson - 0.6.54-3 +- Explicitly enable systemd support (#1576903) (Elliott Sales de Andrade) + +* Mon Jan 14 2019 Björn Esser - 0.6.54-2 +- Rebuilt for libcrypt.so.2 (#1666033) + +* Sat Sep 29 2018 Ray Strode - 0.6.54-1 +- Update to 0.6.54 + +* Thu Sep 27 2018 Ray Strode - 0.6.53-1 +- Update to 0.6.53 + +* Mon Sep 24 2018 Adam Williamson - 0.6.50-1 +- Update to 0.6.50, plus a couple of backported patches + Resolves: #1576903 + +* Thu Jul 12 2018 Fedora Release Engineering - 0.6.49-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild + +* Thu May 10 2018 Ray Strode - 0.6.49-1 +- Update to 0.6.49 (brown bag release) + +* Thu May 10 2018 Ray Strode - 0.6.48-1 +- Update to 0.6.48 + Resolves: #1575780 + +* Fri May 04 2018 Ray Strode - 0.6.47-2 +- fix crash on user deletion + Resolves: #1573550 + +* Tue Apr 24 2018 Ray Strode - 0.6.47-1 +- Update to 0.6.47 + +* Sat Apr 21 2018 Peter Robinson 0.4.46-1 +- Update to 0.6.46 +- Spec cleanup, use %%license + +* Wed Feb 07 2018 Fedora Release Engineering - 0.6.42-9 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild + +* Sun Feb 04 2018 Igor Gnatenko - 0.6.42-8 +- Switch to %%ldconfig_scriptlets + +* Thu Jan 25 2018 Igor Gnatenko - 0.6.42-7 +- Fix systemd executions/requirements + +* Wed Jan 24 2018 Ray Strode - 0.6.42-6 +- Fix crash introduced by glibc/libxcrypt change + https://fedoraproject.org/wiki/Changes/Replace_glibc_libcrypt_with_libxcrypt + Resolves: #1538181 + +* Sat Jan 20 2018 Björn Esser - 0.6.42-5 +- Rebuilt for switch to libxcrypt + +* Wed Aug 02 2017 Fedora Release Engineering - 0.6.42-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild + +* Wed Jul 26 2017 Fedora Release Engineering - 0.6.42-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild + +* Fri Feb 10 2017 Fedora Release Engineering - 0.6.42-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild + +* Thu Jun 09 2016 Ray Strode - 0.6.42-1 +- Update to 0.6.42 +- Fixes systemd incompatibility + +* Tue May 31 2016 Ray Strode - 0.6.40-4 +- Don't create /root/.cache at startup + Resolves: #1331926 + +* Wed Feb 03 2016 Fedora Release Engineering - 0.6.40-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild + +* Tue Jun 16 2015 Fedora Release Engineering - 0.6.40-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild + +* Fri Jan 23 2015 Ray Strode 0.6.40-1 +- Update to 0.6.40 + +* Fri Oct 17 2014 Ray Strode 0.6.39-2 +- More ListCachedUsers race fixes (this time with SSSD) + Related: #1147504 + +* Thu Oct 16 2014 Ray Strode 0.6.39-1 +- Update to 0.6.39 +- Fixes ListCachedUsers race at startup + +* Thu Sep 18 2014 Stef Walter - 0.6.38-1 +- Update to 0.6.38 +- Fixes polkit policy rhbz#1094138 +- Remove dbus-glib-devel dependency, accountsservice uses gdbus now + +* Fri Aug 15 2014 Fedora Release Engineering - 0.6.37-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_22_Mass_Rebuild + +* Tue Jul 22 2014 Kalev Lember - 0.6.37-2 +- Rebuilt for gobject-introspection 1.41.4 + +* Sat Jun 07 2014 Kalev Lember - 0.6.37-1 +- Update to 0.6.37, drop upstreamed patches + +* Sat Jun 07 2014 Fedora Release Engineering - 0.6.35-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild + +* Fri Jan 10 2014 Matthias Clasen - 0.6.35-4 +- Consistently call userdel with -f + +* Wed Nov 20 2013 Ray Strode 0.6.35-3 +- Only treat users < 1000 as system users +- only use user heuristics on the range 500-1000 + +* Mon Nov 11 2013 Ray Strode 0.6.35-2 +- pass --enable-user-heuristics which fedora needs so users + with UIDs less than 1000 show up in the user list. + +* Mon Oct 28 2013 Ray Strode 0.6.35-1 +- Update to 0.6.35 + Related: #1013721 + +* Sat Aug 03 2013 Fedora Release Engineering - 0.6.34-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild + +* Tue Jun 11 2013 Ray Strode 0.6.34-1 +- Update to 0.6.34 + +* Tue Jun 11 2013 Matthias Clasen - 0.6.33-1 +- Update to 0.6.33 + +* Tue May 14 2013 Matthias Clasen - 0.6.32-1 +- Update to 0.6.32 + +* Thu Apr 18 2013 Matthias Clasen - 0.6.31-2 +- Hardened build + +* Tue Apr 16 2013 Matthias Clasen - 0.6.31-1 +- Update to 0.6.31 + +* Wed Feb 13 2013 Fedora Release Engineering - 0.6.30-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild + +* Wed Jan 16 2013 Richard Hughes - 0.6.30-1 +- Update to 0.6.30 + +* Fri Nov 16 2012 Matthias Clasen - 0.6.26-1 +- Update to 0.6.26 + +* Tue Oct 2 2012 Matthias Clasen - 0.6.25-2 +- Update to 0.6.25 +- Use systemd scriptlets (#856649) + +* Wed Jul 18 2012 Fedora Release Engineering - 0.6.22-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild + +* Sat Jul 14 2012 Ville Skyttä - 0.6.22-2 +- Add ldconfig scriptlets to -libs. + +* Thu Jun 28 2012 Ray Strode 0.6.22-1 +- Update to 0.6.22. +- Fixes CVE-2012-2737 - local file disclosure + Related: #832532 + +* Thu May 30 2012 Matthias Clasen 0.6.21-1 +- Update to 0.6.21 + +* Fri May 04 2012 Ray Strode 0.6.20-1 +- Update to 0.6.20. Should fix user list. + Related: #814690 + +* Thu May 03 2012 Ray Strode 0.6.19-1 +- Update to 0.6.19 + Allows user deletion of logged in users + Related: #814690 + +* Wed Apr 11 2012 Matthias Clasen - 0.6.18-1 +- Update to 0.6.18 + +* Tue Mar 27 2012 Ray Strode 0.6.17-1 +- Update to latest release + +* Sun Mar 4 2012 Peter Robinson - 0.6.15-4 +- Fix unitdir with usrmove + +* Thu Jan 12 2012 Fedora Release Engineering - 0.6.15-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild + +* Tue Nov 29 2011 Matthias Clasen 0.6.15-2 +- Make resetting user icons work +- Update to 0.6.15 +- Fixes session chooser at login screen when logged into vt + +* Wed Sep 21 2011 Ray Strode 0.6.14-2 +- Fix wtmp loading so users coming from the network are + remembered in the user list in subsequent boots + +* Wed Sep 21 2011 Ray Strode 0.6.14-1 +- Update to 0.6.14 + +* Sun Sep 4 2011 Matthias Clasen - 0.6.13-3 +- Fix fast user switching + +* Mon Aug 15 2011 Kalev Lember - 0.6.13-2 +- Rebuilt for rpm bug #728707 + +* Tue Jul 19 2011 Matthias Clasen - 0.6.13-1 +- Update to 0.6.13 +- Drop ConsoleKit dependency + +* Mon Jun 06 2011 Ray Strode 0.6.12-1 +- Update to latest release + +* Wed May 18 2011 Matthias Clasen 0.6.11-1 +- Update to 0.6.11 + +* Mon Feb 07 2011 Fedora Release Engineering - 0.6.3-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild + +* Wed Feb 02 2011 Ray Strode 0.6.3-1 +- Update to 0.6.3 + +* Thu Jan 27 2011 Matthias Clasen 0.6.2-1 +- Update to 0.6.2 + +* Wed Jul 21 2010 Matthias Clasen 0.6.1-1 +- Update to 0.6.1 +- Install systemd unit file + +* Mon Apr 5 2010 Matthias Clasen 0.6-2 +- Always emit changed signal on icon change + +* Tue Mar 30 2010 Matthias Clasen 0.6-1 +- Update to 0.6 + +* Mon Mar 22 2010 Matthias Clasen 0.5-1 +- Update to 0.5 + +* Mon Feb 22 2010 Bastien Nocera 0.4-3 +- Fix directory ownership + +* Mon Feb 22 2010 Bastien Nocera 0.4-2 +- Add missing directories to the filelist + +* Fri Jan 29 2010 Matthias Clasen 0.4-1 +-