pam: Add a `pam_malcontent.so` module to enforce time-limited sessions
This involves adding a build-time dependency on PAM. Signed-off-by: Philip Withnall <withnall@endlessm.com>
This commit is contained in:
parent
e16759e0f7
commit
bd7b17ffd4
|
@ -5,7 +5,7 @@ before_script:
|
||||||
libxml2-devel dbus-daemon
|
libxml2-devel dbus-daemon
|
||||||
glib2-devel dbus-devel gobject-introspection-devel
|
glib2-devel dbus-devel gobject-introspection-devel
|
||||||
gettext-devel polkit-devel polkit-gnome git
|
gettext-devel polkit-devel polkit-gnome git
|
||||||
lcov
|
lcov pam-devel
|
||||||
- export LANG=C.UTF-8
|
- export LANG=C.UTF-8
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
|
|
|
@ -19,12 +19,19 @@ po_dir = join_paths(meson.source_root(), 'po')
|
||||||
prefix = get_option('prefix')
|
prefix = get_option('prefix')
|
||||||
bindir = join_paths(prefix, get_option('bindir'))
|
bindir = join_paths(prefix, get_option('bindir'))
|
||||||
datadir = join_paths(prefix, get_option('datadir'))
|
datadir = join_paths(prefix, get_option('datadir'))
|
||||||
|
libdir = join_paths(prefix, get_option('libdir'))
|
||||||
libexecdir = join_paths(prefix, get_option('libexecdir'))
|
libexecdir = join_paths(prefix, get_option('libexecdir'))
|
||||||
|
|
||||||
# FIXME: This isn’t exposed in accountsservice.pc
|
# FIXME: This isn’t exposed in accountsservice.pc
|
||||||
# See https://gitlab.freedesktop.org/accountsservice/accountsservice/merge_requests/16
|
# See https://gitlab.freedesktop.org/accountsservice/accountsservice/merge_requests/16
|
||||||
accountsserviceinterfacesdir = join_paths(datadir, 'accountsservice', 'interfaces')
|
accountsserviceinterfacesdir = join_paths(datadir, 'accountsservice', 'interfaces')
|
||||||
|
|
||||||
|
# FIXME: pam.pc doesn’t exist
|
||||||
|
pamlibdir = get_option('pamlibdir')
|
||||||
|
if pamlibdir == ''
|
||||||
|
pamlibdir = join_paths(prefix, libdir.split('/')[-1], 'security')
|
||||||
|
endif
|
||||||
|
|
||||||
dbus = dependency('dbus-1')
|
dbus = dependency('dbus-1')
|
||||||
dbusinterfacesdir = dbus.get_pkgconfig_variable('interfaces_dir',
|
dbusinterfacesdir = dbus.get_pkgconfig_variable('interfaces_dir',
|
||||||
define_variable: ['datadir', datadir])
|
define_variable: ['datadir', datadir])
|
||||||
|
@ -121,3 +128,4 @@ test_env = [
|
||||||
subdir('accounts-service')
|
subdir('accounts-service')
|
||||||
subdir('malcontent-client')
|
subdir('malcontent-client')
|
||||||
subdir('libmalcontent')
|
subdir('libmalcontent')
|
||||||
|
subdir('pam')
|
||||||
|
|
|
@ -4,3 +4,8 @@ option(
|
||||||
value: false,
|
value: false,
|
||||||
description: 'enable installed tests'
|
description: 'enable installed tests'
|
||||||
)
|
)
|
||||||
|
option(
|
||||||
|
'pamlibdir',
|
||||||
|
type: 'string',
|
||||||
|
description: 'directory for PAM modules'
|
||||||
|
)
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
libpam = cc.find_library('pam', required: true)
|
||||||
|
libpam_misc = cc.find_library('pam_misc', required: true)
|
||||||
|
|
||||||
|
pam_malcontent = shared_library('pam_malcontent',
|
||||||
|
files('pam_malcontent.c'),
|
||||||
|
name_prefix: '',
|
||||||
|
link_args: [
|
||||||
|
'-shared',
|
||||||
|
'-Wl,--version-script=' + join_paths(meson.current_source_dir(), 'pam_malcontent.sym'),
|
||||||
|
],
|
||||||
|
dependencies: [
|
||||||
|
dependency('gio-2.0', version: '>= 2.44'),
|
||||||
|
dependency('glib-2.0', version: '>= 2.54.2'),
|
||||||
|
dependency('gobject-2.0', version: '>= 2.54'),
|
||||||
|
libmalcontent_dep,
|
||||||
|
libpam,
|
||||||
|
libpam_misc,
|
||||||
|
],
|
||||||
|
link_depends: files('pam_malcontent.sym'),
|
||||||
|
include_directories: root_inc,
|
||||||
|
install: true,
|
||||||
|
install_dir: pamlibdir)
|
|
@ -0,0 +1,206 @@
|
||||||
|
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
|
||||||
|
*
|
||||||
|
* Copyright © 2019 Endless Mobile, Inc.
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library 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
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
* Authors:
|
||||||
|
* - Philip Withnall <withnall@endlessm.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#define PAM_SM_ACCOUNT
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
#include <glib/gi18n-lib.h>
|
||||||
|
#include <libmalcontent/malcontent.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <security/pam_ext.h>
|
||||||
|
#include <security/pam_modules.h>
|
||||||
|
#include <security/pam_modutil.h>
|
||||||
|
#include <syslog.h>
|
||||||
|
|
||||||
|
|
||||||
|
/* Example usage:
|
||||||
|
*
|
||||||
|
* Here’s an example of a PAM file which uses `pam_malcontent.so`. Note
|
||||||
|
* that `pam_malcontent.so` must be listed before `pam_systemd.so`, and it must
|
||||||
|
* have type `account`.
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* auth sufficient pam_unix.so nullok try_first_pass
|
||||||
|
* auth required pam_deny.so
|
||||||
|
*
|
||||||
|
* account required pam_nologin.so
|
||||||
|
* account sufficient pam_unix.so
|
||||||
|
* account required pam_permit.so
|
||||||
|
* -account required pam_malcontent.so
|
||||||
|
*
|
||||||
|
* password sufficient pam_unix.so nullok sha512 shadow try_first_pass try_authtok
|
||||||
|
* password required pam_deny.so
|
||||||
|
*
|
||||||
|
* -session optional pam_keyinit.so revoke
|
||||||
|
* -session optional pam_loginuid.so
|
||||||
|
* -session optional pam_systemd.so
|
||||||
|
* session sufficient pam_unix.so
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @pw_out is (transfer none) (out) (not optional) */
|
||||||
|
static int
|
||||||
|
get_user_data (pam_handle_t *handle,
|
||||||
|
const char **username_out,
|
||||||
|
const struct passwd **pw_out)
|
||||||
|
{
|
||||||
|
const char *username = NULL;
|
||||||
|
struct passwd *pw = NULL;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
g_return_val_if_fail (handle != NULL, PAM_AUTH_ERR);
|
||||||
|
g_return_val_if_fail (username_out != NULL, PAM_AUTH_ERR);
|
||||||
|
g_return_val_if_fail (pw_out != NULL, PAM_AUTH_ERR);
|
||||||
|
|
||||||
|
r = pam_get_user (handle, &username, NULL);
|
||||||
|
if (r != PAM_SUCCESS)
|
||||||
|
{
|
||||||
|
pam_syslog (handle, LOG_ERR, "Failed to get user name.");
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (username == NULL || *username == '\0')
|
||||||
|
{
|
||||||
|
pam_syslog (handle, LOG_ERR, "User name not valid.");
|
||||||
|
return PAM_AUTH_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
pw = pam_modutil_getpwnam (handle, username);
|
||||||
|
if (pw == NULL)
|
||||||
|
{
|
||||||
|
pam_syslog (handle, LOG_ERR, "Failed to get user data.");
|
||||||
|
return PAM_USER_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
*pw_out = pw;
|
||||||
|
*username_out = username;
|
||||||
|
|
||||||
|
return PAM_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
runtime_max_sec_free (pam_handle_t *handle,
|
||||||
|
void *data,
|
||||||
|
int error_status)
|
||||||
|
{
|
||||||
|
g_return_if_fail (data != NULL);
|
||||||
|
|
||||||
|
g_free (data);
|
||||||
|
}
|
||||||
|
|
||||||
|
PAM_EXTERN int
|
||||||
|
pam_sm_acct_mgmt (pam_handle_t *handle,
|
||||||
|
int flags,
|
||||||
|
int argc,
|
||||||
|
const char **argv)
|
||||||
|
{
|
||||||
|
int retval;
|
||||||
|
const char *username = NULL;
|
||||||
|
const struct passwd *pw = NULL;
|
||||||
|
g_autoptr(GDBusConnection) connection = NULL;
|
||||||
|
g_autoptr(MctManager) manager = NULL;
|
||||||
|
g_autoptr(MctSessionLimits) limits = NULL;
|
||||||
|
g_autoptr(GError) local_error = NULL;
|
||||||
|
g_autofree gchar *runtime_max_sec_str = NULL;
|
||||||
|
guint64 now = g_get_real_time ();
|
||||||
|
guint64 time_remaining_secs = 0;
|
||||||
|
gboolean time_limit_enabled = FALSE;
|
||||||
|
|
||||||
|
/* Look up the user data from the handle. */
|
||||||
|
retval = get_user_data (handle, &username, &pw);
|
||||||
|
if (retval != PAM_SUCCESS)
|
||||||
|
{
|
||||||
|
/* The error has already been logged. */
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pw->pw_uid == 0)
|
||||||
|
{
|
||||||
|
/* Always allow root, to avoid a situation where this PAM module prevents
|
||||||
|
* all users logging in with no way of recovery. */
|
||||||
|
pam_info (handle, _("User ‘%s’ has no time limits enabled"), "root");
|
||||||
|
return PAM_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Connect to the system bus. */
|
||||||
|
connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &local_error);
|
||||||
|
if (connection == NULL)
|
||||||
|
{
|
||||||
|
pam_error (handle,
|
||||||
|
_("Error getting session limits for user ‘%s’: %s"),
|
||||||
|
username, local_error->message);
|
||||||
|
return PAM_SERVICE_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the time limits on this user’s session usage. */
|
||||||
|
manager = mct_manager_new (connection);
|
||||||
|
limits = mct_manager_get_session_limits (manager, pw->pw_uid,
|
||||||
|
MCT_MANAGER_GET_VALUE_FLAGS_NONE,
|
||||||
|
NULL, &local_error);
|
||||||
|
|
||||||
|
if (limits == NULL)
|
||||||
|
{
|
||||||
|
if (g_error_matches (local_error, MCT_MANAGER_ERROR,
|
||||||
|
MCT_MANAGER_ERROR_DISABLED))
|
||||||
|
{
|
||||||
|
return PAM_SUCCESS;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pam_error (handle,
|
||||||
|
_("Error getting session limits for user ‘%s’: %s"),
|
||||||
|
username, local_error->message);
|
||||||
|
return PAM_SERVICE_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if there’s time left. */
|
||||||
|
if (!mct_session_limits_check_time_remaining (limits, now, &time_remaining_secs,
|
||||||
|
&time_limit_enabled))
|
||||||
|
{
|
||||||
|
pam_error (handle, _("User ‘%s’ has no time remaining"), username);
|
||||||
|
return PAM_AUTH_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!time_limit_enabled)
|
||||||
|
{
|
||||||
|
pam_info (handle, _("User ‘%s’ has no time limits enabled"), username);
|
||||||
|
return PAM_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Propagate the remaining time to the `pam_systemd.so` module, which will
|
||||||
|
* end the user’s session when it runs out. */
|
||||||
|
runtime_max_sec_str = g_strdup_printf ("%" G_GUINT64_FORMAT, time_remaining_secs);
|
||||||
|
retval = pam_set_data (handle, "systemd.runtime_max_sec",
|
||||||
|
g_steal_pointer (&runtime_max_sec_str), runtime_max_sec_free);
|
||||||
|
|
||||||
|
if (retval != PAM_SUCCESS)
|
||||||
|
{
|
||||||
|
pam_error (handle, _("Error setting time limit on login session: %s"),
|
||||||
|
pam_strerror (handle, retval));
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PAM_SUCCESS;
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2019 Endless Mobile, Inc.
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library 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
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
* Authors:
|
||||||
|
* - Philip Withnall <withnall@endlessm.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
{
|
||||||
|
global:
|
||||||
|
pam_sm_acct_mgmt;
|
||||||
|
local: *;
|
||||||
|
};
|
|
@ -2,3 +2,4 @@
|
||||||
# Please keep this file sorted alphabetically.
|
# Please keep this file sorted alphabetically.
|
||||||
accounts-service/com.endlessm.ParentalControls.policy
|
accounts-service/com.endlessm.ParentalControls.policy
|
||||||
libmalcontent/manager.c
|
libmalcontent/manager.c
|
||||||
|
pam/pam_malcontent.c
|
||||||
|
|
Loading…
Reference in New Issue