Merge branch 'pam-module' into 'master'

Add session limits support and PAM module

See merge request pwithnall/malcontent!19
This commit is contained in:
Philip Withnall 2020-01-17 11:28:49 +00:00
commit 83d2fdb838
27 changed files with 3191 additions and 226 deletions

View File

@ -5,7 +5,7 @@ before_script:
libxml2-devel dbus-daemon
glib2-devel dbus-devel gobject-introspection-devel
gettext-devel polkit-devel polkit-gnome git
lcov
lcov pam-devel
- export LANG=C.UTF-8
stages:

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="com.endlessm.ParentalControls.SessionLimits">
<annotation name="org.freedesktop.Accounts.VendorExtension" value="true"/>
<annotation name="org.freedesktop.Accounts.Authentication.ChangeOwn"
value="com.endlessm.ParentalControls.SessionLimits.ChangeOwn"/>
<annotation name="org.freedesktop.Accounts.Authentication.ReadOwn"
value="com.endlessm.ParentalControls.SessionLimits.ReadOwn"/>
<annotation name="org.freedesktop.Accounts.Authentication.ChangeAny"
value="com.endlessm.ParentalControls.SessionLimits.ChangeAny"/>
<annotation name="org.freedesktop.Accounts.Authentication.ReadAny"
value="com.endlessm.ParentalControls.SessionLimits.ReadAny"/>
<!--
LimitType:
The type of session limit applied to the user, as an enumerated type.
Currently, the following values are supported, but more values may be
added in future.
- `0`: No filter enabled. The user is not limited in the times or
durations of their sessions.
- `1`: Daily schedule. The user is limited to using the computer between
a fixed start and end time each day, as set in the `DailySchedule`
property.
-->
<property name="LimitType" type="u" access="readwrite">
<annotation name="org.freedesktop.Accounts.DefaultValue" value="0"/>
</property>
<!--
DailySchedule:
A daily schedule to limit the users computer use. This is a two-tuple of
a start time and an end time, both given as the number of seconds since
midnight. The end time must be greater than the start time, and must be
≤ 86400 (the number of seconds in a day). There is no handling of leap
seconds.
This property will be used if `LimitType` is set to `1`, but it must be
set to a valid value regardless.
-->
<property name="DailySchedule" type="(uu)" access="readwrite">
<annotation name="org.freedesktop.Accounts.DefaultValue" value="(0, 86400)"/>
</property>
</interface>
</node>

View File

@ -39,4 +39,44 @@
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
</policyconfig>
<action id="com.endlessm.ParentalControls.SessionLimits.ChangeOwn">
<description>Change your own session limits</description>
<message>Authentication is required to change your session limits.</message>
<defaults>
<allow_any>auth_admin_keep</allow_any>
<allow_inactive>auth_admin_keep</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
<action id="com.endlessm.ParentalControls.SessionLimits.ReadOwn">
<description>Read your own session limits</description>
<message>Authentication is required to read your session limits.</message>
<defaults>
<allow_any>yes</allow_any>
<allow_inactive>yes</allow_inactive>
<allow_active>yes</allow_active>
</defaults>
</action>
<action id="com.endlessm.ParentalControls.SessionLimits.ChangeAny">
<description>Change another users session limits</description>
<message>Authentication is required to change another users session limits.</message>
<defaults>
<allow_any>auth_admin_keep</allow_any>
<allow_inactive>auth_admin_keep</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
<action id="com.endlessm.ParentalControls.SessionLimits.ReadAny">
<description>Read another users session limits</description>
<message>Authentication is required to read another users session limits.</message>
<defaults>
<allow_any>auth_admin_keep</allow_any>
<allow_inactive>auth_admin_keep</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
</policyconfig>

View File

