|
|
7f2946 |
From 1ef1e18936f1bfad019f3d6427135629f4bdc2a1 Mon Sep 17 00:00:00 2001
|
|
|
7f2946 |
From: Ray Strode <rstrode@redhat.com>
|
|
|
7f2946 |
Date: Fri, 15 Nov 2013 10:11:15 -0500
|
|
|
7f2946 |
Subject: [PATCH] Change up user classification logic again
|
|
|
7f2946 |
|
|
|
7f2946 |
relying on login.defs is fragile, and the
|
|
|
7f2946 |
user heuristics are fragile.
|
|
|
7f2946 |
|
|
|
7f2946 |
This commit requires an explicit uid minimum
|
|
|
7f2946 |
get configured, and heuristics now only get
|
|
|
7f2946 |
applied to the specific problematic range
|
|
|
7f2946 |
they were added to address.
|
|
|
7f2946 |
|
|
|
7f2946 |
https://bugs.freedesktop.org/show_bug.cgi?id=71801
|
|
|
7f2946 |
---
|
|
|
7f2946 |
configure.ac | 8 +++-
|
|
|
7f2946 |
src/user-classify.c | 129 ++++++++++------------------------------------------
|
|
|
7f2946 |
2 files changed, 32 insertions(+), 105 deletions(-)
|
|
|
7f2946 |
|
|
|
7f2946 |
diff --git a/configure.ac b/configure.ac
|
|
|
7f2946 |
index a7f4e20..eb5360e 100644
|
|
|
7f2946 |
--- a/configure.ac
|
|
|
7f2946 |
+++ b/configure.ac
|
|
|
7f2946 |
@@ -28,65 +28,71 @@ AC_SUBST(LT_AGE)
|
|
|
7f2946 |
PKG_CHECK_MODULES(GIO, gio-2.0 gio-unix-2.0)
|
|
|
7f2946 |
PKG_CHECK_MODULES(POLKIT, gio-unix-2.0 polkit-gobject-1)
|
|
|
7f2946 |
|
|
|
7f2946 |
AM_MAINTAINER_MODE([enable])
|
|
|
7f2946 |
|
|
|
7f2946 |
# client library dependencies
|
|
|
7f2946 |
LIBACCOUNTSSERVICE_LIBS="$GIO_LIBS"
|
|
|
7f2946 |
AC_SUBST(LIBACCOUNTSSERVICE_LIBS)
|
|
|
7f2946 |
LIBACCOUNTSSERVICE_CFLAGS="$GIO_CFLAGS"
|
|
|
7f2946 |
AC_SUBST(LIBACCOUNTSSERVICE_CFLAGS)
|
|
|
7f2946 |
|
|
|
7f2946 |
GOBJECT_INTROSPECTION_CHECK([0.9.12])
|
|
|
7f2946 |
|
|
|
7f2946 |
dnl ---------------------------------------------------------------------------
|
|
|
7f2946 |
dnl - Core configuration
|
|
|
7f2946 |
dnl ---------------------------------------------------------------------------
|
|
|
7f2946 |
|
|
|
7f2946 |
AC_ARG_ENABLE(admin-group,
|
|
|
7f2946 |
[AS_HELP_STRING([--enable-admin-group],[Set group for administrative accounts @<:@default=auto@:>@])],
|
|
|
7f2946 |
,enable_admin_group=auto)
|
|
|
7f2946 |
AS_IF([test x$enable_admin_group = xauto], [
|
|
|
7f2946 |
AC_CHECK_FILE(/etc/redhat-release, enable_admin_group=wheel)
|
|
|
7f2946 |
AC_CHECK_FILE(/etc/debian_version, enable_admin_group=sudo)
|
|
|
7f2946 |
AS_IF([test x$enable_admin_group = xauto], [
|
|
|
7f2946 |
enable_admin_group=wheel
|
|
|
7f2946 |
])
|
|
|
7f2946 |
])
|
|
|
7f2946 |
AC_DEFINE_UNQUOTED([ADMIN_GROUP], ["$enable_admin_group"], [Define to the group for administrator users])
|
|
|
7f2946 |
|
|
|
7f2946 |
AC_ARG_ENABLE(user-heuristics,
|
|
|
7f2946 |
- [AS_HELP_STRING([--enable-user-heuristics],[Enable heuristics for guessing system vs. human users])],
|
|
|
7f2946 |
+ [AS_HELP_STRING([--enable-user-heuristics],[Enable heuristics for guessing system vs. human users in the range 500-minimum-uid])],
|
|
|
7f2946 |
[if test "$enableval" = yes; then
|
|
|
7f2946 |
AC_DEFINE([ENABLE_USER_HEURISTICS], , [System vs. human user heuristics enabled])
|
|
|
7f2946 |
fi])
|
|
|
7f2946 |
|
|
|
7f2946 |
+AC_ARG_WITH(minimum-uid,
|
|
|
7f2946 |
+ [AS_HELP_STRING([--with-minimum-uid],[Set minimum uid for human users])],
|
|
|
7f2946 |
+ ,with_minimum_uid=1000)
|
|
|
7f2946 |
+
|
|
|
7f2946 |
+AC_DEFINE_UNQUOTED([MINIMUM_UID], $with_minimum_uid, [Define to the minumum UID of human users])
|
|
|
7f2946 |
+
|
|
|
7f2946 |
dnl ---------------------------------------------------------------------------
|
|
|
7f2946 |
dnl - coverage
|
|
|
7f2946 |
dnl ---------------------------------------------------------------------------
|
|
|
7f2946 |
|
|
|
7f2946 |
AC_MSG_CHECKING([whether to build with gcov testing])
|
|
|
7f2946 |
AC_ARG_ENABLE([coverage],
|
|
|
7f2946 |
AS_HELP_STRING([--enable-coverage],
|
|
|
7f2946 |
[Whether to enable gcov code coverage]),
|
|
|
7f2946 |
[], [enable_coverage=no])
|
|
|
7f2946 |
AC_MSG_RESULT([$enable_coverage])
|
|
|
7f2946 |
|
|
|
7f2946 |
if test "$enable_coverage" = "yes"; then
|
|
|
7f2946 |
if test "$GCC" != "yes"; then
|
|
|
7f2946 |
AC_MSG_ERROR(Coverage testing requires GCC)
|
|
|
7f2946 |
fi
|
|
|
7f2946 |
CFLAGS="$CFLAGS -O0 -g --coverage"
|
|
|
7f2946 |
fi
|
|
|
7f2946 |
|
|
|
7f2946 |
AM_CONDITIONAL([WITH_COVERAGE], [test "$enable_coverage" = "yes"])
|
|
|
7f2946 |
|
|
|
7f2946 |
dnl ---------------------------------------------------------------------------
|
|
|
7f2946 |
dnl - Warnings
|
|
|
7f2946 |
dnl ---------------------------------------------------------------------------
|
|
|
7f2946 |
|
|
|
7f2946 |
AC_ARG_ENABLE(more-warnings,
|
|
|
7f2946 |
AS_HELP_STRING([--enable-more-warnings],
|
|
|
7f2946 |
[Maximum compiler warnings]),
|
|
|
7f2946 |
set_more_warnings="$enableval",[
|
|
|
7f2946 |
if test -d $srcdir/.git; then
|
|
|
7f2946 |
set_more_warnings=yes
|
|
|
7f2946 |
diff --git a/src/user-classify.c b/src/user-classify.c
|
|
|
7f2946 |
index b68c9ae..69e6809 100644
|
|
|
7f2946 |
--- a/src/user-classify.c
|
|
|
7f2946 |
+++ b/src/user-classify.c
|
|
|
7f2946 |
@@ -1,248 +1,169 @@
|
|
|
7f2946 |
/*
|
|
|
7f2946 |
* Copyright (C) 2009-2010 Red Hat, Inc.
|
|
|
7f2946 |
* Copyright (C) 2013 Canonical Limited
|
|
|
7f2946 |
*
|
|
|
7f2946 |
* This program is free software; you can redistribute it and/or modify
|
|
|
7f2946 |
* it under the terms of the GNU General Public License as published by
|
|
|
7f2946 |
* the Free Software Foundation; either version 3 of the licence, or
|
|
|
7f2946 |
* (at your option) any later version.
|
|
|
7f2946 |
*
|
|
|
7f2946 |
* This program is distributed in the hope that it will be useful,
|
|
|
7f2946 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
7f2946 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
7f2946 |
* GNU General Public License for more details.
|
|
|
7f2946 |
*
|
|
|
7f2946 |
* You should have received a copy of the GNU General Public License
|
|
|
7f2946 |
* along with this program; if not, write to the Free Software
|
|
|
7f2946 |
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
7f2946 |
*
|
|
|
7f2946 |
* Authors: Ryan Lortie <desrt@desrt.ca>
|
|
|
7f2946 |
* Matthias Clasen <mclasen@redhat.com>
|
|
|
7f2946 |
*/
|
|
|
7f2946 |
|
|
|
7f2946 |
#include "config.h"
|
|
|
7f2946 |
|
|
|
7f2946 |
#include "user-classify.h"
|
|
|
7f2946 |
|
|
|
7f2946 |
#include <string.h>
|
|
|
7f2946 |
|
|
|
7f2946 |
-#ifdef ENABLE_USER_HEURISTICS
|
|
|
7f2946 |
static const char *default_excludes[] = {
|
|
|
7f2946 |
"bin",
|
|
|
7f2946 |
"root",
|
|
|
7f2946 |
"daemon",
|
|
|
7f2946 |
"adm",
|
|
|
7f2946 |
"lp",
|
|
|
7f2946 |
"sync",
|
|
|
7f2946 |
"shutdown",
|
|
|
7f2946 |
"halt",
|
|
|
7f2946 |
"mail",
|
|
|
7f2946 |
"news",
|
|
|
7f2946 |
"uucp",
|
|
|
7f2946 |
"operator",
|
|
|
7f2946 |
"nobody",
|
|
|
7f2946 |
"nobody4",
|
|
|
7f2946 |
"noaccess",
|
|
|
7f2946 |
"postgres",
|
|
|
7f2946 |
"pvm",
|
|
|
7f2946 |
"rpm",
|
|
|
7f2946 |
"nfsnobody",
|
|
|
7f2946 |
"pcap",
|
|
|
7f2946 |
"mysql",
|
|
|
7f2946 |
"ftp",
|
|
|
7f2946 |
"games",
|
|
|
7f2946 |
"man",
|
|
|
7f2946 |
"at",
|
|
|
7f2946 |
"gdm",
|
|
|
7f2946 |
"gnome-initial-setup"
|
|
|
7f2946 |
};
|
|
|
7f2946 |
|
|
|
7f2946 |
-#define PATH_NOLOGIN "/sbin/nologin"
|
|
|
7f2946 |
-#define PATH_FALSE "/bin/false"
|
|
|
7f2946 |
-
|
|
|
7f2946 |
static gboolean
|
|
|
7f2946 |
-user_classify_is_excluded_by_heuristics (const gchar *username,
|
|
|
7f2946 |
- const gchar *shell,
|
|
|
7f2946 |
- const gchar *password_hash)
|
|
|
7f2946 |
+user_classify_is_blacklisted (const char *username)
|
|
|
7f2946 |
{
|
|
|
7f2946 |
static GHashTable *exclusions;
|
|
|
7f2946 |
- gboolean ret = FALSE;
|
|
|
7f2946 |
|
|
|
7f2946 |
if (exclusions == NULL) {
|
|
|
7f2946 |
guint i;
|
|
|
7f2946 |
|
|
|
7f2946 |
exclusions = g_hash_table_new (g_str_hash, g_str_equal);
|
|
|
7f2946 |
|
|
|
7f2946 |
for (i = 0; i < G_N_ELEMENTS (default_excludes); i++) {
|
|
|
7f2946 |
g_hash_table_add (exclusions, (gpointer) default_excludes[i]);
|
|
|
7f2946 |
}
|
|
|
7f2946 |
}
|
|
|
7f2946 |
|
|
|
7f2946 |
if (g_hash_table_contains (exclusions, username)) {
|
|
|
7f2946 |
return TRUE;
|
|
|
7f2946 |
}
|
|
|
7f2946 |
|
|
|
7f2946 |
+ return FALSE;
|
|
|
7f2946 |
+}
|
|
|
7f2946 |
+
|
|
|
7f2946 |
+#define PATH_NOLOGIN "/sbin/nologin"
|
|
|
7f2946 |
+#define PATH_FALSE "/bin/false"
|
|
|
7f2946 |
+
|
|
|
7f2946 |
+#ifdef ENABLE_USER_HEURISTICS
|
|
|
7f2946 |
+static gboolean
|
|
|
7f2946 |
+user_classify_is_excluded_by_heuristics (const gchar *username,
|
|
|
7f2946 |
+ const gchar *shell,
|
|
|
7f2946 |
+ const gchar *password_hash)
|
|
|
7f2946 |
+{
|
|
|
7f2946 |
+ gboolean ret = FALSE;
|
|
|
7f2946 |
+
|
|
|
7f2946 |
if (shell != NULL) {
|
|
|
7f2946 |
char *basename, *nologin_basename, *false_basename;
|
|
|
7f2946 |
|
|
|
7f2946 |
#ifdef HAVE_GETUSERSHELL
|
|
|
7f2946 |
char *valid_shell;
|
|
|
7f2946 |
|
|
|
7f2946 |
ret = TRUE;
|
|
|
7f2946 |
setusershell ();
|
|
|
7f2946 |
while ((valid_shell = getusershell ()) != NULL) {
|
|
|
7f2946 |
if (g_strcmp0 (shell, valid_shell) != 0)
|
|
|
7f2946 |
continue;
|
|
|
7f2946 |
ret = FALSE;
|
|
|
7f2946 |
}
|
|
|
7f2946 |
endusershell ();
|
|
|
7f2946 |
#endif
|
|
|
7f2946 |
|
|
|
7f2946 |
basename = g_path_get_basename (shell);
|
|
|
7f2946 |
nologin_basename = g_path_get_basename (PATH_NOLOGIN);
|
|
|
7f2946 |
false_basename = g_path_get_basename (PATH_FALSE);
|
|
|
7f2946 |
|
|
|
7f2946 |
if (shell[0] == '\0') {
|
|
|
7f2946 |
ret = TRUE;
|
|
|
7f2946 |
} else if (g_strcmp0 (basename, nologin_basename) == 0) {
|
|
|
7f2946 |
ret = TRUE;
|
|
|
7f2946 |
} else if (g_strcmp0 (basename, false_basename) == 0) {
|
|
|
7f2946 |
ret = TRUE;
|
|
|
7f2946 |
}
|
|
|
7f2946 |
|
|
|
7f2946 |
g_free (basename);
|
|
|
7f2946 |
g_free (nologin_basename);
|
|
|
7f2946 |
g_free (false_basename);
|
|
|
7f2946 |
}
|
|
|
7f2946 |
|
|
|
7f2946 |
if (password_hash != NULL) {
|
|
|
7f2946 |
/* skip over the account-is-locked '!' prefix if present */
|
|
|
7f2946 |
if (password_hash[0] == '!')
|
|
|
7f2946 |
password_hash++;
|
|
|
7f2946 |
|
|
|
7f2946 |
if (password_hash[0] != '\0') {
|
|
|
7f2946 |
/* modern hashes start with "$n$" */
|
|
|
7f2946 |
if (password_hash[0] == '$') {
|
|
|
7f2946 |
if (strlen (password_hash) < 4)
|
|
|
7f2946 |
ret = TRUE;
|
|
|
7f2946 |
|
|
|
7f2946 |
/* DES crypt is base64 encoded [./A-Za-z0-9]*
|
|
|
7f2946 |
*/
|
|
|
7f2946 |
} else if (!g_ascii_isalnum (password_hash[0]) &&
|
|
|
7f2946 |
password_hash[0] != '.' &&
|
|
|
7f2946 |
password_hash[0] != '/') {
|
|
|
7f2946 |
ret = TRUE;
|
|
|
7f2946 |
}
|
|
|
7f2946 |
}
|
|
|
7f2946 |
|
|
|
7f2946 |
}
|
|
|
7f2946 |
|
|
|
7f2946 |
return ret;
|
|
|
7f2946 |
}
|
|
|
7f2946 |
-
|
|
|
7f2946 |
-#else /* ENABLE_USER_HEURISTICS */
|
|
|
7f2946 |
-
|
|
|
7f2946 |
-static gboolean
|
|
|
7f2946 |
-user_classify_parse_login_defs_field (const gchar *contents,
|
|
|
7f2946 |
- const gchar *key,
|
|
|
7f2946 |
- uid_t *result)
|
|
|
7f2946 |
-{
|
|
|
7f2946 |
- gsize key_len;
|
|
|
7f2946 |
- gint64 value;
|
|
|
7f2946 |
- gchar *end;
|
|
|
7f2946 |
-
|
|
|
7f2946 |
- key_len = strlen (key);
|
|
|
7f2946 |
-
|
|
|
7f2946 |
- for (;;) {
|
|
|
7f2946 |
- /* Our key has to be at the start of the line, followed by whitespace */
|
|
|
7f2946 |
- if (strncmp (contents, key, key_len) == 0 && g_ascii_isspace (contents[key_len])) {
|
|
|
7f2946 |
- /* Found it. Move contents past the key itself and break out. */
|
|
|
7f2946 |
- contents += key_len;
|
|
|
7f2946 |
- break;
|
|
|
7f2946 |
- }
|
|
|
7f2946 |
-
|
|
|
7f2946 |
- /* Didn't find it. Find the end of the line. */
|
|
|
7f2946 |
- contents = strchr (contents, '\n');
|
|
|
7f2946 |
-
|
|
|
7f2946 |
- /* EOF? */
|
|
|
7f2946 |
- if (!contents) {
|
|
|
7f2946 |
- /* We didn't find the field... */
|
|
|
7f2946 |
- return FALSE;
|
|
|
7f2946 |
- }
|
|
|
7f2946 |
-
|
|
|
7f2946 |
- /* Start at the beginning of the next line on next iteration. */
|
|
|
7f2946 |
- contents++;
|
|
|
7f2946 |
- }
|
|
|
7f2946 |
-
|
|
|
7f2946 |
- /* 'contents' now points at the whitespace character just after
|
|
|
7f2946 |
- * the field name. strtoll can deal with that.
|
|
|
7f2946 |
- */
|
|
|
7f2946 |
- value = g_ascii_strtoll (contents, &end, 10);
|
|
|
7f2946 |
-
|
|
|
7f2946 |
- if (*end && !g_ascii_isspace (*end)) {
|
|
|
7f2946 |
- g_warning ("Trailing junk after '%s' field in login.defs", key);
|
|
|
7f2946 |
- return FALSE;
|
|
|
7f2946 |
- }
|
|
|
7f2946 |
-
|
|
|
7f2946 |
- if (value <= 0 || value >= G_MAXINT32) {
|
|
|
7f2946 |
- g_warning ("Value for '%s' field out of range", key);
|
|
|
7f2946 |
- return FALSE;
|
|
|
7f2946 |
- }
|
|
|
7f2946 |
-
|
|
|
7f2946 |
- *result = value;
|
|
|
7f2946 |
-
|
|
|
7f2946 |
- return TRUE;
|
|
|
7f2946 |
-}
|
|
|
7f2946 |
-
|
|
|
7f2946 |
-static void
|
|
|
7f2946 |
-user_classify_read_login_defs (uid_t *min_uid,
|
|
|
7f2946 |
- uid_t *max_uid)
|
|
|
7f2946 |
-{
|
|
|
7f2946 |
- GError *error = NULL;
|
|
|
7f2946 |
- char *contents;
|
|
|
7f2946 |
-
|
|
|
7f2946 |
- if (!g_file_get_contents ("/etc/login.defs", &contents, NULL, &error)) {
|
|
|
7f2946 |
- g_warning ("Could not open /etc/login.defs: %s. Falling back to default human uid range of %d to %d",
|
|
|
7f2946 |
- error->message, (int) *min_uid, (int) *max_uid);
|
|
|
7f2946 |
- g_error_free (error);
|
|
|
7f2946 |
- return;
|
|
|
7f2946 |
- }
|
|
|
7f2946 |
-
|
|
|
7f2946 |
- if (!user_classify_parse_login_defs_field (contents, "UID_MIN", min_uid)) {
|
|
|
7f2946 |
- g_warning ("Could not find UID_MIN value in login.defs. Using default of %d", (int) *min_uid);
|
|
|
7f2946 |
- }
|
|
|
7f2946 |
-
|
|
|
7f2946 |
- if (!user_classify_parse_login_defs_field (contents, "UID_MAX", max_uid)) {
|
|
|
7f2946 |
- g_warning ("Could not find UID_MIN value in login.defs. Using default of %d", (int) *max_uid);
|
|
|
7f2946 |
- }
|
|
|
7f2946 |
-
|
|
|
7f2946 |
- g_free (contents);
|
|
|
7f2946 |
-}
|
|
|
7f2946 |
-
|
|
|
7f2946 |
-static gboolean
|
|
|
7f2946 |
-user_classify_is_in_human_range (uid_t uid)
|
|
|
7f2946 |
-{
|
|
|
7f2946 |
- static uid_t min_uid = 1000, max_uid = 60000;
|
|
|
7f2946 |
- static gboolean initialised;
|
|
|
7f2946 |
-
|
|
|
7f2946 |
- if (!initialised) {
|
|
|
7f2946 |
- user_classify_read_login_defs (&min_uid, &max_uid);
|
|
|
7f2946 |
- initialised = TRUE;
|
|
|
7f2946 |
- }
|
|
|
7f2946 |
-
|
|
|
7f2946 |
- return min_uid <= uid && uid <= max_uid;
|
|
|
7f2946 |
-}
|
|
|
7f2946 |
#endif /* ENABLE_USER_HEURISTICS */
|
|
|
7f2946 |
|
|
|
7f2946 |
gboolean
|
|
|
7f2946 |
user_classify_is_human (uid_t uid,
|
|
|
7f2946 |
const gchar *username,
|
|
|
7f2946 |
const gchar *shell,
|
|
|
7f2946 |
const gchar *password_hash)
|
|
|
7f2946 |
{
|
|
|
7f2946 |
+ if (user_classify_is_blacklisted (username))
|
|
|
7f2946 |
+ return FALSE;
|
|
|
7f2946 |
+
|
|
|
7f2946 |
#ifdef ENABLE_USER_HEURISTICS
|
|
|
7f2946 |
- return !user_classify_is_excluded_by_heuristics (username, shell, password_hash);
|
|
|
7f2946 |
-#else
|
|
|
7f2946 |
- return user_classify_is_in_human_range (uid);
|
|
|
7f2946 |
+ /* only do heuristics on the range 500-1000 to catch one off migration problems in Fedora */
|
|
|
7f2946 |
+ if (uid >= 500 && uid < MINIMUM_UID) {
|
|
|
7f2946 |
+ if (!user_classify_is_excluded_by_heuristics (username, shell, password_hash))
|
|
|
7f2946 |
+ return TRUE;
|
|
|
7f2946 |
+ }
|
|
|
7f2946 |
#endif
|
|
|
7f2946 |
+
|
|
|
7f2946 |
+ return uid >= MINIMUM_UID;
|
|
|
7f2946 |
}
|
|
|
7f2946 |
--
|
|
|
7f2946 |
1.8.3.1
|
|
|
7f2946 |
|