diff --git a/accounts-service/com.endlessm.ParentalControls.SessionLimits.xml b/accounts-service/com.endlessm.ParentalControls.SessionLimits.xml new file mode 100644 index 0000000..6dbba64 --- /dev/null +++ b/accounts-service/com.endlessm.ParentalControls.SessionLimits.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/accounts-service/com.endlessm.ParentalControls.policy.in b/accounts-service/com.endlessm.ParentalControls.policy.in index ff09f2d..bdeb76a 100644 --- a/accounts-service/com.endlessm.ParentalControls.policy.in +++ b/accounts-service/com.endlessm.ParentalControls.policy.in @@ -39,4 +39,44 @@ auth_admin_keep - \ No newline at end of file + + + Change your own session limits + Authentication is required to change your session limits. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + + Read your own session limits + Authentication is required to read your session limits. + + yes + yes + yes + + + + + Change another user’s session limits + Authentication is required to change another user’s session limits. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + + Read another user’s session limits + Authentication is required to read another user’s session limits. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + diff --git a/accounts-service/com.endlessm.ParentalControls.rules b/accounts-service/com.endlessm.ParentalControls.rules index e630bcf..b3bf998 100644 --- a/accounts-service/com.endlessm.ParentalControls.rules +++ b/accounts-service/com.endlessm.ParentalControls.rules @@ -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; diff --git a/accounts-service/meson.build b/accounts-service/meson.build index c2b61fe..0a304ae 100644 --- a/accounts-service/meson.build +++ b/accounts-service/meson.build @@ -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')) diff --git a/libmalcontent/malcontent.h b/libmalcontent/malcontent.h index d3f2865..2d523be 100644 --- a/libmalcontent/malcontent.h +++ b/libmalcontent/malcontent.h @@ -24,3 +24,4 @@ #include #include +#include diff --git a/libmalcontent/manager.c b/libmalcontent/manager.c index 1a1fc32..871223b 100644 --- a/libmalcontent/manager.c +++ b/libmalcontent/manager.c @@ -28,8 +28,10 @@ #include #include #include +#include #include "libmalcontent/app-filter-private.h" +#include "libmalcontent/session-limits-private.h" G_DEFINE_QUARK (MctManagerError, mct_manager_error) @@ -865,4 +867,240 @@ 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); -} \ No newline at end of file +} + +/** + * 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 doesn’t 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 don’t 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); +} diff --git a/libmalcontent/manager.h b/libmalcontent/manager.h index 2402b1a..641c577 100644 --- a/libmalcontent/manager.h +++ b/libmalcontent/manager.h @@ -97,6 +97,7 @@ GQuark mct_manager_error_quark (void); #define MCT_MANAGER_ERROR mct_manager_error_quark () #include +#include #define MCT_TYPE_MANAGER mct_manager_get_type () G_DECLARE_FINAL_TYPE (MctManager, mct_manager, MCT, MANAGER, GObject) @@ -135,4 +136,19 @@ 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); + G_END_DECLS diff --git a/libmalcontent/meson.build b/libmalcontent/meson.build index b06b086..e592f11 100644 --- a/libmalcontent/meson.build +++ b/libmalcontent/meson.build @@ -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 = [ diff --git a/libmalcontent/session-limits-private.h b/libmalcontent/session-limits-private.h new file mode 100644 index 0000000..88f6c84 --- /dev/null +++ b/libmalcontent/session-limits-private.h @@ -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 + */ + +#pragma once + +#include +#include +#include +#include + +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 diff --git a/libmalcontent/session-limits.c b/libmalcontent/session-limits.c new file mode 100644 index 0000000..de390fd --- /dev/null +++ b/libmalcontent/session-limits.c @@ -0,0 +1,191 @@ +/* -*- 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 + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#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 user’s 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; +} diff --git a/libmalcontent/session-limits.h b/libmalcontent/session-limits.h new file mode 100644 index 0000000..ba29ca0 --- /dev/null +++ b/libmalcontent/session-limits.h @@ -0,0 +1,59 @@ +/* -*- 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 + */ + +#pragma once + +#include +#include +#include + +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); + +G_END_DECLS