@ -23,7 +23,9 @@ polkit.addRule(function(action, subject) {
/* Allow administrators to read parental controls (for any account) without
* needing an additional polkit authorisation dialogue. */
if ((action.id == "com.endlessm.ParentalControls.AppFilter.ReadOwn" ||
action.id == "com.endlessm.ParentalControls.AppFilter.ReadAny") &&
action.id == "com.endlessm.ParentalControls.AppFilter.ReadAny" ||
action.id == "com.endlessm.ParentalControls.SessionLimits.ReadOwn" ||
action.id == "com.endlessm.ParentalControls.SessionLimits.ReadAny") &&
subject.active && subject.local &&
subject.isInGroup("sudo")) {
return polkit.Result.YES;

View File

@ -6,11 +6,19 @@ i18n.merge_file('com.endlessm.ParentalControls.policy',
install_dir: polkitpolicydir,
)
install_data('com.endlessm.ParentalControls.AppFilter.xml',
install_dir: dbusinterfacesdir)
meson.add_install_script(meson_make_symlink,
join_paths(dbusinterfacesdir, 'com.endlessm.ParentalControls.AppFilter.xml'),
join_paths(accountsserviceinterfacesdir, 'com.endlessm.ParentalControls.AppFilter.xml'))
dbus_interfaces = [
'com.endlessm.ParentalControls.AppFilter',
'com.endlessm.ParentalControls.SessionLimits',
]
foreach dbus_interface: dbus_interfaces
filename = dbus_interface + '.xml'
install_data(filename,
install_dir: dbusinterfacesdir)
meson.add_install_script(meson_make_symlink,
join_paths(dbusinterfacesdir, filename),
join_paths(accountsserviceinterfacesdir, filename))
endforeach
install_data('com.endlessm.ParentalControls.rules',
install_dir: join_paths(get_option('datadir'), 'polkit-1', 'rules.d'))

View File

@ -33,7 +33,12 @@
#include "libmalcontent/app-filter-private.h"
G_DEFINE_QUARK (MctAppFilterError, mct_app_filter_error)
/* FIXME: Eventually deprecate these compatibility fallbacks. */
GQuark
mct_app_filter_error_quark (void)
{
return mct_manager_error_quark ();
}
/* struct _MctAppFilter is defined in app-filter-private.h */

View File

@ -29,31 +29,6 @@
G_BEGIN_DECLS
/**
* MctAppFilterError:
* @MCT_APP_FILTER_ERROR_INVALID_USER: Given user ID doesnt exist
* @MCT_APP_FILTER_ERROR_PERMISSION_DENIED: Not authorized to query the app
* filter for the given user
* @MCT_APP_FILTER_ERROR_INVALID_DATA: The data stored in the app filter for
* a user is inconsistent or invalid
* @MCT_APP_FILTER_ERROR_DISABLED: App filtering is disabled for all users (Since: 0.3.0)
*
* Errors relating to #MctAppFilter instances, which can be returned by
* mct_manager_get_app_filter_async() (for example).
*
* Since: 0.2.0
*/
typedef enum
{
MCT_APP_FILTER_ERROR_INVALID_USER,
MCT_APP_FILTER_ERROR_PERMISSION_DENIED,
MCT_APP_FILTER_ERROR_INVALID_DATA,
MCT_APP_FILTER_ERROR_DISABLED,
} MctAppFilterError;
GQuark mct_app_filter_error_quark (void);
#define MCT_APP_FILTER_ERROR mct_app_filter_error_quark ()
/**
* MctAppFilterOarsValue:
* @MCT_APP_FILTER_OARS_VALUE_UNKNOWN: Unknown value for the given
@ -197,4 +172,16 @@ void mct_app_filter_builder_set_allow_user_installation (MctAppFilterBuilder *
void mct_app_filter_builder_set_allow_system_installation (MctAppFilterBuilder *builder,
gboolean allow_system_installation);
#include <libmalcontent/manager.h>
/* FIXME: Eventually deprecate these compatibility fallbacks. */
typedef MctManagerError MctAppFilterError;
#define MCT_APP_FILTER_ERROR_INVALID_USER MCT_MANAGER_ERROR_INVALID_USER
#define MCT_APP_FILTER_ERROR_PERMISSION_DENIED MCT_MANAGER_ERROR_PERMISSION_DENIED
#define MCT_APP_FILTER_ERROR_INVALID_DATA MCT_MANAGER_ERROR_INVALID_DATA
#define MCT_APP_FILTER_ERROR_DISABLED MCT_MANAGER_ERROR_DISABLED
GQuark mct_app_filter_error_quark (void);
#define MCT_APP_FILTER_ERROR mct_app_filter_error_quark ()
G_END_DECLS

View File

@ -24,3 +24,4 @@
#include <libmalcontent/app-filter.h>
#include <libmalcontent/manager.h>
#include <libmalcontent/session-limits.h>

View File

@ -28,8 +28,13 @@
#include <gio/gio.h>
#include <libmalcontent/app-filter.h>
#include <libmalcontent/manager.h>
#include <libmalcontent/session-limits.h>
#include "libmalcontent/app-filter-private.h"
#include "libmalcontent/session-limits-private.h"
G_DEFINE_QUARK (MctManagerError, mct_manager_error)
/**
* MctManager:
@ -298,19 +303,19 @@ bus_remote_error_matches (const GError *error,
return g_str_equal (error_name, expected_error_name);
}
/* Convert a #GDBusError into a #MctAppFilter error. */
/* Convert a #GDBusError into a #MctManagerError. */
static GError *
bus_error_to_app_filter_error (const GError *bus_error,
uid_t user_id)
bus_error_to_manager_error (const GError *bus_error,
uid_t user_id)
{
if (g_error_matches (bus_error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED) ||
bus_remote_error_matches (bus_error, "org.freedesktop.Accounts.Error.PermissionDenied"))
return g_error_new (MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_PERMISSION_DENIED,
return g_error_new (MCT_MANAGER_ERROR, MCT_MANAGER_ERROR_PERMISSION_DENIED,
_("Not allowed to query app filter data for user %u"),
(guint) user_id);
else if (g_error_matches (bus_error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD) ||
bus_remote_error_matches (bus_error, "org.freedesktop.Accounts.Error.Failed"))
return g_error_new (MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_INVALID_USER,
return g_error_new (MCT_MANAGER_ERROR, MCT_MANAGER_ERROR_INVALID_USER,
_("User %u does not exist"), (guint) user_id);
else
return g_error_copy (bus_error);
@ -346,8 +351,8 @@ accounts_find_user_by_id (GDBusConnection *connection,
&local_error);
if (local_error != NULL)
{
g_autoptr(GError) app_filter_error = bus_error_to_app_filter_error (local_error,
user_id);
g_autoptr(GError) app_filter_error = bus_error_to_manager_error (local_error,
user_id);
g_propagate_error (error, g_steal_pointer (&app_filter_error));
return NULL;
}
@ -373,7 +378,7 @@ accounts_find_user_by_id (GDBusConnection *connection,
MctAppFilter *
mct_manager_get_app_filter (MctManager *self,
uid_t user_id,
MctGetAppFilterFlags flags,
MctManagerGetValueFlags flags,
GCancellable *cancellable,
GError **error)
{
@ -394,7 +399,7 @@ mct_manager_get_app_filter (MctManager *self,
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
object_path = accounts_find_user_by_id (self->connection, user_id,
(flags & MCT_GET_APP_FILTER_FLAGS_INTERACTIVE),
(flags & MCT_MANAGER_GET_VALUE_FLAGS_INTERACTIVE),
cancellable, error);
if (object_path == NULL)
return NULL;
@ -407,7 +412,7 @@ mct_manager_get_app_filter (MctManager *self,
"GetAll",
g_variant_new ("(s)", "com.endlessm.ParentalControls.AppFilter"),
G_VARIANT_TYPE ("(a{sv})"),
(flags & MCT_GET_APP_FILTER_FLAGS_INTERACTIVE)
(flags & MCT_MANAGER_GET_VALUE_FLAGS_INTERACTIVE)
? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION
: G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout, ms */
@ -415,24 +420,23 @@ mct_manager_get_app_filter (MctManager *self,
&local_error);
if (local_error != NULL)
{
g_autoptr(GError) app_filter_error = NULL;
g_autoptr(GError) manager_error = NULL;
if (g_error_matches (local_error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS))
{
/* o.fd.D.GetAll() will return InvalidArgs errors if
* accountsservice doesnt have the com.endlessm.ParentalControls.AppFilter
* extension interface installed. */
app_filter_error = g_error_new_literal (MCT_APP_FILTER_ERROR,
MCT_APP_FILTER_ERROR_DISABLED,
_("App filtering is globally disabled"));
manager_error = g_error_new_literal (MCT_MANAGER_ERROR,
MCT_MANAGER_ERROR_DISABLED,
_("App filtering is globally disabled"));
}
else
{
app_filter_error = bus_error_to_app_filter_error (local_error,
user_id);
manager_error = bus_error_to_manager_error (local_error, user_id);
}
g_propagate_error (error, g_steal_pointer (&app_filter_error));
g_propagate_error (error, g_steal_pointer (&manager_error));
return NULL;
}
@ -442,8 +446,8 @@ mct_manager_get_app_filter (MctManager *self,
if (!g_variant_lookup (properties, "AppFilter", "(b^as)",
&is_whitelist, &app_list))
{
g_set_error (error, MCT_APP_FILTER_ERROR,
MCT_APP_FILTER_ERROR_PERMISSION_DENIED,
g_set_error (error, MCT_MANAGER_ERROR,
MCT_MANAGER_ERROR_PERMISSION_DENIED,
_("Not allowed to query app filter data for user %u"),
(guint) user_id);
return NULL;
@ -462,8 +466,8 @@ mct_manager_get_app_filter (MctManager *self,
if (!g_str_equal (content_rating_kind, "oars-1.0") &&
!g_str_equal (content_rating_kind, "oars-1.1"))
{
g_set_error (error, MCT_APP_FILTER_ERROR,
MCT_APP_FILTER_ERROR_INVALID_DATA,
g_set_error (error, MCT_MANAGER_ERROR,
MCT_MANAGER_ERROR_INVALID_DATA,
_("OARS filter for user %u has an unrecognized kind %s"),
(guint) user_id, content_rating_kind);
return NULL;
@ -505,7 +509,7 @@ static void get_app_filter_thread_cb (GTask *task,
typedef struct
{
uid_t user_id;
MctGetAppFilterFlags flags;
MctManagerGetValueFlags flags;
} GetAppFilterData;
static void
@ -528,7 +532,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (GetAppFilterData, get_app_filter_data_free)
* Asynchronously get a snapshot of the app filter settings for the given
* @user_id.
*
* On failure, an #MctAppFilterError, a #GDBusError or a #GIOError will be
* On failure, an #MctManagerError, a #GDBusError or a #GIOError will be
* returned.
*
* Since: 0.3.0
@ -536,7 +540,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (GetAppFilterData, get_app_filter_data_free)
void
mct_manager_get_app_filter_async (MctManager *self,
uid_t user_id,
MctGetAppFilterFlags flags,
MctManagerGetValueFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
@ -623,7 +627,7 @@ gboolean
mct_manager_set_app_filter (MctManager *self,
uid_t user_id,
MctAppFilter *app_filter,
MctSetAppFilterFlags flags,
MctManagerSetValueFlags flags,
GCancellable *cancellable,
GError **error)
{
@ -637,7 +641,6 @@ mct_manager_set_app_filter (MctManager *self,
g_autoptr(GVariant) allow_user_installation_result_variant = NULL;
g_autoptr(GVariant) allow_system_installation_result_variant = NULL;
g_autoptr(GError) local_error = NULL;
g_autoptr(GDBusConnection) connection = NULL;
g_return_val_if_fail (MCT_IS_MANAGER (self), FALSE);
g_return_val_if_fail (app_filter != NULL, FALSE);
@ -646,7 +649,7 @@ mct_manager_set_app_filter (MctManager *self,
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
object_path = accounts_find_user_by_id (self->connection, user_id,
(flags & MCT_SET_APP_FILTER_FLAGS_INTERACTIVE),
(flags & MCT_MANAGER_SET_VALUE_FLAGS_INTERACTIVE),
cancellable, error);
if (object_path == NULL)
return FALSE;
@ -668,7 +671,7 @@ mct_manager_set_app_filter (MctManager *self,
"AppFilter",
g_steal_pointer (&app_filter_variant)),
G_VARIANT_TYPE ("()"),
(flags & MCT_SET_APP_FILTER_FLAGS_INTERACTIVE)
(flags & MCT_MANAGER_SET_VALUE_FLAGS_INTERACTIVE)
? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION
: G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout, ms */
@ -676,7 +679,7 @@ mct_manager_set_app_filter (MctManager *self,
&local_error);
if (local_error != NULL)
{
g_propagate_error (error, bus_error_to_app_filter_error (local_error, user_id));
g_propagate_error (error, bus_error_to_manager_error (local_error, user_id));
return FALSE;
}
@ -691,7 +694,7 @@ mct_manager_set_app_filter (MctManager *self,
"OarsFilter",
g_steal_pointer (&oars_filter_variant)),
G_VARIANT_TYPE ("()"),
(flags & MCT_SET_APP_FILTER_FLAGS_INTERACTIVE)
(flags & MCT_MANAGER_SET_VALUE_FLAGS_INTERACTIVE)
? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION
: G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout, ms */
@ -699,7 +702,7 @@ mct_manager_set_app_filter (MctManager *self,
&local_error);
if (local_error != NULL)
{
g_propagate_error (error, bus_error_to_app_filter_error (local_error, user_id));
g_propagate_error (error, bus_error_to_manager_error (local_error, user_id));
return FALSE;
}
@ -714,7 +717,7 @@ mct_manager_set_app_filter (MctManager *self,
"AllowUserInstallation",
g_steal_pointer (&allow_user_installation_variant)),
G_VARIANT_TYPE ("()"),
(flags & MCT_SET_APP_FILTER_FLAGS_INTERACTIVE)
(flags & MCT_MANAGER_SET_VALUE_FLAGS_INTERACTIVE)
? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION
: G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout, ms */
@ -722,7 +725,7 @@ mct_manager_set_app_filter (MctManager *self,
&local_error);
if (local_error != NULL)
{
g_propagate_error (error, bus_error_to_app_filter_error (local_error, user_id));
g_propagate_error (error, bus_error_to_manager_error (local_error, user_id));
return FALSE;
}
@ -737,7 +740,7 @@ mct_manager_set_app_filter (MctManager *self,
"AllowSystemInstallation",
g_steal_pointer (&allow_system_installation_variant)),
G_VARIANT_TYPE ("()"),
(flags & MCT_SET_APP_FILTER_FLAGS_INTERACTIVE)
(flags & MCT_MANAGER_SET_VALUE_FLAGS_INTERACTIVE)
? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION
: G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout, ms */
@ -745,7 +748,7 @@ mct_manager_set_app_filter (MctManager *self,
&local_error);
if (local_error != NULL)
{
g_propagate_error (error, bus_error_to_app_filter_error (local_error, user_id));
g_propagate_error (error, bus_error_to_manager_error (local_error, user_id));
return FALSE;
}
@ -761,7 +764,7 @@ typedef struct
{
uid_t user_id;
MctAppFilter *app_filter; /* (owned) */
MctSetAppFilterFlags flags;
MctManagerSetValueFlags flags;
} SetAppFilterData;
static void
@ -786,7 +789,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (SetAppFilterData, set_app_filter_data_free)
* Asynchronously set the app filter settings for the given @user_id to the
* given @app_filter instance. This will set all fields of the app filter.
*
* On failure, an #MctAppFilterError, a #GDBusError or a #GIOError will be
* On failure, an #MctManagerError, a #GDBusError or a #GIOError will be
* returned. The users app filter settings will be left in an undefined state.
*
* Since: 0.3.0
@ -795,7 +798,7 @@ void
mct_manager_set_app_filter_async (MctManager *self,
uid_t user_id,
MctAppFilter *app_filter,
MctSetAppFilterFlags flags,
MctManagerSetValueFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
@ -864,4 +867,469 @@ mct_manager_set_app_filter_finish (MctManager *self,
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
}
/**
* mct_manager_get_session_limits:
* @self: a #MctManager
* @user_id: ID of the user to query, typically coming from getuid()
* @flags: flags to affect the behaviour of the call
* @cancellable: (nullable): a #GCancellable, or %NULL
* @error: return location for a #GError, or %NULL
*
* Synchronous version of mct_manager_get_session_limits_async().
*
* Returns: (transfer full): session limits for the queried user
* Since: 0.5.0
*/
MctSessionLimits *
mct_manager_get_session_limits (MctManager *self,
uid_t user_id,
MctManagerGetValueFlags flags,
GCancellable *cancellable,
GError **error)
{
g_autofree gchar *object_path = NULL;
g_autoptr(GVariant) result_variant = NULL;
g_autoptr(GVariant) properties = NULL;
g_autoptr(GError) local_error = NULL;
g_autoptr(MctSessionLimits) session_limits = NULL;
guint32 limit_type;
guint32 daily_start_time, daily_end_time;
g_return_val_if_fail (MCT_IS_MANAGER (self), NULL);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
object_path = accounts_find_user_by_id (self->connection, user_id,
(flags & MCT_MANAGER_GET_VALUE_FLAGS_INTERACTIVE),
cancellable, error);
if (object_path == NULL)
return NULL;
result_variant =
g_dbus_connection_call_sync (self->connection,
"org.freedesktop.Accounts",
object_path,
"org.freedesktop.DBus.Properties",
"GetAll",
g_variant_new ("(s)", "com.endlessm.ParentalControls.SessionLimits"),
G_VARIANT_TYPE ("(a{sv})"),
(flags & MCT_MANAGER_GET_VALUE_FLAGS_INTERACTIVE)
? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION
: G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout, ms */
cancellable,
&local_error);
if (local_error != NULL)
{
g_autoptr(GError) manager_error = NULL;
if (g_error_matches (local_error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS))
{
/* o.fd.D.GetAll() will return InvalidArgs errors if
* accountsservice doesnt have the com.endlessm.ParentalControls.SessionLimits
* extension interface installed. */
manager_error = g_error_new_literal (MCT_MANAGER_ERROR,
MCT_MANAGER_ERROR_DISABLED,
_("Session limits are globally disabled"));
}
else
{
manager_error = bus_error_to_manager_error (local_error, user_id);
}
g_propagate_error (error, g_steal_pointer (&manager_error));
return NULL;
}
/* Extract the properties we care about. They may be silently omitted from the
* results if we dont have permission to access them. */
properties = g_variant_get_child_value (result_variant, 0);
if (!g_variant_lookup (properties, "LimitType", "u",
&limit_type))
{
g_set_error (error, MCT_MANAGER_ERROR,
MCT_MANAGER_ERROR_PERMISSION_DENIED,
_("Not allowed to query session limits data for user %u"),
(guint) user_id);
return NULL;
}
/* Check that the limit type is something we support. */
G_STATIC_ASSERT (sizeof (limit_type) >= sizeof (MctSessionLimitsType));
if ((guint) limit_type > MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE)
{
g_set_error (error, MCT_MANAGER_ERROR,
MCT_MANAGER_ERROR_INVALID_DATA,
_("Session limit for user %u has an unrecognized type %u"),
(guint) user_id, limit_type);
return NULL;
}
if (!g_variant_lookup (properties, "DailySchedule", "(uu)",
&daily_start_time, &daily_end_time))
{
/* Default value. */
daily_start_time = 0;
daily_end_time = 24 * 60 * 60;
}
if (daily_start_time >= daily_end_time ||
daily_end_time > 24 * 60 * 60)
{
g_set_error (error, MCT_MANAGER_ERROR,
MCT_MANAGER_ERROR_INVALID_DATA,
_("Session limit for user %u has invalid daily schedule %u%u"),
(guint) user_id, daily_start_time, daily_end_time);
return NULL;
}
/* Success. Create an #MctSessionLimits object to contain the results. */
session_limits = g_new0 (MctSessionLimits, 1);
session_limits->ref_count = 1;
session_limits->user_id = user_id;
session_limits->limit_type = limit_type;
session_limits->daily_start_time = daily_start_time;
session_limits->daily_end_time = daily_end_time;
return g_steal_pointer (&session_limits);
}
static void get_session_limits_thread_cb (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable);
typedef struct
{
uid_t user_id;
MctManagerGetValueFlags flags;
} GetSessionLimitsData;
static void
get_session_limits_data_free (GetSessionLimitsData *data)
{
g_free (data);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GetSessionLimitsData, get_session_limits_data_free)
/**
* mct_manager_get_session_limits_async:
* @self: a #MctManager
* @user_id: ID of the user to query, typically coming from getuid()
* @flags: flags to affect the behaviour of the call
* @cancellable: (nullable): a #GCancellable, or %NULL
* @callback: a #GAsyncReadyCallback
* @user_data: user data to pass to @callback
*
* Asynchronously get a snapshot of the session limit settings for the given
* @user_id.
*
* On failure, an #MctManagerError, a #GDBusError or a #GIOError will be
* returned via mct_manager_get_session_limits_finish().
*
* Since: 0.5.0
*/
void
mct_manager_get_session_limits_async (MctManager *self,
uid_t user_id,
MctManagerGetValueFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(GTask) task = NULL;
g_autoptr(GetSessionLimitsData) data = NULL;
g_return_if_fail (MCT_IS_MANAGER (self));
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_source_tag (task, mct_manager_get_session_limits_async);
data = g_new0 (GetSessionLimitsData, 1);
data->user_id = user_id;
data->flags = flags;
g_task_set_task_data (task, g_steal_pointer (&data),
(GDestroyNotify) get_session_limits_data_free);
g_task_run_in_thread (task, get_session_limits_thread_cb);
}
static void
get_session_limits_thread_cb (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
g_autoptr(MctSessionLimits) limits = NULL;
MctManager *manager = MCT_MANAGER (source_object);
GetSessionLimitsData *data = task_data;
g_autoptr(GError) local_error = NULL;
limits = mct_manager_get_session_limits (manager, data->user_id,
data->flags,
cancellable, &local_error);
if (local_error != NULL)
g_task_return_error (task, g_steal_pointer (&local_error));
else
g_task_return_pointer (task, g_steal_pointer (&limits),
(GDestroyNotify) mct_session_limits_unref);
}
/**
* mct_manager_get_session_limits_finish:
* @self: a #MctManager
* @result: a #GAsyncResult
* @error: return location for a #GError, or %NULL
*
* Finish an asynchronous operation to get the session limits for a user,
* started with mct_manager_get_session_limits_async().
*
* Returns: (transfer full): session limits for the queried user
* Since: 0.5.0
*/
MctSessionLimits *
mct_manager_get_session_limits_finish (MctManager *self,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (MCT_IS_MANAGER (self), NULL);
g_return_val_if_fail (g_task_is_valid (result, self), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
return g_task_propagate_pointer (G_TASK (result), error);
}
/**
* mct_manager_set_session_limits:
* @self: a #MctManager
* @user_id: ID of the user to set the limits for, typically coming from getuid()
* @session_limits: (transfer none): the session limits to set for the user
* @flags: flags to affect the behaviour of the call
* @cancellable: (nullable): a #GCancellable, or %NULL
* @error: return location for a #GError, or %NULL
*
* Synchronous version of mct_manager_set_session_limits_async().
*
* Returns: %TRUE on success, %FALSE otherwise
* Since: 0.5.0
*/
gboolean
mct_manager_set_session_limits (MctManager *self,
uid_t user_id,
MctSessionLimits *session_limits,
MctManagerSetValueFlags flags,
GCancellable *cancellable,
GError **error)
{
g_autofree gchar *object_path = NULL;
g_autoptr(GVariant) limit_variant = NULL;
const gchar *limit_property_name = NULL;
g_autoptr(GVariant) limit_type_variant = NULL;
g_autoptr(GVariant) limit_result_variant = NULL;
g_autoptr(GVariant) limit_type_result_variant = NULL;
g_autoptr(GError) local_error = NULL;
g_return_val_if_fail (MCT_IS_MANAGER (self), FALSE);
g_return_val_if_fail (session_limits != NULL, FALSE);
g_return_val_if_fail (session_limits->ref_count >= 1, FALSE);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
object_path = accounts_find_user_by_id (self->connection, user_id,
(flags & MCT_MANAGER_SET_VALUE_FLAGS_INTERACTIVE),
cancellable, error);
if (object_path == NULL)
return FALSE;
switch (session_limits->limit_type)
{
case MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE:
limit_variant = g_variant_new ("(uu)",
session_limits->daily_start_time,
session_limits->daily_end_time);
limit_property_name = "DailySchedule";
break;
case MCT_SESSION_LIMITS_TYPE_NONE:
limit_variant = NULL;
limit_property_name = NULL;
break;
default:
g_assert_not_reached ();
}
limit_type_variant = g_variant_new_uint32 (session_limits->limit_type);
if (limit_property_name != NULL)
{
/* Change the details of the new limit first, so that all the properties are
* correct by the time the limit type is changed over. */
limit_result_variant =
g_dbus_connection_call_sync (self->connection,
"org.freedesktop.Accounts",
object_path,
"org.freedesktop.DBus.Properties",
"Set",
g_variant_new ("(ssv)",
"com.endlessm.ParentalControls.SessionLimits",
limit_property_name,
g_steal_pointer (&limit_variant)),
G_VARIANT_TYPE ("()"),
(flags & MCT_MANAGER_SET_VALUE_FLAGS_INTERACTIVE)
? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION
: G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout, ms */
cancellable,
&local_error);
if (local_error != NULL)
{
g_propagate_error (error, bus_error_to_manager_error (local_error, user_id));
return FALSE;
}
}
limit_type_result_variant =
g_dbus_connection_call_sync (self->connection,
"org.freedesktop.Accounts",
object_path,
"org.freedesktop.DBus.Properties",
"Set",
g_variant_new ("(ssv)",
"com.endlessm.ParentalControls.SessionLimits",
"LimitType",
g_steal_pointer (&limit_type_variant)),
G_VARIANT_TYPE ("()"),
(flags & MCT_MANAGER_SET_VALUE_FLAGS_INTERACTIVE)
? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION
: G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout, ms */
cancellable,
&local_error);
if (local_error != NULL)
{
g_propagate_error (error, bus_error_to_manager_error (local_error, user_id));
return FALSE;
}
return TRUE;
}
static void set_session_limits_thread_cb (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable);
typedef struct
{
uid_t user_id;
MctSessionLimits *session_limits; /* (owned) */
MctManagerSetValueFlags flags;
} SetSessionLimitsData;
static void
set_session_limits_data_free (SetSessionLimitsData *data)
{
mct_session_limits_unref (data->session_limits);
g_free (data);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (SetSessionLimitsData, set_session_limits_data_free)
/**
* mct_manager_set_session_limits_async:
* @self: a #MctManager
* @user_id: ID of the user to set the limits for, typically coming from getuid()
* @session_limits: (transfer none): the session limits to set for the user
* @flags: flags to affect the behaviour of the call
* @cancellable: (nullable): a #GCancellable, or %NULL
* @callback: a #GAsyncReadyCallback
* @user_data: user data to pass to @callback
*
* Asynchronously set the session limits settings for the given @user_id to the
* given @session_limits instance.
*
* On failure, an #MctManagerError, a #GDBusError or a #GIOError will be
* returned via mct_manager_set_session_limits_finish(). The users session
* limits settings will be left in an undefined state.
*
* Since: 0.5.0
*/
void
mct_manager_set_session_limits_async (MctManager *self,
uid_t user_id,
MctSessionLimits *session_limits,
MctManagerSetValueFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(GTask) task = NULL;
g_autoptr(SetSessionLimitsData) data = NULL;
g_return_if_fail (MCT_IS_MANAGER (self));
g_return_if_fail (session_limits != NULL);
g_return_if_fail (session_limits->ref_count >= 1);
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_source_tag (task, mct_manager_set_session_limits_async);
data = g_new0 (SetSessionLimitsData, 1);
data->user_id = user_id;
data->session_limits = mct_session_limits_ref (session_limits);
data->flags = flags;
g_task_set_task_data (task, g_steal_pointer (&data),
(GDestroyNotify) set_session_limits_data_free);
g_task_run_in_thread (task, set_session_limits_thread_cb);
}
static void
set_session_limits_thread_cb (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
gboolean success;
MctManager *manager = MCT_MANAGER (source_object);
SetSessionLimitsData *data = task_data;
g_autoptr(GError) local_error = NULL;
success = mct_manager_set_session_limits (manager, data->user_id,
data->session_limits, data->flags,
cancellable, &local_error);
if (local_error != NULL)
g_task_return_error (task, g_steal_pointer (&local_error));
else
g_task_return_boolean (task, success);
}
/**
* mct_manager_set_session_limits_finish:
* @self: a #MctManager
* @result: a #GAsyncResult
* @error: return location for a #GError, or %NULL
*
* Finish an asynchronous operation to set the session limits for a user,
* started with mct_manager_set_session_limits_async().
*
* Returns: %TRUE on success, %FALSE otherwise
* Since: 0.5.0
*/
gboolean
mct_manager_set_session_limits_finish (MctManager *self,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (MCT_IS_MANAGER (self), FALSE);
g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}

View File

@ -25,43 +25,79 @@
#include <gio/gio.h>
#include <glib.h>
#include <glib-object.h>
#include <libmalcontent/app-filter.h>
G_BEGIN_DECLS
/**
* MctGetAppFilterFlags:
* @MCT_GET_APP_FILTER_FLAGS_NONE: No flags set.
* @MCT_GET_APP_FILTER_FLAGS_INTERACTIVE: Allow interactive polkit dialogs when
* requesting authorization.
* MctManagerGetValueFlags:
* @MCT_MANAGER_GET_VALUE_FLAGS_NONE: No flags set.
* @MCT_MANAGER_GET_VALUE_FLAGS_INTERACTIVE: Allow interactive polkit dialogs
* when requesting authorization.
*
* Flags to control the behaviour of mct_manager_get_app_filter() and
* mct_manager_get_app_filter_async().
* Flags to control the behaviour of getter functions like
* mct_manager_get_app_filter() and mct_manager_get_app_filter_async().
*
* Since: 0.3.0
* Since: 0.5.0
*/
typedef enum
{
MCT_GET_APP_FILTER_FLAGS_NONE = 0,
MCT_GET_APP_FILTER_FLAGS_INTERACTIVE,
} MctGetAppFilterFlags;
MCT_MANAGER_GET_VALUE_FLAGS_NONE = 0,
MCT_MANAGER_GET_VALUE_FLAGS_INTERACTIVE = (1 << 0),
} MctManagerGetValueFlags;
/* FIXME: Eventually deprecate these compatibility fallbacks. */
typedef MctManagerGetValueFlags MctGetAppFilterFlags;
#define MCT_GET_APP_FILTER_FLAGS_NONE MCT_MANAGER_GET_VALUE_FLAGS_NONE
#define MCT_GET_APP_FILTER_FLAGS_INTERACTIVE MCT_MANAGER_GET_VALUE_FLAGS_INTERACTIVE
/**
* MctSetAppFilterFlags:
* @MCT_SET_APP_FILTER_FLAGS_NONE: No flags set.
* @MCT_SET_APP_FILTER_FLAGS_INTERACTIVE: Allow interactive polkit dialogs when
* requesting authorization.
* MctManagerSetValueFlags:
* @MCT_MANAGER_SET_VALUE_FLAGS_NONE: No flags set.
* @MCT_MANAGER_SET_VALUE_FLAGS_INTERACTIVE: Allow interactive polkit dialogs
* when requesting authorization.
*
* Flags to control the behaviour of mct_manager_set_app_filter() and
* mct_manager_set_app_filter_async().
* Flags to control the behaviour of setter functions like
* mct_manager_set_app_filter() and mct_manager_set_app_filter_async().
*
* Since: 0.3.0
* Since: 0.5.0
*/
typedef enum
{
MCT_SET_APP_FILTER_FLAGS_NONE = 0,
MCT_SET_APP_FILTER_FLAGS_INTERACTIVE,
} MctSetAppFilterFlags;
MCT_MANAGER_SET_VALUE_FLAGS_NONE = 0,
MCT_MANAGER_SET_VALUE_FLAGS_INTERACTIVE = (1 << 0),
} MctManagerSetValueFlags;
/* FIXME: Eventually deprecate these compatibility fallbacks. */
typedef MctManagerSetValueFlags MctSetAppFilterFlags;
#define MCT_SET_APP_FILTER_FLAGS_NONE MCT_MANAGER_SET_VALUE_FLAGS_NONE
#define MCT_SET_APP_FILTER_FLAGS_INTERACTIVE MCT_MANAGER_SET_VALUE_FLAGS_INTERACTIVE
/**
* MctManagerError:
* @MCT_MANAGER_ERROR_INVALID_USER: Given user ID doesnt exist
* @MCT_MANAGER_ERROR_PERMISSION_DENIED: Not authorized to query properties of
* the given user
* @MCT_MANAGER_ERROR_INVALID_DATA: The data stored in a property of the given
* user is inconsistent or invalid
* @MCT_MANAGER_ERROR_DISABLED: Parental controls are disabled for all users
*
* Errors relating to get/set operations on an #MctManager instance.
*
* Since: 0.5.0
*/
typedef enum
{
MCT_MANAGER_ERROR_INVALID_USER,
MCT_MANAGER_ERROR_PERMISSION_DENIED,
MCT_MANAGER_ERROR_INVALID_DATA,
MCT_MANAGER_ERROR_DISABLED,
} MctManagerError;
GQuark mct_manager_error_quark (void);
#define MCT_MANAGER_ERROR mct_manager_error_quark ()
#include <libmalcontent/app-filter.h>
#include <libmalcontent/session-limits.h>
#define MCT_TYPE_MANAGER mct_manager_get_type ()
G_DECLARE_FINAL_TYPE (MctManager, mct_manager, MCT, MANAGER, GObject)
@ -70,12 +106,12 @@ MctManager *mct_manager_new (GDBusConnection *connection);
MctAppFilter *mct_manager_get_app_filter (MctManager *self,
uid_t user_id,
MctGetAppFilterFlags flags,
MctManagerGetValueFlags flags,
GCancellable *cancellable,
GError **error);
void mct_manager_get_app_filter_async (MctManager *self,
uid_t user_id,
MctGetAppFilterFlags flags,
MctManagerGetValueFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
@ -86,13 +122,13 @@ MctAppFilter *mct_manager_get_app_filter_finish (MctManager *self,
gboolean mct_manager_set_app_filter (MctManager *self,
uid_t user_id,
MctAppFilter *app_filter,
MctSetAppFilterFlags flags,
MctManagerSetValueFlags flags,
GCancellable *cancellable,
GError **error);
void mct_manager_set_app_filter_async (MctManager *self,
uid_t user_id,
MctAppFilter *app_filter,
MctSetAppFilterFlags flags,
MctManagerSetValueFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
@ -100,4 +136,36 @@ gboolean mct_manager_set_app_filter_finish (MctManager *self,
GAsyncResult *result,
GError **error);
MctSessionLimits *mct_manager_get_session_limits (MctManager *self,
uid_t user_id,
MctManagerGetValueFlags flags,
GCancellable *cancellable,
GError **error);
void mct_manager_get_session_limits_async (MctManager *self,
uid_t user_id,
MctManagerGetValueFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
MctSessionLimits *mct_manager_get_session_limits_finish (MctManager *self,
GAsyncResult *result,
GError **error);
gboolean mct_manager_set_session_limits (MctManager *self,
uid_t user_id,
MctSessionLimits *session_limits,
MctManagerSetValueFlags flags,
GCancellable *cancellable,
GError **error);
void mct_manager_set_session_limits_async (MctManager *self,
uid_t user_id,
MctSessionLimits *session_limits,
MctManagerSetValueFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean mct_manager_set_session_limits_finish (MctManager *self,
GAsyncResult *result,
GError **error);
G_END_DECLS

View File

@ -3,14 +3,17 @@ libmalcontent_api_name = 'malcontent-' + libmalcontent_api_version
libmalcontent_sources = [
'app-filter.c',
'manager.c',
'session-limits.c',
]
libmalcontent_headers = [
'app-filter.h',
'malcontent.h',
'manager.h',
'session-limits.h',
]
libmalcontent_private_headers = [
'app-filter-private.h',
'session-limits-private.h',
]
libmalcontent_public_deps = [

View File

@ -0,0 +1,62 @@
/* -*- 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>
*/
#pragma once
#include <gio/gio.h>
#include <glib.h>
#include <glib-object.h>
#include <libmalcontent/session-limits.h>
G_BEGIN_DECLS
/**
* MctSessionLimitsType:
* @MCT_SESSION_LIMITS_TYPE_NONE: No session limits are imposed.
* @MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE: Sessions are limited to between a
* pair of given times each day.
*
* Types of session limit which can be imposed on an account. Additional types
* may be added in future.
*
* Since: 0.5.0
*/
typedef enum
{
/* these values are used in the com.endlessm.ParentalControls.SessionLimits
* D-Bus interface, so must not be changed */
MCT_SESSION_LIMITS_TYPE_NONE = 0,
MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE = 1,
} MctSessionLimitsType;
struct _MctSessionLimits
{
gint ref_count;
uid_t user_id;
MctSessionLimitsType limit_type;
guint daily_start_time; /* seconds since midnight */
guint daily_end_time; /* seconds since midnight */
};
G_END_DECLS

View File

@ -0,0 +1,451 @@
/* -*- 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"
#include <glib.h>
#include <glib-object.h>
#include <glib/gi18n-lib.h>
#include <gio/gio.h>
#include <libmalcontent/session-limits.h>
#include "libmalcontent/session-limits-private.h"
/* struct _MctSessionLimits is defined in session-limits-private.h */
G_DEFINE_BOXED_TYPE (MctSessionLimits, mct_session_limits,
mct_session_limits_ref, mct_session_limits_unref)
/**
* mct_session_limits_ref:
* @limits: (transfer none): an #MctSessionLimits
*
* Increment the reference count of @limits, and return the same pointer to it.
*
* Returns: (transfer full): the same pointer as @limits
* Since: 0.5.0
*/
MctSessionLimits *
mct_session_limits_ref (MctSessionLimits *limits)
{
g_return_val_if_fail (limits != NULL, NULL);
g_return_val_if_fail (limits->ref_count >= 1, NULL);
g_return_val_if_fail (limits->ref_count <= G_MAXINT - 1, NULL);
limits->ref_count++;
return limits;
}
/**
* mct_session_limits_unref:
* @limits: (transfer full): an #MctSessionLimits
*
* Decrement the reference count of @limits. If the reference count reaches
* zero, free the @limits and all its resources.
*
* Since: 0.5.0
*/
void
mct_session_limits_unref (MctSessionLimits *limits)
{
g_return_if_fail (limits != NULL);
g_return_if_fail (limits->ref_count >= 1);
limits->ref_count--;
if (limits->ref_count <= 0)
{
g_free (limits);
}
}
/**
* mct_session_limits_get_user_id:
* @limits: an #MctSessionLimits
*
* Get the user ID of the user this #MctSessionLimits is for.
*
* Returns: user ID of the relevant user
* Since: 0.5.0
*/
uid_t
mct_session_limits_get_user_id (MctSessionLimits *limits)
{
g_return_val_if_fail (limits != NULL, (uid_t) -1);
g_return_val_if_fail (limits->ref_count >= 1, (uid_t) -1);
return limits->user_id;
}
/**
* mct_session_limits_check_time_remaining:
* @limits: an #MctSessionLimits
* @now_usecs: current time as microseconds since the Unix epoch (UTC),
* typically queried using g_get_real_time()
* @time_remaining_secs_out: (out) (optional): return location for the number
* of seconds remaining before the users session has to end, if limits are
* in force
* @time_limit_enabled_out: (out) (optional): return location for whether time
* limits are enabled for this user
*
* Check whether the user has time remaining in which they are allowed to use
* the computer, assuming that @now_usecs is the current time, and applying the
* session limit policy from @limits to it.
*
* This will return whether the user is allowed to use the computer now; further
* information about the policy and remaining time is provided in
* @time_remaining_secs_out and @time_limit_enabled_out.
*
* Returns: %TRUE if the user this @limits corresponds to is allowed to be in
* an active session at the given time; %FALSE otherwise
* Since: 0.5.0
*/
gboolean
mct_session_limits_check_time_remaining (MctSessionLimits *limits,
guint64 now_usecs,
guint64 *time_remaining_secs_out,
gboolean *time_limit_enabled_out)
{
guint64 time_remaining_secs;
gboolean time_limit_enabled;
gboolean user_allowed_now;
g_autoptr(GDateTime) now_dt = NULL;
guint64 now_time_of_day_secs;
g_return_val_if_fail (limits != NULL, FALSE);
g_return_val_if_fail (limits->ref_count >= 1, FALSE);
/* Helper calculations. */
now_dt = g_date_time_new_from_unix_utc (now_usecs / G_USEC_PER_SEC);
if (now_dt == NULL)
{
time_remaining_secs = 0;
time_limit_enabled = TRUE;
user_allowed_now = FALSE;
goto out;
}
now_time_of_day_secs = ((g_date_time_get_hour (now_dt) * 60 +
g_date_time_get_minute (now_dt)) * 60 +
g_date_time_get_second (now_dt));
/* Work out the limits. */
switch (limits->limit_type)
{
case MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE:
user_allowed_now = (now_time_of_day_secs >= limits->daily_start_time &&
now_time_of_day_secs < limits->daily_end_time);
time_remaining_secs = user_allowed_now ? (limits->daily_end_time - now_time_of_day_secs) : 0;
time_limit_enabled = TRUE;
g_debug ("%s: Daily schedule limit allowed in %u%u (now is %"
G_GUINT64_FORMAT "); %" G_GUINT64_FORMAT " seconds remaining",
G_STRFUNC, limits->daily_start_time, limits->daily_end_time,
now_time_of_day_secs, time_remaining_secs);
break;
case MCT_SESSION_LIMITS_TYPE_NONE:
default:
user_allowed_now = TRUE;
time_remaining_secs = G_MAXUINT64;
time_limit_enabled = FALSE;
g_debug ("%s: No limit enabled", G_STRFUNC);
break;
}
out:
/* Postconditions. */
g_assert (!user_allowed_now || time_remaining_secs > 0);
g_assert (user_allowed_now || time_remaining_secs == 0);
g_assert (time_limit_enabled || time_remaining_secs == G_MAXUINT64);
/* Output. */
if (time_remaining_secs_out != NULL)
*time_remaining_secs_out = time_remaining_secs;
if (time_limit_enabled_out != NULL)
*time_limit_enabled_out = time_limit_enabled;
return user_allowed_now;
}
/*
* Actual implementation of #MctSessionLimitsBuilder.
*
* All members are %NULL if un-initialised, cleared, or ended.
*/
typedef struct
{
MctSessionLimitsType limit_type;
/* Which member is used is determined by @limit_type: */
union
{
struct
{
guint start_time; /* seconds since midnight */
guint end_time; /* seconds since midnight */
} daily_schedule;
};
/*< private >*/
gpointer padding[10];
} MctSessionLimitsBuilderReal;
G_STATIC_ASSERT (sizeof (MctSessionLimitsBuilderReal) ==
sizeof (MctSessionLimitsBuilder));
G_STATIC_ASSERT (__alignof__ (MctSessionLimitsBuilderReal) ==
__alignof__ (MctSessionLimitsBuilder));
G_DEFINE_BOXED_TYPE (MctSessionLimitsBuilder, mct_session_limits_builder,
mct_session_limits_builder_copy, mct_session_limits_builder_free)
/**
* mct_session_limits_builder_init:
* @builder: an uninitialised #MctSessionLimitsBuilder
*
* Initialise the given @builder so it can be used to construct a new
* #MctSessionLimits. @builder must have been allocated on the stack, and must
* not already be initialised.
*
* Construct the #MctSessionLimits by calling methods on @builder, followed by
* mct_session_limits_builder_end(). To abort construction, use
* mct_session_limits_builder_clear().
*
* Since: 0.5.0
*/
void
mct_session_limits_builder_init (MctSessionLimitsBuilder *builder)
{
MctSessionLimitsBuilder local_builder = MCT_SESSION_LIMITS_BUILDER_INIT ();
MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder;
g_return_if_fail (_builder != NULL);
g_return_if_fail (_builder->limit_type == MCT_SESSION_LIMITS_TYPE_NONE);
memcpy (builder, &local_builder, sizeof (local_builder));
}
/**
* mct_session_limits_builder_clear:
* @builder: an #MctSessionLimitsBuilder
*
* Clear @builder, freeing any internal state in it. This will not free the
* top-level storage for @builder itself, which is assumed to be allocated on
* the stack.
*
* If called on an already-cleared #MctSessionLimitsBuilder, this function is
* idempotent.
*
* Since: 0.5.0
*/
void
mct_session_limits_builder_clear (MctSessionLimitsBuilder *builder)
{
MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder;
g_return_if_fail (_builder != NULL);
/* Nothing to free here for now. */
_builder->limit_type = MCT_SESSION_LIMITS_TYPE_NONE;
}
/**
* mct_session_limits_builder_new:
*
* Construct a new #MctSessionLimitsBuilder on the heap. This is intended for
* language bindings. The returned builder must eventually be freed with
* mct_session_limits_builder_free(), but can be cleared zero or more times with
* mct_session_limits_builder_clear() first.
*
* Returns: (transfer full): a new heap-allocated #MctSessionLimitsBuilder
* Since: 0.5.0
*/
MctSessionLimitsBuilder *
mct_session_limits_builder_new (void)
{
g_autoptr(MctSessionLimitsBuilder) builder = NULL;
builder = g_new0 (MctSessionLimitsBuilder, 1);
mct_session_limits_builder_init (builder);
return g_steal_pointer (&builder);
}
/**
* mct_session_limits_builder_copy:
* @builder: an #MctSessionLimitsBuilder
*
* Copy the given @builder to a newly-allocated #MctSessionLimitsBuilder on the
* heap. This is safe to use with cleared, stack-allocated
* #MctSessionLimitsBuilders.
*
* Returns: (transfer full): a copy of @builder
* Since: 0.5.0
*/
MctSessionLimitsBuilder *
mct_session_limits_builder_copy (MctSessionLimitsBuilder *builder)
{
MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder;
g_autoptr(MctSessionLimitsBuilder) copy = NULL;
MctSessionLimitsBuilderReal *_copy;
g_return_val_if_fail (builder != NULL, NULL);
copy = mct_session_limits_builder_new ();
_copy = (MctSessionLimitsBuilderReal *) copy;
mct_session_limits_builder_clear (copy);
_copy->limit_type = _builder->limit_type;
switch (_builder->limit_type)
{
case MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE:
_copy->daily_schedule.start_time = _builder->daily_schedule.start_time;
_copy->daily_schedule.end_time = _builder->daily_schedule.end_time;
break;
case MCT_SESSION_LIMITS_TYPE_NONE:
default:
break;
}
return g_steal_pointer (&copy);
}
/**
* mct_session_limits_builder_free:
* @builder: a heap-allocated #MctSessionLimitsBuilder
*
* Free an #MctSessionLimitsBuilder originally allocated using
* mct_session_limits_builder_new(). This must not be called on stack-allocated
* builders initialised using mct_session_limits_builder_init().
*
* Since: 0.5.0
*/
void
mct_session_limits_builder_free (MctSessionLimitsBuilder *builder)
{
g_return_if_fail (builder != NULL);
mct_session_limits_builder_clear (builder);
g_free (builder);
}
/**
* mct_session_limits_builder_end:
* @builder: an initialised #MctSessionLimitsBuilder
*
* Finish constructing an #MctSessionLimits with the given @builder, and return
* it. The #MctSessionLimitsBuilder will be cleared as if
* mct_session_limits_builder_clear() had been called.
*
* Returns: (transfer full): a newly constructed #MctSessionLimits
* Since: 0.5.0
*/
MctSessionLimits *
mct_session_limits_builder_end (MctSessionLimitsBuilder *builder)
{
MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder;
g_autoptr(MctSessionLimits) session_limits = NULL;
g_return_val_if_fail (_builder != NULL, NULL);
/* Build the #MctSessionLimits. */
session_limits = g_new0 (MctSessionLimits, 1);
session_limits->ref_count = 1;
session_limits->user_id = -1;
session_limits->limit_type = _builder->limit_type;
switch (_builder->limit_type)
{
case MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE:
session_limits->daily_start_time = _builder->daily_schedule.start_time;
session_limits->daily_end_time = _builder->daily_schedule.end_time;
break;
case MCT_SESSION_LIMITS_TYPE_NONE:
default:
/* Defaults: */
session_limits->daily_start_time = 0;
session_limits->daily_end_time = 24 * 60 * 60;
break;
}
mct_session_limits_builder_clear (builder);
return g_steal_pointer (&session_limits);
}
/**
* mct_session_limits_builder_set_none:
* @builder: an initialised #MctSessionLimitsBuilder
*
* Unset any session limits currently set in the @builder.
*
* Since: 0.5.0
*/
void
mct_session_limits_builder_set_none (MctSessionLimitsBuilder *builder)
{
MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder;
g_return_if_fail (_builder != NULL);
/* This will need to free other limit types data first in future. */
_builder->limit_type = MCT_SESSION_LIMITS_TYPE_NONE;
}
/**
* mct_session_limits_builder_set_daily_schedule:
* @builder: an initialised #MctSessionLimitsBuilder
* @start_time_secs: number of seconds since midnight when the users session
* can first start
* @end_time_secs: number of seconds since midnight when the users session can
* last end
*
* Set the session limits in @builder to be a daily schedule, where sessions are
* allowed between @start_time_secs and @end_time_secs every day.
* @start_time_secs and @end_time_secs are given as offsets from the start of
* the day, in seconds. @end_time_secs must be greater than @start_time_secs.
* @end_time_secs must be at most `24 * 60 * 60`.
*
* This will overwrite any other session limits.
*
* Since: 0.5.0
*/
void
mct_session_limits_builder_set_daily_schedule (MctSessionLimitsBuilder *builder,
guint start_time_secs,
guint end_time_secs)
{
MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder;
g_return_if_fail (_builder != NULL);
g_return_if_fail (start_time_secs < end_time_secs);
g_return_if_fail (end_time_secs <= 24 * 60 * 60);
/* This will need to free other limit types data first in future. */
_builder->limit_type = MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE;
_builder->daily_schedule.start_time = start_time_secs;
_builder->daily_schedule.end_time = end_time_secs;
}

View File

@ -0,0 +1,122 @@
/* -*- 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>
*/
#pragma once
#include <gio/gio.h>
#include <glib.h>
#include <glib-object.h>
G_BEGIN_DECLS
/**
* MctSessionLimits:
*
* #MctSessionLimits is an opaque, immutable structure which contains a snapshot
* of the session limits settings for a user at a given time. This includes
* whether session limits are being enforced, and the limit policy for
* example, the times of day when a user is allowed to use the computer.
*
* Typically, session limits settings can only be changed by the administrator,
* and are read-only for non-administrative users. The precise policy is set
* using polkit.
*
* Since: 0.5.0
*/
typedef struct _MctSessionLimits MctSessionLimits;
GType mct_session_limits_get_type (void);
MctSessionLimits *mct_session_limits_ref (MctSessionLimits *limits);
void mct_session_limits_unref (MctSessionLimits *limits);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (MctSessionLimits, mct_session_limits_unref)
uid_t mct_session_limits_get_user_id (MctSessionLimits *limits);
gboolean mct_session_limits_check_time_remaining (MctSessionLimits *limits,
guint64 now_usecs,
guint64 *time_remaining_secs_out,
gboolean *time_limit_enabled_out);
/**
* MctSessionLimitsBuilder:
*
* #MctSessionLimitsBuilder is a stack-allocated mutable structure used to build
* an #MctSessionLimits instance. Use mct_session_limits_builder_init(), various
* method calls to set properties of the session limits, and then
* mct_session_limits_builder_end(), to construct an #MctSessionLimits.
*
* Since: 0.5.0
*/
typedef struct
{
/*< private >*/
guint u0;
guint u1;
guint u2;
gpointer p0[10];
} MctSessionLimitsBuilder;
GType mct_session_limits_builder_get_type (void);
/**
* MCT_SESSION_LIMITS_BUILDER_INIT:
*
* Initialise a stack-allocated #MctSessionLimitsBuilder instance at declaration
* time.
*
* This is typically used with g_auto():
* |[
* g_auto(MctSessionLimitsBuilder) builder = MCT_SESSION_LIMITS_BUILDER_INIT ();
* ]|
*
* Since: 0.5.0
*/
#define MCT_SESSION_LIMITS_BUILDER_INIT() \
{ \
0, /* MCT_SESSION_LIMITS_TYPE_NONE */ \
0, \
0, \
/* padding: */ \
{ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } \
}
void mct_session_limits_builder_init (MctSessionLimitsBuilder *builder);
void mct_session_limits_builder_clear (MctSessionLimitsBuilder *builder);
G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (MctSessionLimitsBuilder,
mct_session_limits_builder_clear)
MctSessionLimitsBuilder *mct_session_limits_builder_new (void);
MctSessionLimitsBuilder *mct_session_limits_builder_copy (MctSessionLimitsBuilder *builder);
void mct_session_limits_builder_free (MctSessionLimitsBuilder *builder);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (MctSessionLimitsBuilder, mct_session_limits_builder_free)
MctSessionLimits *mct_session_limits_builder_end (MctSessionLimitsBuilder *builder);
void mct_session_limits_builder_set_none (MctSessionLimitsBuilder *builder);
void mct_session_limits_builder_set_daily_schedule (MctSessionLimitsBuilder *builder,
guint start_time_secs,
guint end_time_secs);
G_END_DECLS

View File

@ -594,7 +594,7 @@ test_app_filter_bus_get (BusFixture *fixture,
mct_manager_get_app_filter_async (fixture->manager,
fixture->valid_uid,
MCT_GET_APP_FILTER_FLAGS_NONE, NULL,
MCT_MANAGER_GET_VALUE_FLAGS_NONE, NULL,
async_result_cb, &result);
while (result == NULL)
@ -605,7 +605,7 @@ test_app_filter_bus_get (BusFixture *fixture,
{
app_filter = mct_manager_get_app_filter (fixture->manager,
fixture->valid_uid,
MCT_GET_APP_FILTER_FLAGS_NONE, NULL,
MCT_MANAGER_GET_VALUE_FLAGS_NONE, NULL,
&local_error);
}
@ -650,7 +650,7 @@ test_app_filter_bus_get_whitelist (BusFixture *fixture,
app_filter = mct_manager_get_app_filter (fixture->manager,
fixture->valid_uid,
MCT_GET_APP_FILTER_FLAGS_NONE, NULL,
MCT_MANAGER_GET_VALUE_FLAGS_NONE, NULL,
&local_error);
g_assert_no_error (local_error);
@ -705,7 +705,7 @@ test_app_filter_bus_get_all_oars_values (BusFixture *fixture,
app_filter = mct_manager_get_app_filter (fixture->manager,
fixture->valid_uid,
MCT_GET_APP_FILTER_FLAGS_NONE, NULL,
MCT_MANAGER_GET_VALUE_FLAGS_NONE, NULL,
&local_error);
g_assert_no_error (local_error);
@ -753,7 +753,7 @@ test_app_filter_bus_get_defaults (BusFixture *fixture,
app_filter = mct_manager_get_app_filter (fixture->manager,
fixture->valid_uid,
MCT_GET_APP_FILTER_FLAGS_NONE, NULL,
MCT_MANAGER_GET_VALUE_FLAGS_NONE, NULL,
&local_error);
g_assert_no_error (local_error);
@ -785,7 +785,7 @@ test_app_filter_bus_get_error_invalid_user (BusFixture *fixture,
mct_manager_get_app_filter_async (fixture->manager,
fixture->missing_uid,
MCT_GET_APP_FILTER_FLAGS_NONE, NULL,
MCT_MANAGER_GET_VALUE_FLAGS_NONE, NULL,
async_result_cb, &result);
/* Handle the FindUserById() call and claim the user doesnt exist. */
@ -809,7 +809,7 @@ test_app_filter_bus_get_error_invalid_user (BusFixture *fixture,
&local_error);
g_assert_error (local_error,
MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_INVALID_USER);
MCT_MANAGER_ERROR, MCT_MANAGER_ERROR_INVALID_USER);
g_assert_null (app_filter);
}
@ -831,7 +831,7 @@ test_app_filter_bus_get_error_permission_denied (BusFixture *fixture,
mct_manager_get_app_filter_async (fixture->manager,
fixture->valid_uid,
MCT_GET_APP_FILTER_FLAGS_NONE, NULL,
MCT_MANAGER_GET_VALUE_FLAGS_NONE, NULL,
async_result_cb, &result);
/* Handle the FindUserById() call. */
@ -866,7 +866,7 @@ test_app_filter_bus_get_error_permission_denied (BusFixture *fixture,
&local_error);
g_assert_error (local_error,
MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_PERMISSION_DENIED);
MCT_MANAGER_ERROR, MCT_MANAGER_ERROR_PERMISSION_DENIED);
g_assert_null (app_filter);
}
@ -888,7 +888,7 @@ test_app_filter_bus_get_error_permission_denied_missing (BusFixture *fixture,
mct_manager_get_app_filter_async (fixture->manager,
fixture->valid_uid,
MCT_GET_APP_FILTER_FLAGS_NONE, NULL,
MCT_MANAGER_GET_VALUE_FLAGS_NONE, NULL,
async_result_cb, &result);
/* Handle the FindUserById() call. */
@ -924,7 +924,7 @@ test_app_filter_bus_get_error_permission_denied_missing (BusFixture *fixture,
&local_error);
g_assert_error (local_error,
MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_PERMISSION_DENIED);
MCT_MANAGER_ERROR, MCT_MANAGER_ERROR_PERMISSION_DENIED);
g_assert_null (app_filter);
}
@ -943,7 +943,7 @@ test_app_filter_bus_get_error_unknown (BusFixture *fixture,
mct_manager_get_app_filter_async (fixture->manager,
fixture->valid_uid,
MCT_GET_APP_FILTER_FLAGS_NONE, NULL,
MCT_MANAGER_GET_VALUE_FLAGS_NONE, NULL,
async_result_cb, &result);
/* Handle the FindUserById() call and return a bogus error. */
@ -991,7 +991,7 @@ test_app_filter_bus_get_error_disabled (BusFixture *fixture,
mct_manager_get_app_filter_async (fixture->manager,
fixture->valid_uid,
MCT_GET_APP_FILTER_FLAGS_NONE, NULL,
MCT_MANAGER_GET_VALUE_FLAGS_NONE, NULL,
async_result_cb, &result);
/* Handle the FindUserById() call. */
@ -1027,7 +1027,7 @@ test_app_filter_bus_get_error_disabled (BusFixture *fixture,
&local_error);
g_assert_error (local_error,
MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_DISABLED);
MCT_MANAGER_ERROR, MCT_MANAGER_ERROR_DISABLED);
g_assert_null (app_filter);
}
@ -1184,7 +1184,7 @@ test_app_filter_bus_set (BusFixture *fixture,
mct_manager_set_app_filter_async (fixture->manager,
fixture->valid_uid, app_filter,
MCT_SET_APP_FILTER_FLAGS_NONE, NULL,
MCT_MANAGER_SET_VALUE_FLAGS_NONE, NULL,
async_result_cb, &result);
while (result == NULL)
@ -1196,7 +1196,7 @@ test_app_filter_bus_set (BusFixture *fixture,
{
success = mct_manager_set_app_filter (fixture->manager,
fixture->valid_uid, app_filter,
MCT_SET_APP_FILTER_FLAGS_NONE, NULL,
MCT_MANAGER_SET_VALUE_FLAGS_NONE, NULL,
&local_error);
}
@ -1225,7 +1225,7 @@ test_app_filter_bus_set_error_invalid_user (BusFixture *fixture,
mct_manager_set_app_filter_async (fixture->manager,
fixture->missing_uid, app_filter,
MCT_SET_APP_FILTER_FLAGS_NONE, NULL,
MCT_MANAGER_SET_VALUE_FLAGS_NONE, NULL,
async_result_cb, &result);
/* Handle the FindUserById() call and claim the user doesnt exist. */
@ -1249,7 +1249,7 @@ test_app_filter_bus_set_error_invalid_user (BusFixture *fixture,
&local_error);
g_assert_error (local_error,
MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_INVALID_USER);
MCT_MANAGER_ERROR, MCT_MANAGER_ERROR_INVALID_USER);
g_assert_false (success);
}
@ -1282,11 +1282,11 @@ test_app_filter_bus_set_error_permission_denied (BusFixture *fixture,
success = mct_manager_set_app_filter (fixture->manager,
fixture->valid_uid, app_filter,
MCT_SET_APP_FILTER_FLAGS_NONE, NULL,
MCT_MANAGER_SET_VALUE_FLAGS_NONE, NULL,
&local_error);
g_assert_error (local_error,
MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_PERMISSION_DENIED);
MCT_MANAGER_ERROR, MCT_MANAGER_ERROR_PERMISSION_DENIED);
g_assert_false (success);
}
@ -1320,7 +1320,7 @@ test_app_filter_bus_set_error_unknown (BusFixture *fixture,
success = mct_manager_set_app_filter (fixture->manager,
fixture->valid_uid, app_filter,
MCT_SET_APP_FILTER_FLAGS_NONE, NULL,
MCT_MANAGER_SET_VALUE_FLAGS_NONE, NULL,
&local_error);
g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR);
@ -1363,7 +1363,7 @@ test_app_filter_bus_set_error_invalid_property (BusFixture *fixture,
success = mct_manager_set_app_filter (fixture->manager,
fixture->valid_uid, app_filter,
MCT_SET_APP_FILTER_FLAGS_NONE, NULL,
MCT_MANAGER_SET_VALUE_FLAGS_NONE, NULL,
&local_error);
g_assert_error (local_error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);

View File

@ -33,9 +33,14 @@ accounts_service_iface_c = custom_target(
'@INPUT@'],
)
accounts_service_extension_ifaces = [
join_paths(meson.source_root(), 'accounts-service', 'com.endlessm.ParentalControls.AppFilter.xml'),
join_paths(meson.source_root(), 'accounts-service', 'com.endlessm.ParentalControls.SessionLimits.xml'),
]
accounts_service_extension_iface_h = custom_target(
'accounts-service-extension-iface.h',
input: ['com.endlessm.ParentalControls.AppFilter.xml'],
input: accounts_service_extension_ifaces,
output: ['accounts-service-extension-iface.h'],
command: [gdbus_codegen,
'--interface-info-header',
@ -44,7 +49,7 @@ accounts_service_extension_iface_h = custom_target(
)
accounts_service_extension_iface_c = custom_target(
'accounts-service-extension-iface.c',
input: ['com.endlessm.ParentalControls.AppFilter.xml'],
input: accounts_service_extension_ifaces,
output: ['accounts-service-extension-iface.c'],
command: [gdbus_codegen,
'--interface-info-body',
@ -59,6 +64,12 @@ test_programs = [
accounts_service_extension_iface_h,
accounts_service_extension_iface_c,
], deps],
['session-limits', [
accounts_service_iface_h,
accounts_service_iface_c,
accounts_service_extension_iface_h,
accounts_service_extension_iface_c,
], deps],
]
installed_tests_metadir = join_paths(datadir, 'installed-tests',

File diff suppressed because it is too large Load Diff

View File

@ -10,9 +10,9 @@ malcontent\-client — Parental Controls Access Utility
.SH SYNOPSIS
.IX Header "SYNOPSIS"
.\"
\fBmalcontent\-client get [\-q] [\-n] [\fPUSER\fB]
\fBmalcontent\-client get\-app\-filter [\-q] [\-n] [\fPUSER\fB]
.PP
\fBmalcontent\-client check [\-q] [\-n] [\fPUSER\fB] \fPARG\fB
\fBmalcontent\-client check\-app\-filter [\-q] [\-n] [\fPUSER\fB] \fPARG\fB
.\"
.SH DESCRIPTION
.IX Header "DESCRIPTION"
@ -25,10 +25,13 @@ controls.
It communicates with accounts-service, which stores parental controls data.
.PP
Its first argument is a command to run. Currently, the only supported commands
are \fBget\fP and \fBcheck\fP.
are \fBget\-app\-filter\fP and \fBcheck\-app\-filter\fP.
.PP
The command line API and output format are unstable and likely to change in
future versions of \fBmalcontent\-client\fP.
.\"
.SH \fBget\fP OPTIONS
.IX Header "get OPTIONS"
.SH \fBget\-app\-filter\fP OPTIONS
.IX Header "get\-app\-filter OPTIONS"
.\"
.IP "\fBUSER\fP"
Username or ID of the user to get the app filter for. If not specified, the
@ -43,8 +46,8 @@ Do not allow interactive authorization with polkit. If this is needed to
complete the operation, the operation will fail. (Default: Allow interactive
authorization.)
.\"
.SH \fBcheck\fP OPTIONS
.IX Header "check OPTIONS"
.SH \fBcheck\-app\-filter\fP OPTIONS
.IX Header "check\-app\-filter OPTIONS"
.\"
.IP "\fBUSER\fP"
Username or ID of the user to get the app filter for. If not specified, the
@ -85,8 +88,8 @@ encounters problems.
.IP "0" 4
.IX Item "0"
No problems occurred. The utility ran and successfully queried the app filter.
If running the \fBcheck\fP command, the given path, content type or flatpak ref
was allowed for the given user.
If running the \fBcheck\-app\-filter\fP command, the given path, content type or
flatpak ref was allowed for the given user.
.\"
.IP "1" 4
.IX Item "1"
@ -99,8 +102,17 @@ The current user was not authorized to query the app filter for the given user.
.\"
.IP "3" 4
.IX Item "3"
If running the \fBcheck\fP command, the given path, content type or flatpak ref
was \fInot\fP allowed for the given user.
If running the \fBcheck\-app\-filter\fP command, the given path, content type or
flatpak ref was \fInot\fP allowed for the given user.
.\"
.IP "4" 4
.IX Item "4"
Malcontent is disabled at the system level, and hence parental controls are
not enabled or enforced.
.\"
.IP "5" 4
.IX Item "5"
An operation failed and no more specific error information is available.
.\"
.SH BUGS
.IX Header "BUGS"

View File

@ -17,6 +17,7 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import argparse
import datetime
import os
import pwd
import sys
@ -30,6 +31,25 @@ EXIT_SUCCESS = 0
EXIT_INVALID_OPTION = 1
EXIT_PERMISSION_DENIED = 2
EXIT_PATH_NOT_ALLOWED = 3
EXIT_DISABLED = 4
EXIT_FAILED = 5
def __manager_error_to_exit_code(error):
if error.matches(Malcontent.manager_error_quark(),
Malcontent.ManagerError.INVALID_USER):
return EXIT_INVALID_OPTION
elif error.matches(Malcontent.manager_error_quark(),
Malcontent.ManagerError.PERMISSION_DENIED):
return EXIT_PERMISSION_DENIED
elif error.matches(Malcontent.manager_error_quark(),
Malcontent.ManagerError.INVALID_DATA):
return EXIT_INVALID_OPTION
elif error.matches(Malcontent.manager_error_quark(),
Malcontent.ManagerError.DISABLED):
return EXIT_DISABLED
return EXIT_FAILED
def __get_app_filter(user_id, interactive):
@ -38,9 +58,9 @@ def __get_app_filter(user_id, interactive):
If `interactive` is `True`, interactive polkit authorisation dialogues will
be allowed. An exception will be raised on failure."""
if interactive:
flags = Malcontent.GetAppFilterFlags.INTERACTIVE
flags = Malcontent.ManagerGetValueFlags.INTERACTIVE
else:
flags = Malcontent.GetAppFilterFlags.NONE
flags = Malcontent.ManagerGetValueFlags.NONE
connection = Gio.bus_get_sync(Gio.BusType.SYSTEM)
manager = Malcontent.Manager.new(connection)
@ -57,7 +77,35 @@ def __get_app_filter_or_error(user_id, interactive):
except GLib.Error as e:
print('Error getting app filter for user {}: {}'.format(
user_id, e.message), file=sys.stderr)
raise SystemExit(EXIT_PERMISSION_DENIED)
raise SystemExit(__manager_error_to_exit_code(e))
def __get_session_limits(user_id, interactive):
"""Get the session limits for `user_id` off the bus.
If `interactive` is `True`, interactive polkit authorisation dialogues will
be allowed. An exception will be raised on failure."""
if interactive:
flags = Malcontent.ManagerGetValueFlags.INTERACTIVE
else:
flags = Malcontent.ManagerGetValueFlags.NONE
connection = Gio.bus_get_sync(Gio.BusType.SYSTEM)
manager = Malcontent.Manager.new(connection)
return manager.get_session_limits(
user_id=user_id,
flags=flags, cancellable=None)
def __get_session_limits_or_error(user_id, interactive):
"""Wrapper around __get_session_limits() which prints an error and raises
SystemExit, rather than an internal exception."""
try:
return __get_session_limits(user_id, interactive)
except GLib.Error as e:
print('Error getting session limits for user {}: {}'.format(
user_id, e.message), file=sys.stderr)
raise SystemExit(__manager_error_to_exit_code(e))
def __set_app_filter(user_id, app_filter, interactive):
@ -66,9 +114,9 @@ def __set_app_filter(user_id, app_filter, interactive):
If `interactive` is `True`, interactive polkit authorisation dialogues will
be allowed. An exception will be raised on failure."""
if interactive:
flags = Malcontent.GetAppFilterFlags.INTERACTIVE
flags = Malcontent.ManagerSetValueFlags.INTERACTIVE
else:
flags = Malcontent.GetAppFilterFlags.NONE
flags = Malcontent.ManagerSetValueFlags.NONE
connection = Gio.bus_get_sync(Gio.BusType.SYSTEM)
manager = Malcontent.Manager.new(connection)
@ -85,29 +133,34 @@ def __set_app_filter_or_error(user_id, app_filter, interactive):
except GLib.Error as e:
print('Error setting app filter for user {}: {}'.format(
user_id, e.message), file=sys.stderr)
raise SystemExit(EXIT_PERMISSION_DENIED)
raise SystemExit(__manager_error_to_exit_code(e))
def __lookup_user_id(user):
"""Convert a command-line specified username or ID into a user ID. If
`user` is empty, use the current user ID.
def __lookup_user_id(user_id_or_username):
"""Convert a command-line specified username or ID into a
(user ID, username) tuple, looking up the component which isnt specified.
If `user_id_or_username` is empty, use the current user ID.
Raise KeyError if lookup fails."""
if user == '':
return os.getuid()
elif user.isdigit():
return int(user)
if user_id_or_username == '':
user_id = os.getuid()
return (user_id, pwd.getpwuid(user_id).pw_name)
elif user_id_or_username.isdigit():
user_id = int(user_id_or_username)
return (user_id, pwd.getpwuid(user_id).pw_name)
else:
return pwd.getpwnam(user).pw_uid
username = user_id_or_username
return (pwd.getpwnam(username).pw_uid, username)
def __lookup_user_id_or_error(user):
def __lookup_user_id_or_error(user_id_or_username):
"""Wrapper around __lookup_user_id() which prints an error and raises
SystemExit, rather than an internal exception."""
try:
return __lookup_user_id(user)
return __lookup_user_id(user_id_or_username)
except KeyError:
print('Error getting ID for username {}'.format(user), file=sys.stderr)
print('Error getting ID for username {}'.format(user_id_or_username),
file=sys.stderr)
raise SystemExit(EXIT_INVALID_OPTION)
@ -138,12 +191,12 @@ def __oars_value_from_string(value_str):
raise KeyError('Unknown OARS value {}'.format(value_str))
def command_get(user, quiet=False, interactive=True):
def command_get_app_filter(user, quiet=False, interactive=True):
"""Get the app filter for the given user."""
user_id = __lookup_user_id_or_error(user)
(user_id, username) = __lookup_user_id_or_error(user)
app_filter = __get_app_filter_or_error(user_id, interactive)
print('App filter for user {} retrieved:'.format(user_id))
print('App filter for user {} retrieved:'.format(username))
sections = app_filter.get_oars_sections()
for section in sections:
@ -163,12 +216,30 @@ def command_get(user, quiet=False, interactive=True):
print('App installation is disallowed to system repository')
def command_get_session_limits(user, now=None, quiet=False, interactive=True):
"""Get the session limits for the given user."""
(user_id, username) = __lookup_user_id_or_error(user)
session_limits = __get_session_limits_or_error(user_id, interactive)
(user_allowed_now, time_remaining_secs, time_limit_enabled) = \
session_limits.check_time_remaining(now.timestamp() * GLib.USEC_PER_SEC)
if not time_limit_enabled:
print('Session limits are not enabled for user {}'.format(username))
elif user_allowed_now:
print('Session limits are enabled for user {}, and they have {} '
'seconds remaining'.format(username, time_remaining_secs))
else:
print('Session limits are enabled for user {}, and they have no time '
'remaining'.format(username))
def command_monitor(user, quiet=False, interactive=True):
"""Monitor app filter changes for the given user."""
if user == '':
filter_user_id = 0
(filter_user_id, filter_username) = (0, '')
else:
filter_user_id = __lookup_user_id_or_error(user)
(filter_user_id, filter_username) = __lookup_user_id_or_error(user)
apply_filter = (user != '')
def _on_app_filter_changed(manager, changed_user_id):
@ -181,7 +252,7 @@ def command_monitor(user, quiet=False, interactive=True):
if apply_filter:
print('Monitoring app filter changes for '
'user ID {}'.format(filter_user_id))
'user {}'.format(filter_username))
else:
print('Monitoring app filter changes for all users')
@ -213,10 +284,10 @@ def is_valid_content_type(arg):
parts[0] != '' and parts[1] != '')
def command_check(user, arg, quiet=False, interactive=True):
def command_check_app_filter(user, arg, quiet=False, interactive=True):
"""Check the given path, content type or flatpak ref is runnable by the
given user, according to their app filter."""
user_id = __lookup_user_id_or_error(user)
(user_id, username) = __lookup_user_id_or_error(user)
app_filter = __get_app_filter_or_error(user_id, interactive)
is_maybe_flatpak_id = arg.startswith('app/') and arg.count('/') < 3
@ -259,31 +330,32 @@ def command_check(user, arg, quiet=False, interactive=True):
if is_allowed:
if not quiet:
print('{} {} is allowed by app filter for user {}'.format(
noun, arg, user_id))
noun, arg, username))
return
else:
if not quiet:
print('{} {} is not allowed by app filter for user {}'.format(
noun, arg, user_id))
noun, arg, username))
raise SystemExit(EXIT_PATH_NOT_ALLOWED)
def command_oars_section(user, section, quiet=False, interactive=True):
"""Get the value of the given OARS section for the given user, according
to their OARS filter."""
user_id = __lookup_user_id_or_error(user)
(user_id, username) = __lookup_user_id_or_error(user)
app_filter = __get_app_filter_or_error(user_id, interactive)
value = app_filter.get_oars_value(section)
print('OARS section {} for user {} has value {}'.format(
section, user_id, __oars_value_to_string(value)))
section, username, __oars_value_to_string(value)))
def command_set(user, allow_user_installation=True,
allow_system_installation=False, app_filter_args=None,
quiet=False, interactive=True):
def command_set_app_filter(user, allow_user_installation=True,
allow_system_installation=False,
app_filter_args=None, quiet=False,
interactive=True):
"""Set the app filter for the given user."""
user_id = __lookup_user_id_or_error(user)
(user_id, username) = __lookup_user_id_or_error(user)
builder = Malcontent.AppFilterBuilder.new()
builder.set_allow_user_installation(allow_user_installation)
builder.set_allow_system_installation(allow_system_installation)
@ -327,7 +399,7 @@ def command_set(user, allow_user_installation=True,
__set_app_filter_or_error(user_id, app_filter, interactive)
if not quiet:
print('App filter for user {} set'.format(user_id))
print('App filter for user {} set'.format(username))
def main():
@ -335,8 +407,9 @@ def main():
parser = argparse.ArgumentParser(
description='Query and update parental controls.')
subparsers = parser.add_subparsers(metavar='command',
help='command to run (default: get)')
parser.set_defaults(function=command_get)
help='command to run (default: '
'get-app-filter)')
parser.set_defaults(function=command_get_app_filter, user='')
parser.add_argument('-q', '--quiet', action='store_true',
help='output no informational messages')
parser.set_defaults(quiet=False)
@ -353,14 +426,34 @@ def main():
help='opposite of --no-interactive')
common_parser.set_defaults(interactive=True)
# get command
parser_get = subparsers.add_parser('get', parents=[common_parser],
help='get current parental controls '
'settings')
parser_get.set_defaults(function=command_get)
parser_get.add_argument('user', default='', nargs='?',
help='user ID or username to get the app filter '
'for (default: current user)')
# get-app-filter command
parser_get_app_filter = \
subparsers.add_parser('get-app-filter',
parents=[common_parser],
help='get current app filter settings')
parser_get_app_filter.set_defaults(function=command_get_app_filter)
parser_get_app_filter.add_argument('user', default='', nargs='?',
help='user ID or username to get the '
'app filter for (default: current '
'user)')
# get-session-limits command
parser_get_session_limits = \
subparsers.add_parser('get-session-limits',
parents=[common_parser],
help='get current session limit settings')
parser_get_session_limits.set_defaults(function=command_get_session_limits)
parser_get_session_limits.add_argument('user', default='', nargs='?',
help='user ID or username to get '
'the session limits for (default: '
'current user)')
parser_get_session_limits.add_argument(
'--now',
metavar='yyyy-mm-ddThh:mm:ssZ',
type=lambda d: datetime.datetime.strptime(d, '%Y-%m-%dT%H:%M:%S%z'),
default=datetime.datetime.now(),
help='date/time to use as the value for now (default: wall clock '
'time)')
# monitor command
parser_monitor = subparsers.add_parser('monitor',
@ -371,18 +464,19 @@ def main():
help='user ID or username to monitor the app '
'filter for (default: all users)')
# check command
parser_check = subparsers.add_parser('check', parents=[common_parser],
help='check whether a path, content '
'type or flatpak ref is '
'allowed by app filter')
parser_check.set_defaults(function=command_check)
parser_check.add_argument('user', default='', nargs='?',
help='user ID or username to get the app filter '
'for (default: current user)')
parser_check.add_argument('arg',
help='path to a program, content type or '
'flatpak ref to check')
# check-app-filter command
parser_check_app_filter = \
subparsers.add_parser('check-app-filter', parents=[common_parser],
help='check whether a path, content type or '
'flatpak ref is allowed by app filter')
parser_check_app_filter.set_defaults(function=command_check_app_filter)
parser_check_app_filter.add_argument('user', default='', nargs='?',
help='user ID or username to get the '
'app filter for (default: '
'current user)')
parser_check_app_filter.add_argument('arg',
help='path to a program, content '
'type or flatpak ref to check')
# oars-section command
parser_oars_section = subparsers.add_parser('oars-section',
@ -396,40 +490,43 @@ def main():
'user)')
parser_oars_section.add_argument('section', help='OARS section to get')
# set command
parser_set = subparsers.add_parser('set', parents=[common_parser],
help='set current parental controls '
'settings')
parser_set.set_defaults(function=command_set)
parser_set.add_argument('user', default='', nargs='?',
help='user ID or username to get the app filter '
'for (default: current user)')
parser_set.add_argument('--allow-user-installation',
dest='allow_user_installation',
action='store_true',
help='allow installation to the user flatpak '
'repo in general')
parser_set.add_argument('--disallow-user-installation',
dest='allow_user_installation',
action='store_false',
help='unconditionally disallow installation to '
'the user flatpak repo')
parser_set.add_argument('--allow-system-installation',
dest='allow_system_installation',
action='store_true',
help='allow installation to the system flatpak '
'repo in general')
parser_set.add_argument('--disallow-system-installation',
dest='allow_system_installation',
action='store_false',
help='unconditionally disallow installation to '
'the system flatpak repo')
parser_set.add_argument('app_filter_args', nargs='*',
help='paths, content types or flatpak refs to '
'blacklist and OARS section=value '
'pairs to store')
parser_set.set_defaults(allow_user_installation=True,
allow_system_installation=False)
# set-app-filter command
parser_set_app_filter = \
subparsers.add_parser('set-app-filter', parents=[common_parser],
help='set current app filter settings')
parser_set_app_filter.set_defaults(function=command_set_app_filter)
parser_set_app_filter.add_argument('user', default='', nargs='?',
help='user ID or username to set the '
'app filter for (default: current '
'user)')
parser_set_app_filter.add_argument('--allow-user-installation',
dest='allow_user_installation',
action='store_true',
help='allow installation to the user '
'flatpak repo in general')
parser_set_app_filter.add_argument('--disallow-user-installation',
dest='allow_user_installation',
action='store_false',
help='unconditionally disallow '
'installation to the user flatpak '
'repo')
parser_set_app_filter.add_argument('--allow-system-installation',
dest='allow_system_installation',
action='store_true',
help='allow installation to the system '
'flatpak repo in general')
parser_set_app_filter.add_argument('--disallow-system-installation',
dest='allow_system_installation',
action='store_false',
help='unconditionally disallow '
'installation to the system '
'flatpak repo')
parser_set_app_filter.add_argument('app_filter_args', nargs='*',
help='paths, content types or flatpak '
'refs to blacklist and OARS '
'section=value pairs to store')
parser_set_app_filter.set_defaults(allow_user_installation=True,
allow_system_installation=False)
# Parse the command line arguments and run the subcommand.
args = parser.parse_args()

View File

@ -19,12 +19,19 @@ po_dir = join_paths(meson.source_root(), 'po')
prefix = get_option('prefix')
bindir = join_paths(prefix, get_option('bindir'))
datadir = join_paths(prefix, get_option('datadir'))
libdir = join_paths(prefix, get_option('libdir'))
libexecdir = join_paths(prefix, get_option('libexecdir'))
# FIXME: This isnt exposed in accountsservice.pc
# See https://gitlab.freedesktop.org/accountsservice/accountsservice/merge_requests/16
accountsserviceinterfacesdir = join_paths(datadir, 'accountsservice', 'interfaces')
# FIXME: pam.pc doesnt exist
pamlibdir = get_option('pamlibdir')
if pamlibdir == ''
pamlibdir = join_paths(prefix, libdir.split('/')[-1], 'security')
endif
dbus = dependency('dbus-1')
dbusinterfacesdir = dbus.get_pkgconfig_variable('interfaces_dir',
define_variable: ['datadir', datadir])
@ -120,4 +127,5 @@ test_env = [
subdir('accounts-service')
subdir('malcontent-client')
subdir('libmalcontent')
subdir('libmalcontent')
subdir('pam')

View File

@ -3,4 +3,9 @@ option(
type: 'boolean',
value: false,
description: 'enable installed tests'
)
)
option(
'pamlibdir',
type: 'string',
description: 'directory for PAM modules'
)

24
pam/meson.build Normal file
View File

@ -0,0 +1,24 @@
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)
subdir('tests')

206
pam/pam_malcontent.c Normal file
View File

@ -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:
*
* Heres 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 users 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 theres 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 users 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;
}

26
pam/pam_malcontent.sym Normal file
View File

@ -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: *;
};

48
pam/tests/meson.build Normal file
View File

@ -0,0 +1,48 @@
deps = [
dependency('glib-2.0', version: '>= 2.60.0'),
cc.find_library('dl'),
]
envs = test_env + [
'G_TEST_SRCDIR=' + meson.current_source_dir(),
'G_TEST_BUILDDIR=' + meson.current_build_dir(),
]
test_programs = [
['pam_malcontent', [], deps],
]
installed_tests_metadir = join_paths(datadir, 'installed-tests',
'libmalcontent-' + libmalcontent_api_version)
installed_tests_execdir = join_paths(libexecdir, 'installed-tests',
'libmalcontent-' + libmalcontent_api_version)
foreach program: test_programs
test_conf = configuration_data()
test_conf.set('installed_tests_dir', installed_tests_execdir)
test_conf.set('program', program[0])
configure_file(
input: test_template,
output: program[0] + '.test',
install: enable_installed_tests,
install_dir: installed_tests_metadir,
configuration: test_conf,
)
exe = executable(
program[0],
[program[0] + '.c'] + program[1],
dependencies: program[2],
include_directories: root_inc,
install: enable_installed_tests,
install_dir: installed_tests_execdir,
)
test(
program[0],
exe,
env: envs,
args: ['--tap'],
)
endforeach

View File

@ -0,0 +1,62 @@
/* -*- 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 <dlfcn.h>
#include <glib.h>
#include <locale.h>
#include <stdlib.h>
/* Test that the `pam_malcontent.so` module can be loaded using dlopen() and
* that it exports the appropriate symbols for PAM to be able to use it. */
static void
test_pam_malcontent_dlopen (void)
{
g_autofree gchar *module_path = NULL;
void *handle;
int retval;
void *fn;
module_path = g_test_build_filename (G_TEST_BUILT, "..", "pam_malcontent.so", NULL);
/* Check the module can be loaded. */
handle = dlopen (module_path, RTLD_NOW);
g_assert_nonnull (handle);
/* Check the appropriate symbols exist. */
fn = dlsym (handle, "pam_sm_acct_mgmt");
g_assert_nonnull (fn);
retval = dlclose (handle);
g_assert_cmpint (retval, ==, 0);
}
int
main (int argc,
char **argv)
{
setlocale (LC_ALL, "");
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/pam_malcontent/dlopen", test_pam_malcontent_dlopen);
return g_test_run ();
}

View File

@ -1,3 +1,5 @@
# List of source files containing translatable strings.
# Please keep this file sorted alphabetically.
accounts-service/com.endlessm.ParentalControls.policy
accounts-service/com.endlessm.ParentalControls.policy
libmalcontent/manager.c
pam/pam_malcontent.c