Blob Blame Raw
From 2b23e50057fc92da30093fbebb78b320fc4107d0 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Wed, 29 Jun 2016 10:50:37 -0400
Subject: [PATCH 1/5] user: check if user is in wheel more efficiently

We currently get all the groups a user belongs to in one pass,
then check each one to see if it's wheel.

It's much more efficient to just get the wheel group and check if
any of its members are the user.

https://bugs.freedesktop.org/show_bug.cgi?id=48177
---
 src/user.c | 13 ++-----------
 1 file changed, 2 insertions(+), 11 deletions(-)

diff --git a/src/user.c b/src/user.c
index de30090..52f57d0 100644
--- a/src/user.c
+++ b/src/user.c
@@ -92,88 +92,79 @@ struct User {
         gchar        *home_dir;
         gchar        *shell;
         gchar        *email;
         gchar        *language;
         gchar        *x_session;
         gchar        *location;
         guint64       login_frequency;
         gint64        login_time;
         GVariant     *login_history;
         gchar        *icon_file;
         gchar        *default_icon_file;
         gboolean      locked;
         gboolean      automatic_login;
         gboolean      system_account;
         gboolean      local_account;
 };
 
 typedef struct UserClass
 {
         AccountsUserSkeletonClass parent_class;
 } UserClass;
 
 static void user_accounts_user_iface_init (AccountsUserIface *iface);
 
 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;
-        gid_t wheel;
-        gid_t *groups;
-        gint ngroups;
         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;
         }
-        wheel = grp->gr_gid;
 
