Compare commits

...

No commits in common. "c8" and "c10s" have entirely different histories.
c8 ... c10s

16 changed files with 516 additions and 4333 deletions

View File

@ -1 +0,0 @@
bab4f37144196d8ba06195bc72b4a9937c62b9fd SOURCES/accountsservice-0.6.55.tar.xz

4
.gitignore vendored
View File

@ -1 +1,3 @@
SOURCES/accountsservice-0.6.55.tar.xz
/accountsservice-*.tar.xz
/mocklibc-1.0-2-wrap.zip
/mocklibc-1.0.tar.gz

View File

@ -0,0 +1,54 @@
From da65bee12d9118fe1a49c8718d428fe61d232339 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Tue, 11 Apr 2023 10:09:07 -0400
Subject: [PATCH 1/2] mocklibc: Fix compiler warning
print_indent is defined in one file and used in another without a
forward declaration. That leads to a compiler warning/error.
This commit fixes that.
---
subprojects/mocklibc.wrap | 2 ++
subprojects/packagefiles/mocklibc-print-indent.diff | 13 +++++++++++++
2 files changed, 15 insertions(+)
create mode 100644 subprojects/packagefiles/mocklibc-print-indent.diff
diff --git a/subprojects/mocklibc.wrap b/subprojects/mocklibc.wrap
index af82298..539ee83 100644
--- a/subprojects/mocklibc.wrap
+++ b/subprojects/mocklibc.wrap
@@ -1,10 +1,12 @@
[wrap-file]
directory = mocklibc-1.0
source_url = https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/mocklibc/mocklibc-1.0.tar.gz
source_filename = mocklibc-1.0.tar.gz
source_hash = b2236a6af1028414783e9734a46ea051916ec226479d6a55a3bb823bff68f120
patch_url = https://wrapdb.mesonbuild.com/v1/projects/mocklibc/1.0/2/get_zip
patch_filename = mocklibc-1.0-2-wrap.zip
patch_hash = 0280f96a2eeb3c023e5acf4e00cef03d362868218d4a85347ea45137c0ef6c56
+
+diff_files = mocklibc-print-indent.diff
diff --git a/subprojects/packagefiles/mocklibc-print-indent.diff b/subprojects/packagefiles/mocklibc-print-indent.diff
new file mode 100644
index 0000000..4aaed40
--- /dev/null
+++ b/subprojects/packagefiles/mocklibc-print-indent.diff
@@ -0,0 +1,13 @@
+diff -up mocklibc-1.0/src/netgroup-debug.c.print-indent mocklibc-1.0/src/netgroup-debug.c
+--- mocklibc-1.0/src/netgroup-debug.c.print-indent 2023-04-11 10:20:53.717381559 -0400
++++ mocklibc-1.0/src/netgroup-debug.c 2023-04-11 10:21:02.296270333 -0400
+@@ -21,6 +21,9 @@
+ #include <stdio.h>
+ #include <stdlib.h>
+
++void print_indent (FILE *stream,
++ unsigned int indent);
++
+ void netgroup_debug_print_entry(struct entry *entry, FILE *stream, unsigned int indent) {
+ print_indent(stream, indent);
+
--
2.39.2

View File

@ -0,0 +1,145 @@
From 99aa57bfa59e2578c4ef47e84338f7de85c6f61b Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Tue, 11 Apr 2023 10:11:05 -0400
Subject: [PATCH 2/2] user-manager: Fix another compiler warning
-Wswitch-enum apparently complains about missing entries even if there
is a default:.
This commit ensures ACT_USER_MANAGER_SEAT_STATE_UNLOADED is added to the
default case to fix that warning.
---
src/libaccountsservice/act-user-manager.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/libaccountsservice/act-user-manager.c b/src/libaccountsservice/act-user-manager.c
index 61b4da5..3bbd480 100644
--- a/src/libaccountsservice/act-user-manager.c
+++ b/src/libaccountsservice/act-user-manager.c
@@ -1727,60 +1727,61 @@ unload_seat (ActUserManager *manager)
{
ActUserManagerPrivate *priv = act_user_manager_get_instance_private (manager);
priv->seat.state = ACT_USER_MANAGER_SEAT_STATE_UNLOADED;
g_free (priv->seat.id);
priv->seat.id = NULL;
g_free (priv->seat.session_id);
priv->seat.session_id = NULL;
g_debug ("ActUserManager: seat unloaded, so trying to set loaded property");
maybe_set_is_loaded (manager);
}
static void
load_new_session_incrementally (ActUserManagerNewSession *new_session)
{
switch (new_session->state) {
case ACT_USER_MANAGER_NEW_SESSION_STATE_GET_UID:
get_uid_for_new_session (new_session);
break;
case ACT_USER_MANAGER_NEW_SESSION_STATE_GET_X11_DISPLAY:
get_x11_display_for_new_session (new_session);
break;
case ACT_USER_MANAGER_NEW_SESSION_STATE_MAYBE_ADD:
maybe_add_new_session (new_session);
break;
case ACT_USER_MANAGER_NEW_SESSION_STATE_LOADED:
break;
+ case ACT_USER_MANAGER_NEW_SESSION_STATE_UNLOADED:
default:
g_assert_not_reached ();
}
}
static void
free_fetch_user_request (ActUserManagerFetchUserRequest *request)
{
ActUserManager *manager = request->manager;
ActUserManagerPrivate *priv = act_user_manager_get_instance_private (manager);
if (request->user != NULL) {
g_object_set_data (G_OBJECT (request->user), "fetch-user-request", NULL);
g_object_weak_unref (G_OBJECT (request->user), (GWeakNotify) on_user_destroyed, manager);
}
priv->fetch_user_requests = g_slist_remove (priv->fetch_user_requests, request);
if (request->type == ACT_USER_MANAGER_FETCH_USER_FROM_USERNAME_REQUEST) {
g_free (request->username);
}
g_free (request->object_path);
g_free (request->description);
g_cancellable_cancel (request->cancellable);
g_object_unref (request->cancellable);
g_debug ("ActUserManager: unrefing manager owned by fetch user request");
g_object_unref (manager);
@@ -2243,60 +2244,61 @@ load_users (ActUserManager *manager)
return;
}
load_user_paths (manager, (const char * const *) user_paths);
load_included_usernames (manager);
priv->list_cached_users_done = TRUE;
}
static gboolean
load_seat_incrementally (ActUserManager *manager)
{
ActUserManagerPrivate *priv = act_user_manager_get_instance_private (manager);
priv->seat.load_idle_id = 0;
switch (priv->seat.state) {
case ACT_USER_MANAGER_SEAT_STATE_GET_SESSION_ID:
get_current_session_id (manager);
break;
case ACT_USER_MANAGER_SEAT_STATE_GET_ID:
get_seat_id_for_current_session (manager);
break;
case ACT_USER_MANAGER_SEAT_STATE_GET_SEAT_PROXY:
get_seat_proxy (manager);
break;
case ACT_USER_MANAGER_SEAT_STATE_LOADED:
g_debug ("ActUserManager: Seat loading sequence complete");
break;
+ case ACT_USER_MANAGER_NEW_SESSION_STATE_UNLOADED:
default:
g_assert_not_reached ();
}
if (priv->seat.state == ACT_USER_MANAGER_SEAT_STATE_LOADED) {
load_sessions (manager);
}
maybe_set_is_loaded (manager);
return FALSE;
}
static gboolean
load_idle (ActUserManager *manager)
{
ActUserManagerPrivate *priv = act_user_manager_get_instance_private (manager);
priv->seat.state = ACT_USER_MANAGER_SEAT_STATE_UNLOADED + 1;
load_seat_incrementally (manager);
priv->load_id = 0;
return FALSE;
}
static void
queue_load_seat (ActUserManager *manager)
{
ActUserManagerPrivate *priv = act_user_manager_get_instance_private (manager);
--
2.39.2

View File

@ -0,0 +1,110 @@
From e050e4aa99818f7559ab48568ea6662dc4104317 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Esser?= <besser82@fedoraproject.org>
Date: Thu, 30 Jan 2025 12:36:21 +0100
Subject: [PATCH 3/3] act-user: Use the reentrant interfaces of
crypt{,_gensalt}(3)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The crypt(3) function is known to clobber its static allocated internal
buffer when called multiple times consecutively or (especially) when
called in parallel (e.g. from independently operation threads), and
should generally not be used if a reentrant implementation exists.
The reentrant interface, named crypt_rn(3), operates in the same way as
the well known crypt(3) function, but takes an extra parameter of
'struct crypt_data' which includes space for its result (among other
things), so applications can utilize the reentrant interface, in a way
each invocation of the crypt_rn(3) function will freely operate on their
own dedicated memory areas when hashing passphrases.
The same applies for the crypt_gensalt(3) function, to which libxcrypt
offers a variety of reentrant interfaces as well.
Also ensure the buffers in use are properly zeroized.
Signed-off-by: Björn Esser <besser82@fedoraproject.org>
---
src/libaccountsservice/act-user.c | 53 ++++++++-----------------------
1 file changed, 13 insertions(+), 40 deletions(-)
diff --git a/src/libaccountsservice/act-user.c b/src/libaccountsservice/act-user.c
index 77b7b2f..4fd2c62 100644
--- a/src/libaccountsservice/act-user.c
+++ b/src/libaccountsservice/act-user.c
@@ -1748,51 +1748,22 @@ act_user_set_account_type (ActUser *user,
}
}
-#ifdef HAVE_CRYPT_GENSALT
static gchar *
-generate_salt_for_crypt_hash (void)
-{
- return g_strdup (crypt_gensalt (NULL, 0, NULL, 0));
-}
-#else
-static const gchar
-salt_char (GRand *rand)
+make_crypted (const gchar *plain)
{
- const gchar salt[] = "ABCDEFGHIJKLMNOPQRSTUVXYZ"
- "abcdefghijklmnopqrstuvxyz"
- "./0123456789";
-
- return salt[g_rand_int_range (rand, 0, G_N_ELEMENTS (salt))];
-}
+ gchar *crypted = NULL;
+ g_autofree struct crypt_data *cd = NULL;
-static gchar *
-generate_salt_for_crypt_hash (void)
-{
- g_autoptr (GString) salt = NULL;
- g_autoptr (GRand) rand = NULL;
- gint i;
+ cd = g_malloc0 (sizeof (struct crypt_data));
- rand = g_rand_new ();
- salt = g_string_sized_new (21);
+ crypt_gensalt_rn (NULL, 0, NULL, 0,
+ cd->input, sizeof (cd->input));
+ crypted = g_strdup (crypt_rn (plain, cd->input,
+ cd, sizeof (struct crypt_data)));
- /* sha512crypt */
- g_string_append (salt, "$6$");
- for (i = 0; i < 16; i++) {
- g_string_append_c (salt, salt_char (rand));
- }
- g_string_append_c (salt, '$');
+ explicit_bzero (cd, sizeof (struct crypt_data));
- return g_strdup (salt->str);
-}
-#endif
-
-static gchar *
-make_crypted (const gchar *plain)
-{
- g_autofree char *salt = NULL;
-
- salt = generate_salt_for_crypt_hash ();
- return g_strdup (crypt (plain, salt));
+ return crypted;
}
/**
@@ -1828,7 +1799,9 @@ act_user_set_password (ActUser *user,
&error)) {
g_warning ("SetPassword call failed: %s", error->message);
}
- memset (crypted, 0, strlen (crypted));
+ if (crypted) {
+ explicit_bzero (crypted, strlen (crypted));
+ }
}
/**
--
2.48.1

View File

@ -1,304 +0,0 @@
From 14c902f42a4ea74ce9450eb53817e1bf5be05d26 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
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

View File

@ -1,88 +0,0 @@
From 50edc5e45bb984576506e7b2bfb4c267ac566099 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
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 @@
<doc:doc>
<doc:description>
<doc:para>
The users location.
</doc:para>
</doc:description>
</doc:doc>
</property>
<property name="LoginFrequency" type="t" access="read">
<doc:doc>
<doc:description>
<doc:para>
How often the user has logged in.
</doc:para>
</doc:description>
</doc:doc>
</property>
<property name="LoginTime" type="x" access="read">
<doc:doc>
<doc:description>
<doc:para>
The last login time.
</doc:para>
</doc:description>
</doc:doc>
</property>
<property name="LoginHistory" type="a(xxa{sv})" access="read">
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
<doc:doc>
<doc:description>
<doc:para>
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.
</doc:para>
<doc:para>
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).
</doc:para>
</doc:description>
</doc:doc>
</property>
<property name="IconFile" type="s" access="read">
<doc:doc>
<doc:description>
<doc:para>
The filename of a png file containing the users icon.
</doc:para>
</doc:description>
</doc:doc>
</property>
<property name="Saved" type="b" access="read">
<doc:doc>
<doc:description>
<doc:para>
--
2.27.0

View File

@ -1,894 +0,0 @@
From 5124220f12a157ff285072a4f786db2c94c3ca8a Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
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 @@
+<node>
+ <interface name="com.redhat.AccountsServiceUser.System">
+
+ <annotation name="org.freedesktop.Accounts.VendorExtension" value="true"/>
+
+ <property name="id" type="s" access="readwrite"/>
+ <property name="version-id" type="s" access="readwrite"/>
+
+ </interface>
+</node>
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 <string.h>
#include <signal.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#ifdef HAVE_PATHS_H
#include <paths.h>
#endif /* HAVE_PATHS_H */
#include <glib.h>
#include <glib/gi18n-lib.h>
#include <glib/gstdio.h>
#include <glib-object.h>
#include <gio/gio.h>
#include <gio/gunixinputstream.h>
#ifdef WITH_SYSTEMD
#include <systemd/sd-login.h>
/* 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

View File

@ -1,925 +0,0 @@
From 72427bd4fcae931298c670093f9cbd34ad58f59a Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
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 <polkit/polkit.h>
#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

View File

@ -1,195 +0,0 @@
From 12127d9c04e8151c51bd14114dce424ff8448345 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
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 <mclasen@redhat.com>
*/
#include "config.h"
#include <stdlib.h>
#include <stdarg.h>
#include <locale.h>
#include <libintl.h>
#include <syslog.h>
#include <sys/stat.h>
#include <errno.h>
#include <glib.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include <glib-unix.h>
#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

