From 14ca4245b6c842b9c69f0c245bdd6d38bd7f1cb6 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Wed, 29 Jun 2016 15:57:38 -0400 Subject: [PATCH] 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 | 80 ++++++++++++++++++++++++++++++++++++++----------------- src/user.c | 11 ++------ src/user.h | 4 ++- src/wtmp-helper.c | 11 +++++--- src/wtmp-helper.h | 6 +++-- 5 files changed, 71 insertions(+), 41 deletions(-) diff --git a/src/daemon.c b/src/daemon.c index 1b2bacd..2010b82 100644 --- a/src/daemon.c +++ b/src/daemon.c @@ -51,296 +51,319 @@ #define PATH_PASSWD "/etc/passwd" #define PATH_SHADOW "/etc/shadow" #define PATH_GROUP "/etc/group" #define PATH_GDM_CUSTOM "/etc/gdm/custom.conf" enum { PROP_0, PROP_DAEMON_VERSION }; struct DaemonPrivate { GDBusConnection *bus_connection; GHashTable *users; User *autologin; GFileMonitor *passwd_monitor; GFileMonitor *shadow_monitor; GFileMonitor *group_monitor; GFileMonitor *gdm_monitor; GFileMonitor *wtmp_monitor; guint reload_id; guint autologin_id; PolkitAuthority *authority; GHashTable *extension_ifaces; }; -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 } GType error_get_type (void) { static GType etype = 0; if (etype == 0) { static const GEnumValue values[] = { ENUM_ENTRY (ERROR_FAILED, "Failed"), ENUM_ENTRY (ERROR_USER_EXISTS, "UserExists"), ENUM_ENTRY (ERROR_USER_DOES_NOT_EXIST, "UserDoesntExist"), ENUM_ENTRY (ERROR_PERMISSION_DENIED, "PermissionDenied"), ENUM_ENTRY (ERROR_NOT_SUPPORTED, "NotSupported"), { 0, 0, 0 } }; g_assert (NUM_ERRORS == G_N_ELEMENTS (values) - 1); etype = g_enum_register_static ("Error", values); } return etype; } #ifndef HAVE_FGETPWENT #include "fgetpwent.c" #endif 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 (); @@ -658,115 +681,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 2cf4450..b2ea4be 100644 --- a/src/user.c +++ b/src/user.c @@ -119,65 +119,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) { @@ -222,93 +220,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 diff --git a/src/wtmp-helper.c b/src/wtmp-helper.c index 4986e16..d9a43ea 100644 --- a/src/wtmp-helper.c +++ b/src/wtmp-helper.c @@ -47,62 +47,63 @@ typedef struct { } WTmpGeneratorState; static void user_previous_login_free (UserPreviousLogin *previous_login) { g_free (previous_login->id); g_free (previous_login); } static gboolean wtmp_helper_start (void) { #if defined(HAVE_SETUTXDB) if (setutxdb (UTXDB_LOG, NULL) != 0) { return FALSE; } #elif defined(PATH_WTMP) if (utmpxname (PATH_WTMP) != 0) { return FALSE; } setutxent (); #else #error You have utmpx.h, but no known way to use it for wtmp entries #endif return TRUE; } struct passwd * -wtmp_helper_entry_generator (GHashTable *users, - gpointer *state) +wtmp_helper_entry_generator (GHashTable *users, + gpointer *state, + struct spwd **shadow_entry) { 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 */ if (!wtmp_helper_start ()) { return NULL; } *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; while ((wtmp_entry = getutxent ())) { UserAccounting *accounting; @@ -147,95 +148,97 @@ wtmp_helper_entry_generator (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); + *shadow_entry = 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; } const gchar * wtmp_helper_get_path_for_monitor (void) { return PATH_WTMP; } #else /* HAVE_UTMPX_H */ struct passwd * -wtmp_helper_entry_generator (GHashTable *users, - gpointer *state) +wtmp_helper_entry_generator (GHashTable *users, + gpointer *state, + struct spwd **shadow_entry) { return NULL; } const gchar * wtmp_helper_get_path_for_monitor (void) { return NULL; } #endif /* HAVE_UTMPX_H */ diff --git a/src/wtmp-helper.h b/src/wtmp-helper.h index 7058415..fa65ead 100644 --- a/src/wtmp-helper.h +++ b/src/wtmp-helper.h @@ -1,31 +1,33 @@ /* * Copyright (C) 2014 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 */ #ifndef __WTMP_HELPER_H__ #define __WTMP_HELPER_H__ #include #include +#include const gchar * wtmp_helper_get_path_for_monitor (void); -struct passwd * wtmp_helper_entry_generator (GHashTable *users, - gpointer *state); +struct passwd * wtmp_helper_entry_generator (GHashTable *users, + gpointer *state, + struct spwd **shadow_entry); #endif /* __WTMP_HELPER_H__ */ -- 2.7.4