summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Lortie <desrt@desrt.ca>2013-06-26 12:39:05 -0400
committerRay Strode <rstrode@redhat.com>2013-10-01 11:17:43 -0400
commit0a7e6f638e643fca3e08c3cf47fbc7aa3c5c1582 (patch)
tree8a753b1836e60ca4225e22c0f30fc9cfc56af5bc
parent499bfeb4bce5841dc67118737d78eb8aead14ef0 (diff)
Clean up user classification logic
Bring back the simple login.defs-based check for if a user is human or not and enable it by default. Add a build option --enable-user-heuristics to get the old behaviour back again. Split out all human vs. system user divination into a new file, user-classify.c in order to clean up daemon.c a bit.
-rw-r--r--configure.ac6
-rw-r--r--src/Makefile.am2
-rw-r--r--src/daemon.c123
-rw-r--r--src/daemon.h4
-rw-r--r--src/user-classify.c248
-rw-r--r--src/user-classify.h32
-rw-r--r--src/user.c9
7 files changed, 293 insertions, 131 deletions
diff --git a/configure.ac b/configure.ac
index d555bef..47e9c67 100644
--- a/configure.ac
+++ b/configure.ac
@@ -54,6 +54,12 @@ AS_IF([test x$enable_admin_group = xauto], [
])
AC_DEFINE_UNQUOTED([ADMIN_GROUP], ["$enable_admin_group"], [Define to the group for administrator users])
+AC_ARG_ENABLE(user-heuristics,
+ [AS_HELP_STRING([--enable-user-heuristics],[Enable heuristics for guessing system vs. human users])],
+ [if test "$enableval" = yes; then
+ AC_DEFINE([ENABLE_USER_HEURISTICS], , [System vs. human user heuristics enabled])
+ fi])
+
dnl ---------------------------------------------------------------------------
dnl - Warnings
dnl ---------------------------------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index 13c57ca..a5bfcf9 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -35,6 +35,8 @@ accounts_daemon_SOURCES = \
daemon.h \
daemon.c \
extensions.c \
+ user-classify.h \
+ user-classify.c \
user.h \
user.c \
util.h \
diff --git a/src/daemon.c b/src/daemon.c
index cd09e2c..eb75ec9 100644
--- a/src/daemon.c
+++ b/src/daemon.c
@@ -43,49 +43,17 @@
#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_NOLOGIN "/sbin/nologin"
-#define PATH_FALSE "/bin/false"
#define PATH_GDM_CUSTOM "/etc/gdm/custom.conf"
#ifdef HAVE_UTMPX_H
#define PATH_WTMP _PATH_WTMPX
#endif
-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",
- NULL
-};
-
enum {
PROP_0,
PROP_DAEMON_VERSION
@@ -96,7 +64,6 @@ struct DaemonPrivate {
GDBusProxy *bus_proxy;
GHashTable *users;
- GHashTable *exclusions;
User *autologin;
@@ -167,78 +134,6 @@ error_get_type (void)
return etype;
}
-gboolean
-daemon_local_user_is_excluded (Daemon *daemon,
- const gchar *username,
- const gchar *shell,
- const gchar *password_hash)
-{
- int ret;
-
- if (g_hash_table_lookup (daemon->priv->exclusions, username)) {
- return TRUE;
- }
-
- 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;
-}
-
#ifdef HAVE_UTMPX_H
typedef struct {
@@ -520,7 +415,7 @@ load_entries (Daemon *daemon,
break;
/* Skip system users... */
- if (daemon_local_user_is_excluded (daemon, pwent->pw_name, pwent->pw_shell, NULL)) {
+ if (!user_classify_is_human (pwent->pw_uid, pwent->pw_name, pwent->pw_shell, NULL)) {
g_debug ("skipping user: %s", pwent->pw_name);
continue;
}
@@ -750,25 +645,13 @@ on_gdm_monitor_changed (GFileMonitor *monitor,
static void
daemon_init (Daemon *daemon)
{
- gint i;
GFile *file;
GError *error;
daemon->priv = DAEMON_GET_PRIVATE (daemon);
- daemon->priv->exclusions = g_hash_table_new_full (g_str_hash,
- g_str_equal,
- g_free,
- NULL);
-
daemon->priv->extension_ifaces = daemon_read_extension_ifaces ();
- for (i = 0; default_excludes[i] != NULL; i++) {
- g_hash_table_insert (daemon->priv->exclusions,
- g_strdup (default_excludes[i]),
- GUINT_TO_POINTER (TRUE));
- }
-
daemon->priv->users = create_users_hash_table ();
file = g_file_new_for_path (PATH_PASSWD);
@@ -1092,7 +975,7 @@ finish_list_cached_users (gpointer user_data)
uid = user_get_uid (user);
shell = user_get_shell (user);
- if (daemon_local_user_is_excluded (data->daemon, name, shell, NULL)) {
+ if (!user_classify_is_human (uid, name, shell, NULL)) {
g_debug ("user %s %ld excluded", name, (long) uid);
continue;
}
diff --git a/src/daemon.h b/src/daemon.h
index 6792f50..b7e072e 100644
--- a/src/daemon.h
+++ b/src/daemon.h
@@ -76,10 +76,6 @@ User *daemon_local_find_user_by_id (Daemon *daemon,
User *daemon_local_find_user_by_name (Daemon *daemon,
const gchar *name);
User *daemon_local_get_automatic_login_user (Daemon *daemon);
-gboolean daemon_local_user_is_excluded (Daemon *daemon,
- const gchar *name,
- const gchar *shell,
- const gchar *password_hash);
typedef void (*AuthorizedCallback) (Daemon *daemon,
User *user,
diff --git a/src/user-classify.c b/src/user-classify.c
new file mode 100644
index 0000000..b68c9ae
--- /dev/null
+++ b/src/user-classify.c
@@ -0,0 +1,248 @@
+/*
+ * 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 <desrt@desrt.ca>
+ * Matthias Clasen <mclasen@redhat.com>
+ */
+
+#include "config.h"
+
+#include "user-classify.h"
+
+#include <string.h>
+
+#ifdef ENABLE_USER_HEURISTICS
+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"
+};
+
+#define PATH_NOLOGIN "/sbin/nologin"
+#define PATH_FALSE "/bin/false"
+
+static gboolean
+user_classify_is_excluded_by_heuristics (const gchar *username,
+ const gchar *shell,
+ const gchar *password_hash)
+{
+ static GHashTable *exclusions;
+ gboolean ret = FALSE;
+
+ 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;
+ }
+
+ 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;
+}
+
+#else /* ENABLE_USER_HEURISTICS */
+
+static gboolean
+user_classify_parse_login_defs_field (const gchar *contents,
+ const gchar *key,
+ uid_t *result)
+{
+ gsize key_len;
+ gint64 value;
+ gchar *end;
+
+ key_len = strlen (key);
+
+ for (;;) {
+ /* Our key has to be at the start of the line, followed by whitespace */
+ if (strncmp (contents, key, key_len) == 0 && g_ascii_isspace (contents[key_len])) {
+ /* Found it. Move contents past the key itself and break out. */
+ contents += key_len;
+ break;
+ }
+
+ /* Didn't find it. Find the end of the line. */
+ contents = strchr (contents, '\n');
+
+ /* EOF? */
+ if (!contents) {
+ /* We didn't find the field... */
+ return FALSE;
+ }
+
+ /* Start at the beginning of the next line on next iteration. */
+ contents++;
+ }
+
+ /* 'contents' now points at the whitespace character just after
+ * the field name. strtoll can deal with that.
+ */
+ value = g_ascii_strtoll (contents, &end, 10);
+
+ if (*end && !g_ascii_isspace (*end)) {
+ g_warning ("Trailing junk after '%s' field in login.defs", key);
+ return FALSE;
+ }
+
+ if (value <= 0 || value >= G_MAXINT32) {
+ g_warning ("Value for '%s' field out of range", key);
+ return FALSE;
+ }
+
+ *result = value;
+
+ return TRUE;
+}
+
+static void
+user_classify_read_login_defs (uid_t *min_uid,
+ uid_t *max_uid)
+{
+ GError *error = NULL;
+ char *contents;
+
+ if (!g_file_get_contents ("/etc/login.defs", &contents, NULL, &error)) {
+ g_warning ("Could not open /etc/login.defs: %s. Falling back to default human uid range of %d to %d",
+ error->message, (int) *min_uid, (int) *max_uid);
+ g_error_free (error);
+ return;
+ }
+
+ if (!user_classify_parse_login_defs_field (contents, "UID_MIN", min_uid)) {
+ g_warning ("Could not find UID_MIN value in login.defs. Using default of %d", (int) *min_uid);
+ }
+
+ if (!user_classify_parse_login_defs_field (contents, "UID_MAX", max_uid)) {
+ g_warning ("Could not find UID_MIN value in login.defs. Using default of %d", (int) *max_uid);
+ }
+
+ g_free (contents);
+}
+
+static gboolean
+user_classify_is_in_human_range (uid_t uid)
+{
+ static uid_t min_uid = 1000, max_uid = 60000;
+ static gboolean initialised;
+
+ if (!initialised) {
+ user_classify_read_login_defs (&min_uid, &max_uid);
+ initialised = TRUE;
+ }
+
+ return min_uid <= uid && uid <= max_uid;
+}
+#endif /* ENABLE_USER_HEURISTICS */
+
+gboolean
+user_classify_is_human (uid_t uid,
+ const gchar *username,
+ const gchar *shell,
+ const gchar *password_hash)
+{
+#ifdef ENABLE_USER_HEURISTICS
+ return !user_classify_is_excluded_by_heuristics (username, shell, password_hash);
+#else
+ return user_classify_is_in_human_range (uid);
+#endif
+}
diff --git a/src/user-classify.h b/src/user-classify.h
new file mode 100644
index 0000000..3e3a391
--- /dev/null
+++ b/src/user-classify.h
@@ -0,0 +1,32 @@
+/*
+ * 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
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#ifndef __USER_CLASSIFY_H__
+#define __USER_CLASSIFY_H__
+
+#include <sys/types.h>
+#include <glib.h>
+
+gboolean user_classify_is_human (uid_t uid,
+ const gchar *username,
+ const gchar *shell,
+ const gchar *password_hash);
+
+#endif /* __USER_CLASSIFY_H__ */
diff --git a/src/user.c b/src/user.c
index 6492a62..1f28e78 100644
--- a/src/user.c
+++ b/src/user.c
@@ -42,6 +42,7 @@
#include <gio/gunixinputstream.h>
#include <polkit/polkit.h>
+#include "user-classify.h"
#include "daemon.h"
#include "user.h"
#include "accounts-user-generated.h"
@@ -291,13 +292,7 @@ user_update_from_pwent (User *user,
g_object_notify (G_OBJECT (user), "password-mode");
}
- /* FIXME: this relies on heuristics that don't always come out
- * right.
- */
- user->system_account = daemon_local_user_is_excluded (user->daemon,
- user->user_name,
- pwent->pw_shell,
- passwd);
+ user->system_account = !user_classify_is_human (user->uid, user->user_name, pwent->pw_shell, passwd);
g_object_thaw_notify (G_OBJECT (user));