View File

@ -1,13 +0,0 @@
# 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

View File

@ -1,15 +1,12 @@
%global _hardened_build 1
Name: accountsservice
Version: 0.6.55
Release: 4%{?dist}
Version: 23.13.9
Release: 8%{?dist}
Summary: D-Bus interfaces for querying and manipulating user account information
License: GPLv3+
License: GPL-3.0-or-later
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
#VCS: git:git://gitlab.freedesktop.org/accountsservice/accountsservice
Source0: https://www.freedesktop.org/software/accountsservice/accountsservice-%{version}.tar.xz
BuildRequires: gettext-devel
BuildRequires: pkgconfig(dbus-1)
@ -21,21 +18,19 @@ BuildRequires: gobject-introspection-devel
BuildRequires: gtk-doc
BuildRequires: git
BuildRequires: meson
BuildRequires: vala
BuildRequires: python3-dbusmock
BuildRequires: libxcrypt-devel
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
# https://bugzilla.redhat.com/show_bug.cgi?id=2185850
Patch10001: 0001-mocklibc-Fix-compiler-warning.patch
Patch10002: 0002-user-manager-Fix-another-compiler-warning.patch
Patch10003: 0003-act-user-Use-the-reentrant-interfaces-of-crypt-_gens.patch
Patch10004: gecos-allow-lastname-comma-firstname.patch
%description
The accountsservice project provides a set of D-Bus interfaces for
@ -44,7 +39,7 @@ 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}
Requires: %{name}%{?_isa} = %{version}-%{release}
%description libs
The accountsservice-libs package contains a library that can
@ -53,7 +48,7 @@ daemon.
%package devel
Summary: Development files for accountsservice-libs
Requires: %{name}-libs = %{version}-%{release}
Requires: %{name}-libs%{?_isa} = %{version}-%{release}
%description devel
The accountsservice-devel package contains headers and other
@ -64,17 +59,15 @@ files needed to build applications that use accountsservice-libs.
%autosetup -S git
%build
%meson -Dgtk_doc=true -Dsystemd=true -Duser_heuristics=true
%meson \
-Dgtk_doc=true \
-Dadmin_group=wheel
%meson_build
%install
%meson_install
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
mkdir -p $RPM_BUILD_ROOT%{_datadir}/accountsservice/interfaces/
%find_lang accounts-service
@ -92,84 +85,161 @@ cp $RPM_SOURCE_DIR/user-template $RPM_BUILD_ROOT%{_datadir}/accountsservice/user
%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}/accountsservice/user-templates/
%{_datadir}/dbus-1/interfaces/org.freedesktop.Accounts.xml
%{_datadir}/dbus-1/interfaces/org.freedesktop.Accounts.User.xml
%{_datadir}/dbus-1/system.d/org.freedesktop.Accounts.conf
%{_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
%dir %{_localstatedir}/lib/AccountsService/users/
%dir %{_localstatedir}/lib/AccountsService/icons/
%{_unitdir}/accounts-daemon.service
%files libs
%{_libdir}/libaccountsservice.so.*
%dir %{_libdir}/girepository-1.0/
%{_libdir}/girepository-1.0/AccountsService-1.0.typelib
%files devel
%{_includedir}/accountsservice-1.0
%{_libdir}/libaccountsservice.so
%{_libdir}/pkgconfig/accountsservice.pc
%dir %{_datadir}/gir-1.0/
%{_datadir}/gir-1.0/AccountsService-1.0.gir
%dir %{_datadir}/gtk-doc/html/libaccountsservice
%{_datadir}/gtk-doc/html/libaccountsservice/*
%dir %{_datadir}/vala/
%dir %{_datadir}/vala/vapi/
%{_datadir}/vala/vapi/accountsservice.*
%changelog
* Mon Oct 25 2021 Ray Strode <rstrode@redhat.com> - 0.6.55-4
- Synchronize permissions and group ownership for icon and users dirs
between rpm file manifest and daemon expectations
Resolves: #1919300
* Fri Apr 25 2025 Adrian Vovk <avovk@redhat.com> - 23.13.9-8
* Tue Oct 12 2021 Ray Strode <rstrode@redhat.com> - 0.6.55-3
- Allow cache files to configure and override system accounts
Resolves: #2012331
- Allow "Lastname, Firstname" in /etc/passwd
Resolves: RHEL-12265
* Wed Aug 04 2021 Ray Strode <rstrode@redhat.com> - 0.6.55-2
- Add support for user templates so user can specify default session
Resolves: #1812788
* Fri Apr 18 2025 Adrian Vovk <avovk@redhat.com> - 23.13.9-7
* Fri Jan 15 2021 Ray Strode <rstrode@redhat.com> - 0.6.55-1
- Rebase to 0.6.55
Resolves: #1846376
- Backport changes from Fedora 40
Resolves: RHEL-78526
* Sun Dec 15 2019 Ray Strode <rstrode@redhat.com> - 0.6.50-8
- Don't set HasNoUsers=true if realmd has providers
Related: #1750516
* Tue Oct 29 2024 Troy Dawson <tdawson@redhat.com> - 23.13.9-6
- Bump release for October 2024 mass rebuild:
Resolves: RHEL-64018
* Mon Jun 17 2019 Ray Strode <rstrode@redhat.com> - 0.6.50-7
- Don't send change updates for login history changes
Resolves: #1713080
* Mon Jun 24 2024 Troy Dawson <tdawson@redhat.com> - 23.13.9-5
- Bump release for June 2024 mass rebuild
* Mon Nov 26 2018 Ray Strode <rstrode@redhat.com> - 0.6.50-6
- Fix user switching before screen lock
Resolves: #1653263
* Wed Jan 24 2024 Fedora Release Engineering <releng@fedoraproject.org> - 23.13.9-4
- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild
* Mon Oct 15 2018 Ray Strode <rstrode@redhat.com> - 0.6.50-5
- Turn off aliasing optimizations until glib codegen is fixed
Related: #1628060 1639428
* Fri Jan 19 2024 Fedora Release Engineering <releng@fedoraproject.org> - 23.13.9-3
- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild
* Fri Oct 12 2018 Ray Strode <rstrode@redhat.com> - 0.6.50-4
Correct rpmdiff complaints
Related: #1628060
* Wed Jul 19 2023 Fedora Release Engineering <releng@fedoraproject.org> - 23.13.9-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild
* Fri Oct 12 2018 Ray Strode <rstrode@redhat.com> - 0.6.50-3
- Record OS in user data when creating new users
Related: #1628060
* Tue Apr 11 2023 Ray Strode <rstrode@redhat.com> - 23.13.9-1
- Update to 23.13.9
- Fix C99 compile error
Resolves: #2185850
* Mon Aug 20 2018 Ray Strode <rstrode@redhat.com> - 0.6.50-2
- add new api needed for handling upgrades from RHEL 7
Related: #1612915 1595825
* Fri Mar 24 2023 Ray Strode <rstrode@redhat.com> - 23.11.69-2
- Fix delay during boot for some users
Resolves: #2180768
* Fri Jul 13 2018 Ray Strode <rstrode@redhat.com> - 0.6.50-1
- Update to 0.6.50
Related: #1597499
* Wed Mar 15 2023 Ray Strode <rstrode@redhat.com> - 23.11.69-1
- Update to 23.11.69
* Wed Jan 18 2023 Fedora Release Engineering <releng@fedoraproject.org> - 22.08.8-3
- Rebuilt for https://fedoraproject.org/wiki/Fedora_38_Mass_Rebuild
* Mon Sep 26 2022 Kalev Lember <klember@redhat.com> - 22.08.8-2
- Rebuild
* Sat Aug 27 2022 Leigh Scott <leigh123linux@gmail.com> - 22.08.8-1
- Update to 22.08.8
* Tue Jul 26 2022 Tomas Popela <tpopela@redhat.com> - 0.6.55-11
- Fix the build with meson 0.60+
* Wed Jul 20 2022 Fedora Release Engineering <releng@fedoraproject.org> - 0.6.55-10
- Rebuilt for https://fedoraproject.org/wiki/Fedora_37_Mass_Rebuild
* Wed Jan 19 2022 Fedora Release Engineering <releng@fedoraproject.org> - 0.6.55-9
- Rebuilt for https://fedoraproject.org/wiki/Fedora_36_Mass_Rebuild
* Wed Jul 21 2021 Fedora Release Engineering <releng@fedoraproject.org> - 0.6.55-8
- Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild
* Fri Jun 25 2021 Björn Esser <besser82@fedoraproject.org> - 0.6.55-7
+ accountsservice-0.6.55-7
- Add patch to use yescrypt for new user passwords, fixes rhbz#1976334
* Mon Jan 25 2021 Fedora Release Engineering <releng@fedoraproject.org> - 0.6.55-6
- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild
* Fri Sep 04 2020 Bastien Nocera <bnocera@redhat.com> - 0.6.55-5
+ accountsservice-0.6.55-5
- Own /usr/share/accountsservice
* Fri Jul 31 2020 Fedora Release Engineering <releng@fedoraproject.org> - 0.6.55-4
- Second attempt - Rebuilt for
https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild
* Mon Jul 27 2020 Fedora Release Engineering <releng@fedoraproject.org> - 0.6.55-3
- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild
* Tue Jan 28 2020 Fedora Release Engineering <releng@fedoraproject.org> - 0.6.55-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild
* Thu Sep 26 2019 Benjamin Berg <bberg@redhat.com> - 0.6.55-1
- Update to 0.6.55
Resolves: #1755838
* Wed Jul 24 2019 Fedora Release Engineering <releng@fedoraproject.org> - 0.6.54-6
- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild
* Thu Jan 31 2019 Fedora Release Engineering <releng@fedoraproject.org> - 0.6.54-5
- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild
* Mon Jan 21 2019 Alexandru-Sever Horin <alex.sever.h@gmail.com> - 0.6.54-4
- Add patch from upstream to fix UID detection
Resolves: #1646418
* Thu Jan 17 2019 Adam Williamson <awilliam@redhat.com> - 0.6.54-3
- Explicitly enable systemd support (#1576903) (Elliott Sales de Andrade)
* Mon Jan 14 2019 Björn Esser <besser82@fedoraproject.org> - 0.6.54-2
- Rebuilt for libcrypt.so.2 (#1666033)
* Sat Sep 29 2018 Ray Strode <rstrode@redhat.com> - 0.6.54-1
- Update to 0.6.54
* Thu Sep 27 2018 Ray Strode <rstrode@redhat.com> - 0.6.53-1
- Update to 0.6.53
* Mon Sep 24 2018 Adam Williamson <awilliam@redhat.com> - 0.6.50-1
- Update to 0.6.50, plus a couple of backported patches
Resolves: #1576903
* Thu Jul 12 2018 Fedora Release Engineering <releng@fedoraproject.org> - 0.6.49-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild
* Thu May 10 2018 Ray Strode <rstrode@redhat.com> - 0.6.49-1
- Update to 0.6.49 (brown bag release)
* Thu May 10 2018 Ray Strode <rstrode@redhat.com> - 0.6.48-1
- Update to 0.6.48
Resolves: #1575780
* Fri May 04 2018 Ray Strode <rstrode@redhat.com> - 0.6.47-2
- fix crash on user deletion
Resolves: #1573550
* Tue Apr 24 2018 Ray Strode <rstrode@redhat.com> - 0.6.47-1
- Update to 0.6.47
@ -303,7 +373,7 @@ cp $RPM_SOURCE_DIR/user-template $RPM_BUILD_ROOT%{_datadir}/accountsservice/user
- Fixes CVE-2012-2737 - local file disclosure
Related: #832532
* Thu May 31 2012 Matthias Clasen <mclasen@redhat.com> 0.6.21-1
* Thu May 30 2012 Matthias Clasen <mclasen@redhatcom> 0.6.21-1
- Update to 0.6.21
* Fri May 04 2012 Ray Strode <rstrode@redhat.com> 0.6.20-1

6
gating.yaml Normal file
View File

@ -0,0 +1,6 @@
--- !Policy
product_versions:
- rhel-10
decision_context: osci_compose_gate
rules:
- !PassingTestCaseRule {test_case_name: desktop-qe.desktop-ci.tier1-gating.functional}

View File

@ -0,0 +1,54 @@
From 091513774c7049618dcc180a7345ec2acfdb7b26 Mon Sep 17 00:00:00 2001
From: Adrian Vovk <adrianvovk@gmail.com>
Date: Fri, 18 Apr 2025 17:36:52 -0400
Subject: [PATCH] Allow "Lastname, Firstname" in /etc/passwd
While accountsservice itself doesn't allow this to be set, other
programs may write these values in /etc/passwd. For presentation
purposes we display "Lastname, Firstname" names. We use a heuristic to
determine whether we're looking at separated GECOS fields or
"Lastname, Firstname".
Resolves: https://issues.redhat.com/browse/RHEL-12265
---
src/user.c | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/src/user.c b/src/user.c
index 1fa047e..da6428c 100644
--- a/src/user.c
+++ b/src/user.c
@@ -429,6 +429,7 @@ user_update_from_pwent (User *user,
g_clear_pointer (&user->gecos, g_free);
if (pwent->pw_gecos && pwent->pw_gecos[0] != '\0') {
gchar *first_comma = NULL;
+ gchar *second_comma = NULL;
gchar *valid_utf8_name = NULL;
if (g_utf8_validate (pwent->pw_gecos, -1, NULL)) {
@@ -442,8 +443,21 @@ user_update_from_pwent (User *user,
}
if (first_comma) {
- real_name = g_strndup (valid_utf8_name,
- (first_comma - valid_utf8_name));
+ second_comma = g_utf8_strchr (first_comma + 1, -1, ',');
+
+ /* Sometimes, /etc/password will contain names in `LastName, FirstName` format.
+ * This is, strictly speaking, incorrect because GECOS fields are separated by
+ * commas. However, we can pretty reliably guess when this has happened. If
+ * using GECOS fields, you wouldn't start the office number with a space. When
+ * using a `LastName, FirstName` format, you wouldn't have more than one comma.
+ * So, if we detect something that _looks_ like `LastName, FirstName`, let's use
+ * it for display purposes. */
+ if (first_comma[1] == ' ' && !second_comma) {
+ real_name = g_strdup (valid_utf8_name);
+ } else {
+ real_name = g_strndup (valid_utf8_name,
+ (first_comma - valid_utf8_name));
+ }
} else if (valid_utf8_name) {
real_name = g_strdup (valid_utf8_name);
} else {
--
GitLab

1
sources Normal file
View File

@ -0,0 +1 @@
SHA512 (accountsservice-23.13.9.tar.xz) = c6eb543ce2e07b61bbdfa454e388949bc688517be0536b14f30c66383ed105b15d3fab8b6c62b1eaa08f67d9bdc29b928729d9f7c704f47de4decf0de8e12f89