-        ngroups = get_user_groups (pwent->pw_name, pwent->pw_gid, &groups);
-
-        for (i = 0; i < ngroups; i++) {
-                if (groups[i] == wheel) {
-                        g_free (groups);
+        for (i = 0; grp->gr_mem[i] != NULL; i++) {
+                if (g_strcmp0 (grp->gr_mem[i], pwent->pw_name) == 0) {
                         return ACCOUNT_TYPE_ADMINISTRATOR;
                 }
         }
 
-        g_free (groups);
-
         return ACCOUNT_TYPE_STANDARD;
 }
 
 void
 user_update_from_pwent (User          *user,
                         struct passwd *pwent)
 {
 #ifdef HAVE_SHADOW_H
         struct spwd *spent;
 #endif
         gchar *real_name;
         gboolean changed;
         const gchar *passwd;
         gboolean locked;
         PasswordMode mode;
         AccountType account_type;
 
         g_object_freeze_notify (G_OBJECT (user));
 
         changed = FALSE;
 
         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 {
-- 
2.7.4


From c2b87f89a85ffa5465a523aa291ea0018a050cc5 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Tue, 28 Jun 2016 15:43:08 -0400
Subject: [PATCH 2/5] daemon: get local users from /etc/shadow not /etc/passwd

For some sites, it's common practice to rsync around large
/etc/passwd files containing the password entries for remote
users. That means accountsservices' "assume /etc/passwd is local
users" heuristic falls over.

This commit changes it to only treat users in /etc/shadow as local.

https://bugs.freedesktop.org/show_bug.cgi?id=48177
---
 src/daemon.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 53 insertions(+), 6 deletions(-)

diff --git a/src/daemon.c b/src/daemon.c
index 38f6a47..5c269af 100644
--- a/src/daemon.c
+++ b/src/daemon.c
@@ -2,60 +2,63 @@
  *
  * Copyright (C) 2009-2010 Red Hat, Inc.
  * Copyright (c) 2013 Canonical Limited
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 3 of the License, or
  * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * 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 <stdio.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <sys/wait.h>
 #include <pwd.h>
+#ifdef HAVE_SHADOW_H
+#include <shadow.h>
+#endif
 #include <unistd.h>
 #include <errno.h>
 #include <sys/types.h>
 #ifdef HAVE_UTMPX_H
 #include <utmpx.h>
 #endif
 
 #include <glib.h>
 #include <glib/gi18n.h>
 #include <glib-object.h>
 #include <glib/gstdio.h>
 #include <gio/gio.h>
 #include <polkit/polkit.h>
 
 #include "user-classify.h"
 #include "daemon.h"
 #include "util.h"
 
 #define PATH_PASSWD "/etc/passwd"
 #define PATH_SHADOW "/etc/shadow"
 #define PATH_GROUP "/etc/group"
 #define PATH_GDM_CUSTOM "/etc/gdm/custom.conf"
 #ifdef HAVE_UTMPX_H
 #define PATH_WTMP _PATH_WTMPX
 #endif
 
 enum {
         PROP_0,
         PROP_DAEMON_VERSION
 };
@@ -279,81 +282,125 @@ entry_generator_wtmp (GHashTable *users,
 
                 builder = g_variant_builder_new (G_VARIANT_TYPE ("a(xxa{sv})"));
                 for (l = g_list_last (accounting->previous_logins); l != NULL; l = l->prev) {
                         previous_login = l->data;
 
                         builder2 = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
                         g_variant_builder_add (builder2, "{sv}", "type", g_variant_new_string (previous_login->id));
                         g_variant_builder_add (builder, "(xxa{sv})", previous_login->login_time, previous_login->logout_time, builder2);
                         g_variant_builder_unref (builder2);
                 }
                 g_object_set (user, "login-history", g_variant_new ("a(xxa{sv})", builder), NULL);
                 g_variant_builder_unref (builder);
                 g_list_free_full (accounting->previous_logins, (GDestroyNotify) user_previous_login_free);
 
                 user_changed (user);
         }
 
         g_hash_table_unref (login_hash);
         g_hash_table_unref (logout_hash);
         g_free (state_data);
         *state = NULL;
         return NULL;
 }
 #endif /* HAVE_UTMPX_H */
 
 static struct passwd *
 entry_generator_fgetpwent (GHashTable *users,
                            gpointer   *state)
 {
         struct passwd *pwent;
-        FILE *fp;
+        struct {
+                FILE *fp;
+                GHashTable *users;
+        } *generator_state;
 
         /* First iteration */
         if (*state == NULL) {
-                *state = fp = fopen (PATH_PASSWD, "r");
+                GHashTable *shadow_users = NULL;
+                FILE *fp;
+#ifdef HAVE_SHADOW_H
+                struct spwd *shadow_entry;
+
+                fp = fopen (PATH_SHADOW, "r");
+                if (fp == NULL) {
+                        g_warning ("Unable to open %s: %s", PATH_SHADOW, g_strerror (errno));
+                        return NULL;
+                }
+
+                shadow_users = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+                do {
+                        shadow_entry = fgetspent (fp);
+                        if (shadow_entry != NULL) {
+                                g_hash_table_add (shadow_users, g_strdup (shadow_entry->sp_namp));
+                        } else if (errno != EINTR) {
+                                break;
+                        }
+                } while (shadow_entry != NULL);
+
+                fclose (fp);
+
+                if (g_hash_table_size (shadow_users) == 0) {
+                        g_clear_pointer (&shadow_users, g_hash_table_unref);
+                        return NULL;
+                }
+#endif
+
+                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 */
-        fp = *state;
-        pwent = fgetpwent (fp);
+        generator_state = *state;
+        pwent = fgetpwent (generator_state->fp);
         if (pwent != NULL) {
-                return pwent;
+                if (!generator_state->users || g_hash_table_lookup (generator_state->users, pwent->pw_name))
+                        return pwent;
         }
 
         /* Last iteration */
-        fclose (fp);
+        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 (GHashTable *users,
                           gpointer   *state)
 {
         struct passwd *pwent;
         const gchar *name;
         GError *error = NULL;
         gchar *filename;
         gboolean regular;
         GHashTableIter iter;
         GKeyFile *key_file;
         User *user;
         GDir *dir;
 
         /* First iteration */
         if (*state == NULL) {
                 *state = g_dir_open (USERDIR, 0, &error);
                 if (error != NULL) {
                         if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
                                 g_warning ("couldn't list user cache directory: %s", USERDIR);
                         g_error_free (error);
                         return NULL;
                 }
         }
 
         /* Every iteration */
 
-- 
2.7.4


From 011ef555b0db601186a38c43f9359589ed61e230 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Wed, 29 Jun 2016 15:57:38 -0400
Subject: [PATCH 3/5] daemon: don't call getspnam for local users

We're already iterating over the whole shadow file, so
just cache the entries instead of calling getspname a
few lines later.

https://bugs.freedesktop.org/show_bug.cgi?id=48177
---
 src/daemon.c | 86 +++++++++++++++++++++++++++++++++++++++++-------------------
 src/user.c   | 11 ++------
 src/user.h   |  4 ++-
 3 files changed, 64 insertions(+), 37 deletions(-)

diff --git a/src/daemon.c b/src/daemon.c
index 5c269af..71a3ea4 100644
--- a/src/daemon.c
+++ b/src/daemon.c
@@ -58,61 +58,61 @@
 #define PATH_WTMP _PATH_WTMPX
 #endif
 
 enum {
         PROP_0,
         PROP_DAEMON_VERSION
 };
 
 struct DaemonPrivate {
         GDBusConnection *bus_connection;
         GDBusProxy *bus_proxy;
 
         GHashTable *users;
 
         User *autologin;
 
         GFileMonitor *passwd_monitor;
         GFileMonitor *shadow_monitor;
         GFileMonitor *group_monitor;
         GFileMonitor *gdm_monitor;
 #ifdef HAVE_UTMPX_H
         GFileMonitor *wtmp_monitor;
 #endif
 
         guint reload_id;
         guint autologin_id;
 
         PolkitAuthority *authority;
 };
 
-typedef struct passwd * (* EntryGeneratorFunc) (GHashTable *, gpointer *);
+typedef struct passwd * (* EntryGeneratorFunc) (GHashTable *, gpointer *, struct spwd **shadow_entry);
 
 static void daemon_accounts_accounts_iface_init (AccountsAccountsIface *iface);
 
 G_DEFINE_TYPE_WITH_CODE (Daemon, daemon, ACCOUNTS_TYPE_ACCOUNTS_SKELETON, G_IMPLEMENT_INTERFACE (ACCOUNTS_TYPE_ACCOUNTS, daemon_accounts_accounts_iface_init));
 
 #define DAEMON_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TYPE_DAEMON, DaemonPrivate))
 
 static const GDBusErrorEntry accounts_error_entries[] =
 { 
         { ERROR_FAILED, "org.freedesktop.Accounts.Error.Failed" },
         { ERROR_USER_EXISTS, "org.freedesktop.Accounts.Error.UserExists" },
         { ERROR_USER_DOES_NOT_EXIST, "org.freedesktop.Accounts.Error.UserDoesNotExist" },
         { ERROR_PERMISSION_DENIED, "org.freedesktop.Accounts.Error.PermissionDenied" },
         { ERROR_NOT_SUPPORTED, "org.freedesktop.Accounts.Error.NotSupported" }
 };
 
 GQuark
 error_quark (void)
 {
         static volatile gsize quark_volatile = 0;
 
         g_dbus_error_register_error_domain ("accounts_error",
                                             &quark_volatile,
                                             accounts_error_entries,
                                             G_N_ELEMENTS (accounts_error_entries));
 
         return (GQuark) quark_volatile;
 }
 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
 
@@ -138,62 +138,63 @@ error_get_type (void)
   return etype;
 }
 
 #ifdef HAVE_UTMPX_H
 
 typedef struct {
         guint64 frequency;
         gint64 time;
         GList *previous_logins;
 } UserAccounting;
 
 typedef struct {
         gchar  *id;
         gint64  login_time;
         gint64  logout_time;
 } UserPreviousLogin;
 
 typedef struct {
         GHashTable *login_hash;
         GHashTable *logout_hash;
 } WTmpGeneratorState;
 
 static void
 user_previous_login_free (UserPreviousLogin *previous_login)
 {
         g_free (previous_login->id);
         g_free (previous_login);
 }
 
 static struct passwd *
-entry_generator_wtmp (GHashTable *users,
-                      gpointer   *state)
+entry_generator_wtmp (GHashTable   *users,
+                      gpointer     *state,
+                      struct spwd **spent)
 {
         GHashTable *login_hash, *logout_hash;
         struct utmpx *wtmp_entry;
         GHashTableIter iter;
         gpointer key, value;
         struct passwd *pwent;
         User *user;
         WTmpGeneratorState *state_data;
         GVariantBuilder *builder, *builder2;
         GList *l;
 
         if (*state == NULL) {
                 /* First iteration */
 #ifdef UTXDB_LOG
                 if (setutxdb (UTXDB_LOG, NULL) != 0) {
                         return NULL;
                 }
 #else
                 utmpxname (PATH_WTMP);
                 setutxent ();
 #endif
                 *state = g_new (WTmpGeneratorState, 1);
                 state_data = *state;
                 state_data->login_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
                 state_data->logout_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
         }
 
         /* Every iteration */
         state_data = *state;
         login_hash = state_data->login_hash;
@@ -232,284 +233,308 @@ entry_generator_wtmp (GHashTable *users,
                 }
 
                 pwent = getpwnam (wtmp_entry->ut_user);
                 if (pwent == NULL) {
                         continue;
                 }
 
                 if (!g_hash_table_lookup_extended (login_hash,
                                                    wtmp_entry->ut_user,
                                                    &key, &value)) {
                         accounting = g_new (UserAccounting, 1);
                         accounting->frequency = 0;
                         accounting->previous_logins = NULL;
 
                         g_hash_table_insert (login_hash, g_strdup (wtmp_entry->ut_user), accounting);
                 } else {
                         accounting = value;
                 }
 
                 accounting->frequency++;
                 accounting->time = wtmp_entry->ut_tv.tv_sec;
 
                 /* Add zero logout time to change it later on logout record */
                 previous_login = g_new (UserPreviousLogin, 1);
                 previous_login->id = g_strdup (wtmp_entry->ut_line);
                 previous_login->login_time = wtmp_entry->ut_tv.tv_sec;
                 previous_login->logout_time = 0;
                 accounting->previous_logins = g_list_prepend (accounting->previous_logins, previous_login);
 
                 g_hash_table_insert (logout_hash, g_strdup (wtmp_entry->ut_line), previous_login);
+                *spent = getspnam (pwent->pw_name);
 
                 return pwent;
         }
 
         /* Last iteration */
         endutxent ();
 
         g_hash_table_iter_init (&iter, login_hash);
         while (g_hash_table_iter_next (&iter, &key, &value)) {
                 UserAccounting    *accounting = (UserAccounting *) value;
                 UserPreviousLogin *previous_login;
 
                 user = g_hash_table_lookup (users, key);
                 if (user == NULL) {
                         g_list_free_full (accounting->previous_logins, (GDestroyNotify) user_previous_login_free);
                         continue;
                 }
 
                 g_object_set (user, "login-frequency", accounting->frequency, NULL);
                 g_object_set (user, "login-time", accounting->time, NULL);
 
                 builder = g_variant_builder_new (G_VARIANT_TYPE ("a(xxa{sv})"));
                 for (l = g_list_last (accounting->previous_logins); l != NULL; l = l->prev) {
                         previous_login = l->data;
 
                         builder2 = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
                         g_variant_builder_add (builder2, "{sv}", "type", g_variant_new_string (previous_login->id));
                         g_variant_builder_add (builder, "(xxa{sv})", previous_login->login_time, previous_login->logout_time, builder2);
                         g_variant_builder_unref (builder2);
                 }
                 g_object_set (user, "login-history", g_variant_new ("a(xxa{sv})", builder), NULL);
                 g_variant_builder_unref (builder);
                 g_list_free_full (accounting->previous_logins, (GDestroyNotify) user_previous_login_free);
 
                 user_changed (user);
         }
 
         g_hash_table_unref (login_hash);
         g_hash_table_unref (logout_hash);
         g_free (state_data);
         *state = NULL;
         return NULL;
 }
 #endif /* HAVE_UTMPX_H */
 
 static struct passwd *
-entry_generator_fgetpwent (GHashTable *users,
-                           gpointer   *state)
+entry_generator_fgetpwent (GHashTable   *users,
+                           gpointer     *state,
+                           struct spwd **spent)
 {
         struct passwd *pwent;
+
+        struct {
+                struct spwd spbuf;
+                char buf[1024];
+        } *shadow_entry_buffers;
+
         struct {
                 FILE *fp;
                 GHashTable *users;
         } *generator_state;
 
         /* First iteration */
         if (*state == NULL) {
                 GHashTable *shadow_users = NULL;
                 FILE *fp;
-#ifdef HAVE_SHADOW_H
                 struct spwd *shadow_entry;
 
                 fp = fopen (PATH_SHADOW, "r");
                 if (fp == NULL) {
                         g_warning ("Unable to open %s: %s", PATH_SHADOW, g_strerror (errno));
                         return NULL;
                 }
 
-                shadow_users = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+                shadow_users = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
 
                 do {
-                        shadow_entry = fgetspent (fp);
-                        if (shadow_entry != NULL) {
-                                g_hash_table_add (shadow_users, g_strdup (shadow_entry->sp_namp));
-                        } else if (errno != EINTR) {
-                                break;
+                        int ret = 0;
+
+                        shadow_entry_buffers = g_malloc0 (sizeof (*shadow_entry_buffers));
+
+                        ret = fgetspent_r (fp, &shadow_entry_buffers->spbuf, shadow_entry_buffers->buf, sizeof (shadow_entry_buffers->buf), &shadow_entry);
+                        if (ret == 0) {
+                                g_hash_table_insert (shadow_users, g_strdup (shadow_entry->sp_namp), shadow_entry_buffers);
+                        } else {
+                                g_free (shadow_entry_buffers);
+
+                                if (errno != EINTR) {
+                                        break;
+                                }
                         }
                 } while (shadow_entry != NULL);
 
                 fclose (fp);
 
                 if (g_hash_table_size (shadow_users) == 0) {
                         g_clear_pointer (&shadow_users, g_hash_table_unref);
                         return NULL;
                 }
-#endif
 
                 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;
         pwent = fgetpwent (generator_state->fp);
         if (pwent != NULL) {
-                if (!generator_state->users || g_hash_table_lookup (generator_state->users, pwent->pw_name))
+                shadow_entry_buffers = g_hash_table_lookup (generator_state->users, pwent->pw_name);
+
+                if (shadow_entry_buffers != NULL) {
+                        *spent = &shadow_entry_buffers->spbuf;
                         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 (GHashTable *users,
-                          gpointer   *state)
+entry_generator_cachedir (GHashTable   *users,
+                          gpointer     *state,
+                          struct spwd **shadow_entry)
 {
         struct passwd *pwent;
         const gchar *name;
         GError *error = NULL;
         gchar *filename;
         gboolean regular;
         GHashTableIter iter;
         GKeyFile *key_file;
         User *user;
         GDir *dir;
 
         /* First iteration */
         if (*state == NULL) {
                 *state = g_dir_open (USERDIR, 0, &error);
                 if (error != NULL) {
                         if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
                                 g_warning ("couldn't list user cache directory: %s", USERDIR);
                         g_error_free (error);
                         return NULL;
                 }
         }
 
         /* Every iteration */
 
         /*
          * Use names of files of regular type to lookup information
          * about each user. Loop until we find something valid.
          */
         dir = *state;
         while (TRUE) {
                 name = g_dir_read_name (dir);
                 if (name == NULL)
                         break;
 
                 /* Only load files in this directory */
                 filename = g_build_filename (USERDIR, name, NULL);
                 regular = g_file_test (filename, G_FILE_TEST_IS_REGULAR);
                 g_free (filename);
 
                 if (regular) {
                         pwent = getpwnam (name);
-                        if (pwent == NULL)
+                        if (pwent == NULL) {
                                 g_debug ("user '%s' in cache dir but not present on system", name);
-                        else
+                        } else {
+                                *shadow_entry = getspnam (pwent->pw_name);
+
                                 return pwent;
+                        }
                 }
         }
 
         /* 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, (gpointer *)&name, (gpointer *)&user)) {
                 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);
                 g_key_file_unref (key_file);
                 g_free (filename);
         }
 
         *state = NULL;
         return NULL;
 }
 
 static void
 load_entries (Daemon             *daemon,
               GHashTable         *users,
               EntryGeneratorFunc  entry_generator)
 {
         gpointer generator_state = NULL;
         struct passwd *pwent;
+        struct spwd *spent = NULL;
         User *user = NULL;
 
         g_assert (entry_generator != NULL);
 
         for (;;) {
-                pwent = entry_generator (users, &generator_state);
+                spent = NULL;
+                pwent = entry_generator (users, &generator_state, &spent);
                 if (pwent == NULL)
                         break;
 
                 /* Skip system users... */
-                if (!user_classify_is_human (pwent->pw_uid, pwent->pw_name, pwent->pw_shell, NULL)) {
+                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);
                         continue;
                 }
 
                 /* ignore duplicate entries */
                 if (g_hash_table_lookup (users, pwent->pw_name)) {
                         continue;
                 }
 
                 user = g_hash_table_lookup (daemon->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);
+                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));
         }
 
         /* 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 void
 reload_users (Daemon *daemon)
 {
         GHashTable *users;
         GHashTable *old_users;
         GHashTable *local;
         GHashTableIter iter;
         gpointer name;
         User *user;
 
         /* Track the users that we saw during our (re)load */
         users = create_users_hash_table ();
@@ -827,115 +852,122 @@ daemon_new (void)
                 g_object_unref (daemon);
                 goto error;
         }
 
         return daemon;
 
  error:
         return NULL;
 }
 
 static void
 throw_error (GDBusMethodInvocation *context,
              gint                   error_code,
              const gchar           *format,
              ...)
 {
         va_list args;
         gchar *message;
 
         va_start (args, format);
         message = g_strdup_vprintf (format, args);
         va_end (args);
 
         g_dbus_method_invocation_return_error (context, ERROR, error_code, "%s", message);
 
         g_free (message);
 }
 
 static User *
 add_new_user_for_pwent (Daemon        *daemon,
-                        struct passwd *pwent)
+                        struct passwd *pwent,
+                        struct spwd   *spent)
 {
         User *user;
 
         user = user_new (daemon, pwent->pw_uid);
-        user_update_from_pwent (user, pwent);
+        user_update_from_pwent (user, pwent, spent);
         user_register (user);
 
         g_hash_table_insert (daemon->priv->users,
                              g_strdup (user_get_user_name (user)),
                              user);
 
         accounts_accounts_emit_user_added (ACCOUNTS_ACCOUNTS (daemon), user_get_object_path (user));
 
         return user;
 }
 
 User *
 daemon_local_find_user_by_id (Daemon *daemon,
                               uid_t   uid)
 {
         User *user;
         struct passwd *pwent;
 
         pwent = getpwuid (uid);
         if (pwent == NULL) {
                 g_debug ("unable to lookup uid %d", (int)uid);
                 return NULL;
         }
 
         user = g_hash_table_lookup (daemon->priv->users, pwent->pw_name);
 
-        if (user == NULL)
-                user = add_new_user_for_pwent (daemon, pwent);
+        if (user == NULL) {
+                struct spwd *spent;
+                spent = getspnam (pwent->pw_name);
+                user = add_new_user_for_pwent (daemon, pwent, spent);
+        }
 
         return user;
 }
 
 User *
 daemon_local_find_user_by_name (Daemon      *daemon,
                                 const gchar *name)
 {
         User *user;
         struct passwd *pwent;
 
         pwent = getpwnam (name);
         if (pwent == NULL) {
                 g_debug ("unable to lookup name %s: %s", name, g_strerror (errno));
                 return NULL;
         }
 
         user = g_hash_table_lookup (daemon->priv->users, pwent->pw_name);
 
-        if (user == NULL)
-                user = add_new_user_for_pwent (daemon, pwent);
+        if (user == NULL) {
+                struct spwd *spent;
+                spent = getspnam (pwent->pw_name);
+                user = add_new_user_for_pwent (daemon, pwent, spent);
+        }
 
         return user;
 }
 
 User *
 daemon_local_get_automatic_login_user (Daemon *daemon)
 {
         return daemon->priv->autologin;
 }
 
 static gboolean
 daemon_find_user_by_id (AccountsAccounts      *accounts,
                         GDBusMethodInvocation *context,
                         gint64                 uid)
 {
         Daemon *daemon = (Daemon*)accounts;
         User *user;
 
         user = daemon_local_find_user_by_id (daemon, uid);
 
         if (user) {
                 accounts_accounts_complete_find_user_by_id (NULL, context, user_get_object_path (user));
         }
         else {
                 throw_error (context, ERROR_FAILED, "Failed to look up user with uid %d.", (int)uid);
         }
 
         return TRUE;
 }
 
diff --git a/src/user.c b/src/user.c
index 52f57d0..247ca2f 100644
--- a/src/user.c
+++ b/src/user.c
@@ -116,65 +116,63 @@ static void user_accounts_user_iface_init (AccountsUserIface *iface);
 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;
 }
 
 void
 user_update_from_pwent (User          *user,
-                        struct passwd *pwent)
+                        struct passwd *pwent,
+                        struct spwd   *spent)
 {
-#ifdef HAVE_SHADOW_H
-        struct spwd *spent;
-#endif
         gchar *real_name;
         gboolean changed;
         const gchar *passwd;
         gboolean locked;
         PasswordMode mode;
         AccountType account_type;
 
         g_object_freeze_notify (G_OBJECT (user));
 
         changed = FALSE;
 
         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,
                                                   (first_comma - valid_utf8_name));
                 }
                 else if (valid_utf8_name) {
@@ -219,93 +217,88 @@ user_update_from_pwent (User          *user,
                 g_object_notify (G_OBJECT (user), "account-type");
         }
 
         /* Username */
         if (g_strcmp0 (user->user_name, pwent->pw_name) != 0) {
                 g_free (user->user_name);
                 user->user_name = g_strdup (pwent->pw_name);
                 changed = TRUE;
                 g_object_notify (G_OBJECT (user), "user-name");
         }
 
         /* Home Directory */
         if (g_strcmp0 (user->home_dir, pwent->pw_dir) != 0) {
                 g_free (user->home_dir);
                 user->home_dir = g_strdup (pwent->pw_dir);
                 g_free (user->default_icon_file);
                 user->default_icon_file = g_build_filename (user->home_dir, ".face", NULL);
                 changed = TRUE;
                 g_object_notify (G_OBJECT (user), "home-directory");
         }
 
         /* Shell */
         if (g_strcmp0 (user->shell, pwent->pw_shell) != 0) {
                 g_free (user->shell);
                 user->shell = g_strdup (pwent->pw_shell);
                 changed = TRUE;
                 g_object_notify (G_OBJECT (user), "shell");
         }
 
         passwd = NULL;
-#ifdef HAVE_SHADOW_H
-        spent = getspnam (pwent->pw_name);
         if (spent)
                 passwd = spent->sp_pwdp;
-#endif
 
         if (passwd && passwd[0] == '!') {
                 locked = TRUE;
         }
         else {
                 locked = FALSE;
         }
 
         if (user->locked != locked) {
                 user->locked = locked;
                 changed = TRUE;
                 g_object_notify (G_OBJECT (user), "locked");
         }
 
         if (passwd == NULL || passwd[0] != 0) {
                 mode = PASSWORD_MODE_REGULAR;
         }
         else {
                 mode = PASSWORD_MODE_NONE;
         }
 
-#ifdef HAVE_SHADOW_H
         if (spent) {
                 if (spent->sp_lstchg == 0) {
                         mode = PASSWORD_MODE_SET_AT_LOGIN;
                 }
         }
-#endif
 
         if (user->password_mode != mode) {
                 user->password_mode = mode;
                 changed = TRUE;
                 g_object_notify (G_OBJECT (user), "password-mode");
         }
 
         user->system_account = !user_classify_is_human (user->uid, user->user_name, pwent->pw_shell, passwd);
 
         g_object_thaw_notify (G_OBJECT (user));
 
         if (changed)
                 accounts_user_emit_changed (ACCOUNTS_USER (user));
 }
 
 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) {
                 /* TODO: validate / normalize */
                 g_free (user->language);
                 user->language = s;
                 g_object_notify (G_OBJECT (user), "language");
         }
