From 44ca2685d274da026e81221821a4c342eba2a641 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Nov 03 2016 06:19:22 +0000 Subject: import accountsservice-0.6.35-12.el7 --- diff --git a/SOURCES/0001-configure-actually-define-HAVE_GETUSERSHELL.patch b/SOURCES/0001-configure-actually-define-HAVE_GETUSERSHELL.patch new file mode 100644 index 0000000..2e39df9 --- /dev/null +++ b/SOURCES/0001-configure-actually-define-HAVE_GETUSERSHELL.patch @@ -0,0 +1,86 @@ +From 8e3bad7b7eb7212aa7554fa445b2e8e29daaacaa Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 6 Sep 2016 15:33:30 -0400 +Subject: [PATCH] configure: actually define HAVE_GETUSERSHELL + +currently we check for it but never actually set it, leading to +dead code. + +This commit fixes that. +--- + configure.ac | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/configure.ac b/configure.ac +index eb5360e..54f4eb4 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -160,60 +160,65 @@ AC_CACHE_CHECK([for supported warning flags], accountsservice_cv_warn_cflags, [ + ACCOUNTSSERVICE_CC_TRY_FLAG([$W], [WARN_CFLAGS="$WARN_CFLAGS $W"]) + done + + accountsservice_cv_warn_cflags=$WARN_CFLAGS + accountsservice_cv_warn_maybe=$MAYBE_WARN + + AC_MSG_CHECKING([which warning flags were supported]) + ]) + + WARN_CFLAGS="$accountsservice_cv_warn_cflags" + + if test "$GCC" = "yes" -a "$set_more_warnings" != "no"; then + # Only add this when optimizing is enabled (default) + AC_MSG_CHECKING([whether optimization is enabled]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#if __OPTIMIZE__ == 0 + #error No optimization + #endif + ]], [[]])], + [has_optimization=yes], + [has_optimization=no]) + if test $has_optimization = yes; then + WARN_CFLAGS="$WARN_CFLAGS -Wp,-D_FORTIFY_SOURCE=2" + fi + AC_MSG_RESULT($has_optimization) + fi + + AC_SUBST(WARN_CFLAGS) + + AC_CHECK_HEADERS([shadow.h utmpx.h]) + ++AC_CHECK_LIB(c, getusershell, have_getusershell=yes, have_getusershell=no) ++if test x$have_getusershell = xyes; then ++ AC_DEFINE(HAVE_GETUSERSHELL, 1, [Define i getusershell() is available]) ++fi ++ + dnl --------------------------------------------------------------------------- + dnl - gtk-doc Documentation + dnl --------------------------------------------------------------------------- + + m4_ifdef([GTK_DOC_CHECK], [ + GTK_DOC_CHECK([1.15], [--flavour no-tmpl]) + ],[ + AM_CONDITIONAL([ENABLE_GTK_DOC],[false]) + ]) + + dnl --------------------------------------------------------------------------- + dnl - DocBook Documentation + dnl --------------------------------------------------------------------------- + + AC_ARG_ENABLE(docbook-docs, + [AS_HELP_STRING([--enable-docbook-docs],[build documentation (requires xmlto)])], + enable_docbook_docs=$enableval,enable_docbook_docs=no) + AC_PATH_PROG(XMLTO, xmlto, no) + AC_MSG_CHECKING([whether to build DocBook documentation]) + if test x$XMLTO = xno ; then + have_docbook=no + else + have_docbook=yes + fi + if test x$enable_docbook_docs = xauto ; then + if test x$have_docbook = xno ; then + enable_docbook_docs=no + else + enable_docbook_docs=yes + fi +-- +2.7.4 + diff --git a/SOURCES/0001-user-classify-exclude-nologin-users.patch b/SOURCES/0001-user-classify-exclude-nologin-users.patch new file mode 100644 index 0000000..40f2963 --- /dev/null +++ b/SOURCES/0001-user-classify-exclude-nologin-users.patch @@ -0,0 +1,231 @@ +From 450731558cbd5c77aa6932d35f27abf898553db6 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 6 Sep 2016 13:54:48 -0400 +Subject: [PATCH] user-classify: exclude nologin users + +Sometimes admins set a user's shell to nologin to hide it from +the user list. This commit fixes accountsservice so that behavior +works again. +--- + src/user-classify.c | 77 +++++++++++++++++++++++++++++------------------------ + 1 file changed, 42 insertions(+), 35 deletions(-) + +diff --git a/src/user-classify.c b/src/user-classify.c +index 69e6809..b79a35f 100644 +--- a/src/user-classify.c ++++ b/src/user-classify.c +@@ -1,169 +1,176 @@ + /* + * 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 licence, 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 + * + * Authors: Ryan Lortie + * Matthias Clasen + */ + + #include "config.h" + + #include "user-classify.h" + + #include ++#include + + static const char *default_excludes[] = { + "bin", + "root", + "daemon", + "adm", + "lp", + "sync", + "shutdown", + "halt", + "mail", + "news", + "uucp", + "operator", + "nobody", + "nobody4", + "noaccess", + "postgres", + "pvm", + "rpm", + "nfsnobody", + "pcap", + "mysql", + "ftp", + "games", + "man", + "at", + "gdm", + "gnome-initial-setup" + }; + + static gboolean + user_classify_is_blacklisted (const char *username) + { + static GHashTable *exclusions; + + if (exclusions == NULL) { + guint i; + + exclusions = g_hash_table_new (g_str_hash, g_str_equal); + + for (i = 0; i < G_N_ELEMENTS (default_excludes); i++) { + g_hash_table_add (exclusions, (gpointer) default_excludes[i]); + } + } + + if (g_hash_table_contains (exclusions, username)) { + return TRUE; + } + + return FALSE; + } + + #define PATH_NOLOGIN "/sbin/nologin" + #define PATH_FALSE "/bin/false" + + #ifdef ENABLE_USER_HEURISTICS + static gboolean + user_classify_is_excluded_by_heuristics (const gchar *username, +- const gchar *shell, + const gchar *password_hash) + { + gboolean ret = FALSE; + +- if (shell != NULL) { +- char *basename, *nologin_basename, *false_basename; +- +-#ifdef HAVE_GETUSERSHELL +- char *valid_shell; +- +- ret = TRUE; +- setusershell (); +- while ((valid_shell = getusershell ()) != NULL) { +- if (g_strcmp0 (shell, valid_shell) != 0) +- continue; +- ret = FALSE; +- } +- endusershell (); +-#endif +- +- basename = g_path_get_basename (shell); +- nologin_basename = g_path_get_basename (PATH_NOLOGIN); +- false_basename = g_path_get_basename (PATH_FALSE); +- +- if (shell[0] == '\0') { +- ret = TRUE; +- } else if (g_strcmp0 (basename, nologin_basename) == 0) { +- ret = TRUE; +- } else if (g_strcmp0 (basename, false_basename) == 0) { +- ret = TRUE; +- } +- +- g_free (basename); +- g_free (nologin_basename); +- g_free (false_basename); +- } +- + if (password_hash != NULL) { + /* skip over the account-is-locked '!' prefix if present */ + if (password_hash[0] == '!') + password_hash++; + + if (password_hash[0] != '\0') { + /* modern hashes start with "$n$" */ + if (password_hash[0] == '$') { + if (strlen (password_hash) < 4) + ret = TRUE; + + /* DES crypt is base64 encoded [./A-Za-z0-9]* + */ + } else if (!g_ascii_isalnum (password_hash[0]) && + password_hash[0] != '.' && + password_hash[0] != '/') { + ret = TRUE; + } + } + + } + + return ret; + } + #endif /* ENABLE_USER_HEURISTICS */ + ++static gboolean ++is_invalid_shell (const char *shell) ++{ ++ char *basename, *nologin_basename, *false_basename; ++ int ret = FALSE; ++ ++#ifdef HAVE_GETUSERSHELL ++ char *valid_shell; ++ ++ setusershell (); ++ while ((valid_shell = getusershell ()) != NULL) { ++ if (g_strcmp0 (shell, valid_shell) != 0) ++ continue; ++ ret = FALSE; ++ } ++ endusershell (); ++#endif ++ ++ basename = g_path_get_basename (shell); ++ nologin_basename = g_path_get_basename (PATH_NOLOGIN); ++ false_basename = g_path_get_basename (PATH_FALSE); ++ ++ if (shell[0] == '\0') { ++ ret = TRUE; ++ } else if (g_strcmp0 (basename, nologin_basename) == 0) { ++ ret = TRUE; ++ } else if (g_strcmp0 (basename, false_basename) == 0) { ++ ret = TRUE; ++ } ++ ++ g_free (basename); ++ g_free (nologin_basename); ++ g_free (false_basename); ++ ++ return ret; ++} ++ + gboolean + user_classify_is_human (uid_t uid, + const gchar *username, + const gchar *shell, + const gchar *password_hash) + { + if (user_classify_is_blacklisted (username)) + return FALSE; + ++ if (shell != NULL && is_invalid_shell (shell)) ++ return FALSE; ++ + #ifdef ENABLE_USER_HEURISTICS + /* only do heuristics on the range 500-1000 to catch one off migration problems in Fedora */ + if (uid >= 500 && uid < MINIMUM_UID) { +- if (!user_classify_is_excluded_by_heuristics (username, shell, password_hash)) ++ if (!user_classify_is_excluded_by_heuristics (username, password_hash)) + return TRUE; + } + #endif + + return uid >= MINIMUM_UID; + } +-- +2.7.4 + diff --git a/SOURCES/fix-log-leak.patch b/SOURCES/fix-log-leak.patch new file mode 100644 index 0000000..1e59714 --- /dev/null +++ b/SOURCES/fix-log-leak.patch @@ -0,0 +1,79 @@ +From 1ff7d912fe329867a40c8e473cf0bde8d55202ef Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Fri, 1 Jul 2016 08:13:09 -0400 +Subject: [PATCH] main: fix leak in log handler + +--- + src/main.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/src/main.c b/src/main.c +index 2f799e5..cc62e05 100644 +--- a/src/main.c ++++ b/src/main.c +@@ -94,60 +94,62 @@ on_bus_acquired (GDBusConnection *connection, + static void + on_name_lost (GDBusConnection *connection, + const gchar *name, + gpointer 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) + { + GString *string; + const gchar *progname; + int ret G_GNUC_UNUSED; + + string = g_string_new (NULL); + + progname = g_get_prgname (); + g_string_append_printf (string, "(%s:%lu): %s%sDEBUG: %s\n", + progname ? progname : "process", (gulong)getpid (), + log_domain ? log_domain : "", log_domain ? "-" : "", + message ? message : "(NULL) message"); + + ret = write (1, string->str, string->len); ++ ++ g_string_free (string, TRUE); + } + + static void + log_handler (const gchar *domain, + GLogLevelFlags level, + const gchar *message, + gpointer data) + { + /* filter out DEBUG messages if debug isn't set */ + if ((level & G_LOG_LEVEL_MASK) == G_LOG_LEVEL_DEBUG && !debug) + return; + + g_log_default_handler (domain, level, message, data); + } + + static gboolean + on_signal_quit (gpointer data) + { + g_main_loop_quit (data); + return FALSE; + } + + int + main (int argc, char *argv[]) + { + GError *error; + gint ret; + GBusNameOwnerFlags flags; + GOptionContext *context; + static gboolean replace; +-- +2.7.4 + diff --git a/SOURCES/scale-better.patch b/SOURCES/scale-better.patch new file mode 100644 index 0000000..98a0517 --- /dev/null +++ b/SOURCES/scale-better.patch @@ -0,0 +1,1667 @@ +From 2b23e50057fc92da30093fbebb78b320fc4107d0 Mon Sep 17 00:00:00 2001 +From: Ray Strode +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 +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 + */ + + #include "config.h" + + #include + #include + #include + #include + #include + #include + #include ++#ifdef HAVE_SHADOW_H ++#include ++#endif + #include + #include + #include + #ifdef HAVE_UTMPX_H + #include + #endif + + #include + #include + #include + #include + #include + #include + + #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 +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 + #include ++#include + + #include + #include + + #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 +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 +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 + diff --git a/SPECS/accountsservice.spec b/SPECS/accountsservice.spec index e21e4fa..cdce0e5 100644 --- a/SPECS/accountsservice.spec +++ b/SPECS/accountsservice.spec @@ -2,7 +2,7 @@ Name: accountsservice Version: 0.6.35 -Release: 9%{?dist} +Release: 12%{?dist} Summary: D-Bus interfaces for querying and manipulating user account information Group: System Environment/Daemons @@ -35,6 +35,10 @@ Patch3: userdel-f.patch Patch4: fix-leak.patch Patch5: 0001-Add-asynchronous-api-for-user-uncaching.patch Patch6: 0001-systemd-ensure-that-accounts-service-starts-after-NS.patch +Patch7: scale-better.patch +Patch8: fix-log-leak.patch +Patch9: 0001-user-classify-exclude-nologin-users.patch +Patch10: 0001-configure-actually-define-HAVE_GETUSERSHELL.patch %package libs Summary: Client-side library to talk to accountsservice @@ -72,6 +76,10 @@ of these interfaces, based on the useradd, usermod and userdel commands. %patch4 -p1 -b .fix-leak %patch5 -p1 -b .async-api-uncaching %patch6 -p1 -b .start-after-nsswitch +%patch7 -p1 -b .scale-better +%patch8 -p1 -b .fix-log-leak +%patch9 -p1 -b .hide-nologin-users +%patch10 -p1 -b .define-HAVE_GETUSERSHELL %build autoreconf -f -i @@ -126,6 +134,21 @@ rm $RPM_BUILD_ROOT%{_libdir}/*.a %{_datadir}/gtk-doc/html/libaccountsservice/* %changelog +* Tue Sep 06 2016 Ray Strode - 0.6.35-12 +- hide users with /sbin/nologin shell + Resolves: #1341276 + +* Fri Jul 01 2016 Ray Strode - 0.6.35-11 +- address minor bug in previous patch + Related: #1220904 + +- fix memory leak in log handler + Resolves: #1292543 + +* Thu Jun 30 2016 Ray Strode - 0.6.35-10 +- scale to a large number of remote users better + Resolves: #1220904 + * Mon Jun 08 2015 Ray Strode 0.6.35-9 - Start after nsswitch and ypbind Resolves: #1217439