diff --git a/src/user.h b/src/user.h
index 0848b50..22548f9 100644
--- a/src/user.h
+++ b/src/user.h
@@ -1,80 +1,82 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
  *
  * Copyright (C) 2009-2010 Red Hat, Inc.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 3 of the License, or
  * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
 #ifndef __USER__
 #define __USER__
 
 #include <sys/types.h>
 #include <pwd.h>
+#include <shadow.h>
 
 #include <glib.h>
 #include <gio/gio.h>
 
 #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 passwd *pwent,
+                                             struct spwd   *spent);
 void           user_update_from_keyfile     (User          *user,
                                              GKeyFile      *keyfile);
 void           user_update_local_account_property (User          *user,
                                                    gboolean       local);
 void           user_update_system_account_property (User          *user,
                                                     gboolean       system);
 
 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.7.4


From 2accf123c55f3c6a9596e9fc2d614fcb07c88559 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Wed, 29 Jun 2016 16:22:42 -0400
Subject: [PATCH 4/5] daemon: constrain max local users to 50

Systems with tens of thousands of users don't want all those users
showing up in the user list.

Set a cap at an even 50, which should cover the lion's share of use
cases well. Of course, if a user not in the list explicitly
logs in (from Not Listed? or whatever) they get added to the list.

https://bugs.freedesktop.org/show_bug.cgi?id=48177
---
 src/daemon.c | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/src/daemon.c b/src/daemon.c
index 71a3ea4..cb586bb 100644
--- a/src/daemon.c
+++ b/src/daemon.c
@@ -279,60 +279,64 @@ entry_generator_wtmp (GHashTable   *users,
                         continue;
                 }
 
                 g_object_set (user, "login-frequency", accounting->frequency, NULL);
                 g_object_set (user, "login-time", accounting->time, NULL);
 
                 builder = g_variant_builder_new (G_VARIANT_TYPE ("a(xxa{sv})"));
                 for (l = g_list_last (accounting->previous_logins); l != NULL; l = l->prev) {
                         previous_login = l->data;
 
                         builder2 = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
                         g_variant_builder_add (builder2, "{sv}", "type", g_variant_new_string (previous_login->id));
                         g_variant_builder_add (builder, "(xxa{sv})", previous_login->login_time, previous_login->logout_time, builder2);
                         g_variant_builder_unref (builder2);
                 }
                 g_object_set (user, "login-history", g_variant_new ("a(xxa{sv})", builder), NULL);
                 g_variant_builder_unref (builder);
                 g_list_free_full (accounting->previous_logins, (GDestroyNotify) user_previous_login_free);
 
                 user_changed (user);
         }
 
         g_hash_table_unref (login_hash);
         g_hash_table_unref (logout_hash);
         g_free (state_data);
         *state = NULL;
         return NULL;
 }
 #endif /* HAVE_UTMPX_H */
 
+#ifndef MAX_LOCAL_USERS
+#define MAX_LOCAL_USERS 50
+#endif
+
 static struct passwd *
 entry_generator_fgetpwent (GHashTable   *users,
                            gpointer     *state,
                            struct spwd **spent)
 {
         struct passwd *pwent;
 
         struct {
                 struct spwd spbuf;
                 char buf[1024];
         } *shadow_entry_buffers;
 
         struct {
                 FILE *fp;
                 GHashTable *users;
         } *generator_state;
 
         /* First iteration */
         if (*state == NULL) {
                 GHashTable *shadow_users = NULL;
                 FILE *fp;
                 struct spwd *shadow_entry;
 
                 fp = fopen (PATH_SHADOW, "r");
                 if (fp == NULL) {
                         g_warning ("Unable to open %s: %s", PATH_SHADOW, g_strerror (errno));
                         return NULL;
                 }
 
                 shadow_users = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
@@ -350,67 +354,70 @@ entry_generator_fgetpwent (GHashTable   *users,
 
                                 if (errno != EINTR) {
                                         break;
                                 }
                         }
                 } while (shadow_entry != NULL);
 
                 fclose (fp);
 
                 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;
-        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;
-                        return pwent;
+        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;
+                            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 (GHashTable   *users,
                           gpointer     *state,
                           struct spwd **shadow_entry)
 {
         struct passwd *pwent;
         const gchar *name;
         GError *error = NULL;
         gchar *filename;
         gboolean regular;
         GHashTableIter iter;
         GKeyFile *key_file;
         User *user;
         GDir *dir;
 
         /* First iteration */
         if (*state == NULL) {
                 *state = g_dir_open (USERDIR, 0, &error);
-- 
2.7.4


From ed58ad3210010a09b6f114b4d392afb66ad0bbfa Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Wed, 29 Jun 2016 16:32:17 -0400
Subject: [PATCH 5/5] daemon: don't source user list from wtmp

wtmp can get rather large on some systems from ssh logins.
Furthermore it's pretty much completely redundant given the user
cache in /var/lib/AccountService

This commit changes the wtmp code to only get used for maintaining
login frequency and accounting, not for generating new users.

https://bugs.freedesktop.org/show_bug.cgi?id=48177
---
 src/daemon.c | 42 ++++++++++++------------------------------
 1 file changed, 12 insertions(+), 30 deletions(-)

diff --git a/src/daemon.c b/src/daemon.c
index cb586bb..815e2c9 100644
--- a/src/daemon.c
+++ b/src/daemon.c
@@ -137,95 +137,83 @@ error_get_type (void)
     }
   return etype;
 }
 
 #ifdef HAVE_UTMPX_H
 
 typedef struct {
         guint64 frequency;
         gint64 time;
         GList *previous_logins;
 } UserAccounting;
 
 typedef struct {
         gchar  *id;
         gint64  login_time;
         gint64  logout_time;
 } UserPreviousLogin;
 
 typedef struct {
         GHashTable *login_hash;
         GHashTable *logout_hash;
 } WTmpGeneratorState;
 
 static void
 user_previous_login_free (UserPreviousLogin *previous_login)
 {
         g_free (previous_login->id);
         g_free (previous_login);
 }
 
-static struct passwd *
-entry_generator_wtmp (GHashTable   *users,
-                      gpointer     *state,
-                      struct spwd **spent)
+static void
+wtmp_update_login_frequencies (GHashTable *users)
 {
         GHashTable *login_hash, *logout_hash;
         struct utmpx *wtmp_entry;
         GHashTableIter iter;
         gpointer key, value;
         struct passwd *pwent;
         User *user;
-        WTmpGeneratorState *state_data;
         GVariantBuilder *builder, *builder2;
         GList *l;
 
-        if (*state == NULL) {
-                /* First iteration */
 #ifdef UTXDB_LOG
-                if (setutxdb (UTXDB_LOG, NULL) != 0) {
-                        return NULL;
-                }
+        if (setutxdb (UTXDB_LOG, NULL) != 0) {
+                return NULL;
+        }
 #else
-                utmpxname (PATH_WTMP);
-                setutxent ();
+        utmpxname (PATH_WTMP);
+        setutxent ();
 #endif
-                *state = g_new (WTmpGeneratorState, 1);
-                state_data = *state;
-                state_data->login_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
-                state_data->logout_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
-        }
 
-        /* Every iteration */
-        state_data = *state;
-        login_hash = state_data->login_hash;
-        logout_hash = state_data->logout_hash;
+        login_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+        logout_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
         while ((wtmp_entry = getutxent ())) {
                 UserAccounting    *accounting;
                 UserPreviousLogin *previous_login;
 
                 if (wtmp_entry->ut_type == BOOT_TIME) {
                         /* Set boot time for missing logout records */
                         g_hash_table_iter_init (&iter, logout_hash);
                         while (g_hash_table_iter_next (&iter, &key, &value)) {
                                 previous_login = (UserPreviousLogin *) value;
 
                                 if (previous_login->logout_time == 0) {
                                         previous_login->logout_time = wtmp_entry->ut_tv.tv_sec;
                                 }
                         }
                         g_hash_table_remove_all (logout_hash);
                 } else if (wtmp_entry->ut_type == DEAD_PROCESS) {
                         /* Save corresponding logout time */
                         if (g_hash_table_lookup_extended (logout_hash, wtmp_entry->ut_line, &key, &value)) {
                                 previous_login = (UserPreviousLogin *) value;
                                 previous_login->logout_time = wtmp_entry->ut_tv.tv_sec;
 
                                 g_hash_table_remove (logout_hash, previous_login->id);
                         }
                 }
 
                 if (wtmp_entry->ut_type != USER_PROCESS) {
                         continue;
                 }
 
                 if (wtmp_entry->ut_user[0] == 0) {
@@ -233,103 +221,96 @@ entry_generator_wtmp (GHashTable   *users,
                 }
 
                 pwent = getpwnam (wtmp_entry->ut_user);
                 if (pwent == NULL) {
                         continue;
                 }
 
                 if (!g_hash_table_lookup_extended (login_hash,
                                                    wtmp_entry->ut_user,
                                                    &key, &value)) {
                         accounting = g_new (UserAccounting, 1);
                         accounting->frequency = 0;
                         accounting->previous_logins = NULL;
 
                         g_hash_table_insert (login_hash, g_strdup (wtmp_entry->ut_user), accounting);
                 } else {
                         accounting = value;
                 }
 
                 accounting->frequency++;
                 accounting->time = wtmp_entry->ut_tv.tv_sec;
 
                 /* Add zero logout time to change it later on logout record */
                 previous_login = g_new (UserPreviousLogin, 1);
                 previous_login->id = g_strdup (wtmp_entry->ut_line);
                 previous_login->login_time = wtmp_entry->ut_tv.tv_sec;
                 previous_login->logout_time = 0;
                 accounting->previous_logins = g_list_prepend (accounting->previous_logins, previous_login);
 
                 g_hash_table_insert (logout_hash, g_strdup (wtmp_entry->ut_line), previous_login);
-                *spent = getspnam (pwent->pw_name);
-
-                return pwent;
         }
 
-        /* Last iteration */
         endutxent ();
 
         g_hash_table_iter_init (&iter, login_hash);
         while (g_hash_table_iter_next (&iter, &key, &value)) {
                 UserAccounting    *accounting = (UserAccounting *) value;
                 UserPreviousLogin *previous_login;
 
                 user = g_hash_table_lookup (users, key);
                 if (user == NULL) {
                         g_list_free_full (accounting->previous_logins, (GDestroyNotify) user_previous_login_free);
                         continue;
                 }
 
                 g_object_set (user, "login-frequency", accounting->frequency, NULL);
                 g_object_set (user, "login-time", accounting->time, NULL);
 
                 builder = g_variant_builder_new (G_VARIANT_TYPE ("a(xxa{sv})"));
                 for (l = g_list_last (accounting->previous_logins); l != NULL; l = l->prev) {
                         previous_login = l->data;
 
                         builder2 = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
                         g_variant_builder_add (builder2, "{sv}", "type", g_variant_new_string (previous_login->id));
                         g_variant_builder_add (builder, "(xxa{sv})", previous_login->login_time, previous_login->logout_time, builder2);
                         g_variant_builder_unref (builder2);
                 }
                 g_object_set (user, "login-history", g_variant_new ("a(xxa{sv})", builder), NULL);
                 g_variant_builder_unref (builder);
                 g_list_free_full (accounting->previous_logins, (GDestroyNotify) user_previous_login_free);
 
                 user_changed (user);
         }
 
         g_hash_table_unref (login_hash);
         g_hash_table_unref (logout_hash);
-        g_free (state_data);
-        *state = NULL;
-        return NULL;
 }
 #endif /* HAVE_UTMPX_H */
 
 #ifndef MAX_LOCAL_USERS
 #define MAX_LOCAL_USERS 50
 #endif
 
 static struct passwd *
 entry_generator_fgetpwent (GHashTable   *users,
                            gpointer     *state,
                            struct spwd **spent)
 {
         struct passwd *pwent;
 
         struct {
                 struct spwd spbuf;
                 char buf[1024];
         } *shadow_entry_buffers;
 
         struct {
                 FILE *fp;
                 GHashTable *users;
         } *generator_state;
 
         /* First iteration */
         if (*state == NULL) {
                 GHashTable *shadow_users = NULL;
                 FILE *fp;
                 struct spwd *shadow_entry;
 
@@ -533,64 +514,65 @@ create_users_hash_table (void)
                                       g_object_unref);
 }
 
 static void
 reload_users (Daemon *daemon)
 {
         GHashTable *users;
         GHashTable *old_users;
         GHashTable *local;
         GHashTableIter iter;
         gpointer name;
         User *user;
 
         /* 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, 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);
 
         /* Now add/update users from other sources, possibly non-local */
+        load_entries (daemon, users, entry_generator_cachedir);
+
 #ifdef HAVE_UTMPX_H
-        load_entries (daemon, users, entry_generator_wtmp);
+        wtmp_update_login_frequencies (users);
 #endif
-        load_entries (daemon, users, entry_generator_cachedir);
 
         /* Mark which users are local, which are not */
         g_hash_table_iter_init (&iter, users);
         while (g_hash_table_iter_next (&iter, &name, (gpointer *)&user))
                 user_update_local_account_property (user, g_hash_table_lookup (local, name) != NULL);
 
         g_hash_table_destroy (local);
 
         /* Swap out the users */
         old_users = daemon->priv->users;
         daemon->priv->users = users;
 
         /* Remove all the old users */
         g_hash_table_iter_init (&iter, old_users);
         while (g_hash_table_iter_next (&iter, &name, (gpointer *)&user)) {
                 if (!g_hash_table_lookup (users, name)) {
                         user_unregister (user);
                         accounts_accounts_emit_user_deleted (ACCOUNTS_ACCOUNTS (daemon),
                                                              user_get_object_path (user));
                 }
         }
 
         /* Register all the new users */
         g_hash_table_iter_init (&iter, users);
         while (g_hash_table_iter_next (&iter, &name, (gpointer *)&user)) {
                 if (!g_hash_table_lookup (old_users, name)) {
                         user_register (user);
                         accounts_accounts_emit_user_added (ACCOUNTS_ACCOUNTS (daemon),
                                                            user_get_object_path (user));
                 }
-- 
2.7.4