From 4f66a9549a393e4d74b93eb85301a04ea94bc750 Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Wed, 24 Jul 2019 15:55:17 +0800 Subject: [PATCH 01/16] Add duktape as javascript engine. Signed-off-by: Gustavo Lima Chaves --- configure.ac | 28 +- src/polkitbackend/Makefile.am | 14 +- .../polkitbackendduktapeauthority.c | 1402 +++++++++++++++++ 3 files changed, 1436 insertions(+), 8 deletions(-) create mode 100644 src/polkitbackend/polkitbackendduktapeauthority.c diff --git a/configure.ac b/configure.ac index e434ca2..5a03593 100644 --- a/configure.ac +++ b/configure.ac @@ -80,11 +80,22 @@ PKG_CHECK_MODULES(GLIB, [gmodule-2.0 gio-unix-2.0 >= 2.30.0]) AC_SUBST(GLIB_CFLAGS) AC_SUBST(GLIB_LIBS) -PKG_CHECK_MODULES(LIBJS, [mozjs-78]) - -AC_SUBST(LIBJS_CFLAGS) -AC_SUBST(LIBJS_CXXFLAGS) -AC_SUBST(LIBJS_LIBS) +dnl --------------------------------------------------------------------------- +dnl - Check javascript backend +dnl --------------------------------------------------------------------------- +AC_ARG_WITH(duktape, AS_HELP_STRING([--with-duktape],[Use Duktape as javascript backend]),with_duktape=yes,with_duktape=no) +AS_IF([test x${with_duktape} == xyes], [ + PKG_CHECK_MODULES(LIBJS, [duktape >= 2.0.0 ]) + AC_SUBST(LIBJS_CFLAGS) + AC_SUBST(LIBJS_LIBS) +], [ + PKG_CHECK_MODULES(LIBJS, [mozjs-78]) + + AC_SUBST(LIBJS_CFLAGS) + AC_SUBST(LIBJS_CXXFLAGS) + AC_SUBST(LIBJS_LIBS) +]) +AM_CONDITIONAL(USE_DUKTAPE, [test x$with_duktape == xyes], [Using duktape as javascript engine library]) EXPAT_LIB="" AC_ARG_WITH(expat, [ --with-expat= Use expat from here], @@ -585,6 +596,13 @@ echo " PAM support: ${have_pam} systemdsystemunitdir: ${systemdsystemunitdir} polkitd user: ${POLKITD_USER}" +if test "x${with_duktape}" = xyes; then +echo " + Javascript engine: Duktape" +else +echo " + Javascript engine: Mozjs" +fi if test "$have_pam" = yes ; then echo " diff --git a/src/polkitbackend/Makefile.am b/src/polkitbackend/Makefile.am index 7e3c080..abcbc6f 100644 --- a/src/polkitbackend/Makefile.am +++ b/src/polkitbackend/Makefile.am @@ -33,7 +33,7 @@ libpolkit_backend_1_la_SOURCES = \ polkitbackendprivate.h \ polkitbackendauthority.h polkitbackendauthority.c \ polkitbackendinteractiveauthority.h polkitbackendinteractiveauthority.c \ - polkitbackendjsauthority.h polkitbackendjsauthority.cpp \ + polkitbackendjsauthority.h \ polkitbackendactionpool.h polkitbackendactionpool.c \ polkitbackendactionlookup.h polkitbackendactionlookup.c \ $(NULL) @@ -51,19 +51,27 @@ libpolkit_backend_1_la_CFLAGS = \ -D_POLKIT_BACKEND_COMPILATION \ $(GLIB_CFLAGS) \ $(LIBSYSTEMD_CFLAGS) \ - $(LIBJS_CFLAGS) \ + $(LIBJS_CFLAGS) \ $(NULL) libpolkit_backend_1_la_CXXFLAGS = $(libpolkit_backend_1_la_CFLAGS) libpolkit_backend_1_la_LIBADD = \ $(GLIB_LIBS) \ + $(DUKTAPE_LIBS) \ $(LIBSYSTEMD_LIBS) \ $(top_builddir)/src/polkit/libpolkit-gobject-1.la \ $(EXPAT_LIBS) \ - $(LIBJS_LIBS) \ + $(LIBJS_LIBS) \ $(NULL) +if USE_DUKTAPE +libpolkit_backend_1_la_SOURCES += polkitbackendduktapeauthority.c +libpolkit_backend_1_la_LIBADD += -lm +else +libpolkit_backend_1_la_SOURCES += polkitbackendjsauthority.cpp +endif + rulesdir = $(sysconfdir)/polkit-1/rules.d rules_DATA = 50-default.rules diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c new file mode 100644 index 0000000..ae98453 --- /dev/null +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -0,0 +1,1402 @@ +/* + * Copyright (C) 2008-2012 Red Hat, Inc. + * Copyright (C) 2015 Tangent Space + * Copyright (C) 2019 Wu Xiaotian + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "polkitbackendjsauthority.h" + +#include + +#ifdef HAVE_LIBSYSTEMD +#include +#endif /* HAVE_LIBSYSTEMD */ + +#include "initjs.h" /* init.js */ +#include "duktape.h" + +/** + * SECTION:polkitbackendjsauthority + * @title: PolkitBackendJsAuthority + * @short_description: JS Authority + * @stability: Unstable + * + * An implementation of #PolkitBackendAuthority that reads and + * evalates Javascript files and supports interaction with + * authentication agents (virtue of being based on + * #PolkitBackendInteractiveAuthority). + */ + +/* ---------------------------------------------------------------------------------------------------- */ + +struct _PolkitBackendJsAuthorityPrivate +{ + gchar **rules_dirs; + GFileMonitor **dir_monitors; /* NULL-terminated array of GFileMonitor instances */ + duk_context *cx; +}; + +#define WATCHDOG_TIMEOUT (15 * G_TIME_SPAN_SECOND) + +static void utils_spawn (const gchar *const *argv, + guint timeout_seconds, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean utils_spawn_finish (GAsyncResult *res, + gint *out_exit_status, + gchar **out_standard_output, + gchar **out_standard_error, + GError **error); + +static void on_dir_monitor_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data); + +/* ---------------------------------------------------------------------------------------------------- */ + +enum +{ + PROP_0, + PROP_RULES_DIRS, +}; + +/* ---------------------------------------------------------------------------------------------------- */ + +static GList *polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *authority, + PolkitSubject *caller, + PolkitSubject *subject, + PolkitIdentity *user_for_subject, + gboolean subject_is_local, + gboolean subject_is_active, + const gchar *action_id, + PolkitDetails *details); + +static PolkitImplicitAuthorization polkit_backend_js_authority_check_authorization_sync ( + PolkitBackendInteractiveAuthority *authority, + PolkitSubject *caller, + PolkitSubject *subject, + PolkitIdentity *user_for_subject, + gboolean subject_is_local, + gboolean subject_is_active, + const gchar *action_id, + PolkitDetails *details, + PolkitImplicitAuthorization implicit); + +G_DEFINE_TYPE (PolkitBackendJsAuthority, polkit_backend_js_authority, POLKIT_BACKEND_TYPE_INTERACTIVE_AUTHORITY); + +/* ---------------------------------------------------------------------------------------------------- */ + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +polkit_backend_js_authority_init (PolkitBackendJsAuthority *authority) +{ + authority->priv = G_TYPE_INSTANCE_GET_PRIVATE (authority, + POLKIT_BACKEND_TYPE_JS_AUTHORITY, + PolkitBackendJsAuthorityPrivate); +} + +static gint +rules_file_name_cmp (const gchar *a, + const gchar *b) +{ + gint ret; + const gchar *a_base; + const gchar *b_base; + + a_base = strrchr (a, '/'); + b_base = strrchr (b, '/'); + + g_assert (a_base != NULL); + g_assert (b_base != NULL); + a_base += 1; + b_base += 1; + + ret = g_strcmp0 (a_base, b_base); + if (ret == 0) + { + /* /etc wins over /usr */ + ret = g_strcmp0 (a, b); + g_assert (ret != 0); + } + + return ret; +} + +static void +load_scripts (PolkitBackendJsAuthority *authority) +{ + duk_context *cx = authority->priv->cx; + GList *files = NULL; + GList *l; + guint num_scripts = 0; + GError *error = NULL; + guint n; + + files = NULL; + + for (n = 0; authority->priv->rules_dirs != NULL && authority->priv->rules_dirs[n] != NULL; n++) + { + const gchar *dir_name = authority->priv->rules_dirs[n]; + GDir *dir = NULL; + + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Loading rules from directory %s", + dir_name); + + dir = g_dir_open (dir_name, + 0, + &error); + if (dir == NULL) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error opening rules directory: %s (%s, %d)", + error->message, g_quark_to_string (error->domain), error->code); + g_clear_error (&error); + } + else + { + const gchar *name; + while ((name = g_dir_read_name (dir)) != NULL) + { + if (g_str_has_suffix (name, ".rules")) + files = g_list_prepend (files, g_strdup_printf ("%s/%s", dir_name, name)); + } + g_dir_close (dir); + } + } + + files = g_list_sort (files, (GCompareFunc) rules_file_name_cmp); + + for (l = files; l != NULL; l = l->next) + { + const gchar *filename = l->data; + +#if (DUK_VERSION >= 20000) + gchar *contents; + gsize length; + GError *error = NULL; + if (!g_file_get_contents (filename, &contents, &length, &error)){ + g_warning("Error when file contents of %s: %s\n", filename, error->message); + g_error_free (error); + continue; + } + if (duk_peval_lstring_noresult(cx, contents,length) != 0) +#else + if (duk_peval_file_noresult (cx, filename) != 0) +#endif + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error compiling script %s: %s", + filename, duk_safe_to_string (authority->priv->cx, -1)); +#if (DUK_VERSION >= 20000) + g_free (contents); +#endif + continue; + } +#if (DUK_VERSION >= 20000) + g_free (contents); +#endif + num_scripts++; + } + + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Finished loading, compiling and executing %d rules", + num_scripts); + g_list_free_full (files, g_free); +} + +static void +reload_scripts (PolkitBackendJsAuthority *authority) +{ + duk_context *cx = authority->priv->cx; + + duk_set_top (cx, 0); + duk_get_global_string (cx, "polkit"); + duk_push_string (cx, "_deleteRules"); + + duk_call_prop (cx, 0, 0); + + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Collecting garbage unconditionally..."); + + load_scripts (authority); + + /* Let applications know we have new rules... */ + g_signal_emit_by_name (authority, "changed"); +} + +static void +on_dir_monitor_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (user_data); + + /* TODO: maybe rate-limit so storms of events are collapsed into one with a 500ms resolution? + * Because when editing a file with emacs we get 4-8 events.. + */ + + if (file != NULL) + { + gchar *name; + + name = g_file_get_basename (file); + + /* g_print ("event_type=%d file=%p name=%s\n", event_type, file, name); */ + if (!g_str_has_prefix (name, ".") && + !g_str_has_prefix (name, "#") && + g_str_has_suffix (name, ".rules") && + (event_type == G_FILE_MONITOR_EVENT_CREATED || + event_type == G_FILE_MONITOR_EVENT_DELETED || + event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Reloading rules"); + reload_scripts (authority); + } + g_free (name); + } +} + + +static void +setup_file_monitors (PolkitBackendJsAuthority *authority) +{ + guint n; + GPtrArray *p; + + p = g_ptr_array_new (); + for (n = 0; authority->priv->rules_dirs != NULL && authority->priv->rules_dirs[n] != NULL; n++) + { + GFile *file; + GError *error; + GFileMonitor *monitor; + + file = g_file_new_for_path (authority->priv->rules_dirs[n]); + error = NULL; + monitor = g_file_monitor_directory (file, + G_FILE_MONITOR_NONE, + NULL, + &error); + g_object_unref (file); + if (monitor == NULL) + { + g_warning ("Error monitoring directory %s: %s", + authority->priv->rules_dirs[n], + error->message); + g_clear_error (&error); + } + else + { + g_signal_connect (monitor, + "changed", + G_CALLBACK (on_dir_monitor_changed), + authority); + g_ptr_array_add (p, monitor); + } + } + g_ptr_array_add (p, NULL); + authority->priv->dir_monitors = (GFileMonitor**) g_ptr_array_free (p, FALSE); +} + +static duk_ret_t js_polkit_log (duk_context *cx); +static duk_ret_t js_polkit_spawn (duk_context *cx); +static duk_ret_t js_polkit_user_is_in_netgroup (duk_context *cx); + +static const duk_function_list_entry js_polkit_functions[] = +{ + { "log", js_polkit_log, 1 }, + { "spawn", js_polkit_spawn, 1 }, + { "_userIsInNetGroup", js_polkit_user_is_in_netgroup, 2 }, + { NULL, NULL, 0 }, +}; + +static void +polkit_backend_js_authority_constructed (GObject *object) +{ + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); + duk_context *cx; + + cx = duk_create_heap (NULL, NULL, NULL, authority, NULL); + if (cx == NULL) + goto fail; + + authority->priv->cx = cx; + + duk_push_global_object (cx); + duk_push_object (cx); + duk_put_function_list (cx, -1, js_polkit_functions); + duk_put_prop_string (cx, -2, "polkit"); + + duk_eval_string (cx, init_js); + + if (authority->priv->rules_dirs == NULL) + { + authority->priv->rules_dirs = g_new0 (gchar *, 3); + authority->priv->rules_dirs[0] = g_strdup (PACKAGE_SYSCONF_DIR "/polkit-1/rules.d"); + authority->priv->rules_dirs[1] = g_strdup (PACKAGE_DATA_DIR "/polkit-1/rules.d"); + } + + setup_file_monitors (authority); + load_scripts (authority); + + G_OBJECT_CLASS (polkit_backend_js_authority_parent_class)->constructed (object); + return; + + fail: + g_critical ("Error initializing JavaScript environment"); + g_assert_not_reached (); +} + +static void +polkit_backend_js_authority_finalize (GObject *object) +{ + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); + guint n; + + for (n = 0; authority->priv->dir_monitors != NULL && authority->priv->dir_monitors[n] != NULL; n++) + { + GFileMonitor *monitor = authority->priv->dir_monitors[n]; + g_signal_handlers_disconnect_by_func (monitor, + G_CALLBACK (on_dir_monitor_changed), + authority); + g_object_unref (monitor); + } + g_free (authority->priv->dir_monitors); + g_strfreev (authority->priv->rules_dirs); + + duk_destroy_heap (authority->priv->cx); + + G_OBJECT_CLASS (polkit_backend_js_authority_parent_class)->finalize (object); +} + +static void +polkit_backend_js_authority_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); + + switch (property_id) + { + case PROP_RULES_DIRS: + g_assert (authority->priv->rules_dirs == NULL); + authority->priv->rules_dirs = (gchar **) g_value_dup_boxed (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static const gchar * +polkit_backend_js_authority_get_name (PolkitBackendAuthority *authority) +{ + return "js"; +} + +static const gchar * +polkit_backend_js_authority_get_version (PolkitBackendAuthority *authority) +{ + return PACKAGE_VERSION; +} + +static PolkitAuthorityFeatures +polkit_backend_js_authority_get_features (PolkitBackendAuthority *authority) +{ + return POLKIT_AUTHORITY_FEATURES_TEMPORARY_AUTHORIZATION; +} + +static void +polkit_backend_js_authority_class_init (PolkitBackendJsAuthorityClass *klass) +{ + GObjectClass *gobject_class; + PolkitBackendAuthorityClass *authority_class; + PolkitBackendInteractiveAuthorityClass *interactive_authority_class; + + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = polkit_backend_js_authority_finalize; + gobject_class->set_property = polkit_backend_js_authority_set_property; + gobject_class->constructed = polkit_backend_js_authority_constructed; + + authority_class = POLKIT_BACKEND_AUTHORITY_CLASS (klass); + authority_class->get_name = polkit_backend_js_authority_get_name; + authority_class->get_version = polkit_backend_js_authority_get_version; + authority_class->get_features = polkit_backend_js_authority_get_features; + + interactive_authority_class = POLKIT_BACKEND_INTERACTIVE_AUTHORITY_CLASS (klass); + interactive_authority_class->get_admin_identities = polkit_backend_js_authority_get_admin_auth_identities; + interactive_authority_class->check_authorization_sync = polkit_backend_js_authority_check_authorization_sync; + + g_object_class_install_property (gobject_class, + PROP_RULES_DIRS, + g_param_spec_boxed ("rules-dirs", + NULL, + NULL, + G_TYPE_STRV, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); + + + g_type_class_add_private (klass, sizeof (PolkitBackendJsAuthorityPrivate)); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +set_property_str (duk_context *cx, + const gchar *name, + const gchar *value) +{ + duk_push_string (cx, value); + duk_put_prop_string (cx, -2, name); +} + +static void +set_property_strv (duk_context *cx, + const gchar *name, + GPtrArray *value) +{ + guint n; + duk_push_array (cx); + for (n = 0; n < value->len; n++) + { + duk_push_string (cx, g_ptr_array_index (value, n)); + duk_put_prop_index (cx, -2, n); + } + duk_put_prop_string (cx, -2, name); +} + +static void +set_property_int32 (duk_context *cx, + const gchar *name, + gint32 value) +{ + duk_push_int (cx, value); + duk_put_prop_string (cx, -2, name); +} + +static void +set_property_bool (duk_context *cx, + const char *name, + gboolean value) +{ + duk_push_boolean (cx, value); + duk_put_prop_string (cx, -2, name); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +push_subject (duk_context *cx, + PolkitSubject *subject, + PolkitIdentity *user_for_subject, + gboolean subject_is_local, + gboolean subject_is_active, + GError **error) +{ + gboolean ret = FALSE; + pid_t pid; + uid_t uid; + gchar *user_name = NULL; + GPtrArray *groups = NULL; + struct passwd *passwd; + char *seat_str = NULL; + char *session_str = NULL; + + duk_get_global_string (cx, "Subject"); + duk_new (cx, 0); + + if (POLKIT_IS_UNIX_PROCESS (subject)) + { + pid = polkit_unix_process_get_pid (POLKIT_UNIX_PROCESS (subject)); + } + else if (POLKIT_IS_SYSTEM_BUS_NAME (subject)) + { + PolkitSubject *process; + process = polkit_system_bus_name_get_process_sync (POLKIT_SYSTEM_BUS_NAME (subject), NULL, error); + if (process == NULL) + goto out; + pid = polkit_unix_process_get_pid (POLKIT_UNIX_PROCESS (process)); + g_object_unref (process); + } + else + { + g_assert_not_reached (); + } + +#ifdef HAVE_LIBSYSTEMD + if (sd_pid_get_session (pid, &session_str) == 0) + { + if (sd_session_get_seat (session_str, &seat_str) == 0) + { + /* do nothing */ + } + } +#endif /* HAVE_LIBSYSTEMD */ + + g_assert (POLKIT_IS_UNIX_USER (user_for_subject)); + uid = polkit_unix_user_get_uid (POLKIT_UNIX_USER (user_for_subject)); + + groups = g_ptr_array_new_with_free_func (g_free); + + passwd = getpwuid (uid); + if (passwd == NULL) + { + user_name = g_strdup_printf ("%d", (gint) uid); + g_warning ("Error looking up info for uid %d: %m", (gint) uid); + } + else + { + gid_t gids[512]; + int num_gids = 512; + + user_name = g_strdup (passwd->pw_name); + + if (getgrouplist (passwd->pw_name, + passwd->pw_gid, + gids, + &num_gids) < 0) + { + g_warning ("Error looking up groups for uid %d: %m", (gint) uid); + } + else + { + gint n; + for (n = 0; n < num_gids; n++) + { + struct group *group; + group = getgrgid (gids[n]); + if (group == NULL) + { + g_ptr_array_add (groups, g_strdup_printf ("%d", (gint) gids[n])); + } + else + { + g_ptr_array_add (groups, g_strdup (group->gr_name)); + } + } + } + } + + set_property_int32 (cx, "pid", pid); + set_property_str (cx, "user", user_name); + set_property_strv (cx, "groups", groups); + set_property_str (cx, "seat", seat_str); + set_property_str (cx, "session", session_str); + set_property_bool (cx, "local", subject_is_local); + set_property_bool (cx, "active", subject_is_active); + + ret = TRUE; + + out: + free (session_str); + free (seat_str); + g_free (user_name); + if (groups != NULL) + g_ptr_array_unref (groups); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +push_action_and_details (duk_context *cx, + const gchar *action_id, + PolkitDetails *details, + GError **error) +{ + gchar **keys; + guint n; + + duk_get_global_string (cx, "Action"); + duk_new (cx, 0); + + set_property_str (cx, "id", action_id); + + keys = polkit_details_get_keys (details); + for (n = 0; keys != NULL && keys[n] != NULL; n++) + { + gchar *key; + const gchar *value; + key = g_strdup_printf ("_detail_%s", keys[n]); + value = polkit_details_lookup (details, keys[n]); + set_property_str (cx, key, value); + g_free (key); + } + g_strfreev (keys); + + return TRUE; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* ---------------------------------------------------------------------------------------------------- */ + +static GList * +polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *_authority, + PolkitSubject *caller, + PolkitSubject *subject, + PolkitIdentity *user_for_subject, + gboolean subject_is_local, + gboolean subject_is_active, + const gchar *action_id, + PolkitDetails *details) +{ + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (_authority); + GList *ret = NULL; + guint n; + GError *error = NULL; + const char *ret_str = NULL; + gchar **ret_strs = NULL; + duk_context *cx = authority->priv->cx; + + duk_set_top (cx, 0); + duk_get_global_string (cx, "polkit"); + duk_push_string (cx, "_runAdminRules"); + + if (!push_action_and_details (cx, action_id, details, &error)) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error converting action and details to JS object: %s", + error->message); + g_clear_error (&error); + goto out; + } + + if (!push_subject (cx, subject, user_for_subject, subject_is_local, subject_is_active, &error)) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error converting subject to JS object: %s", + error->message); + g_clear_error (&error); + goto out; + } + + if (duk_pcall_prop (cx, 0, 2) != DUK_ERR_NONE) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error evaluating admin rules: ", + duk_safe_to_string (cx, -1)); + goto out; + } + + ret_str = duk_require_string (cx, -1); + + ret_strs = g_strsplit (ret_str, ",", -1); + for (n = 0; ret_strs != NULL && ret_strs[n] != NULL; n++) + { + const gchar *identity_str = ret_strs[n]; + PolkitIdentity *identity; + + error = NULL; + identity = polkit_identity_from_string (identity_str, &error); + if (identity == NULL) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Identity `%s' is not valid, ignoring: %s", + identity_str, error->message); + g_clear_error (&error); + } + else + { + ret = g_list_prepend (ret, identity); + } + } + ret = g_list_reverse (ret); + + out: + g_strfreev (ret_strs); + /* fallback to root password auth */ + if (ret == NULL) + ret = g_list_prepend (ret, polkit_unix_user_new (0)); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static PolkitImplicitAuthorization +polkit_backend_js_authority_check_authorization_sync (PolkitBackendInteractiveAuthority *_authority, + PolkitSubject *caller, + PolkitSubject *subject, + PolkitIdentity *user_for_subject, + gboolean subject_is_local, + gboolean subject_is_active, + const gchar *action_id, + PolkitDetails *details, + PolkitImplicitAuthorization implicit) +{ + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (_authority); + PolkitImplicitAuthorization ret = implicit; + GError *error = NULL; + gchar *ret_str = NULL; + gboolean good = FALSE; + duk_context *cx = authority->priv->cx; + + duk_set_top (cx, 0); + duk_get_global_string (cx, "polkit"); + duk_push_string (cx, "_runRules"); + + if (!push_action_and_details (cx, action_id, details, &error)) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error converting action and details to JS object: %s", + error->message); + g_clear_error (&error); + goto out; + } + + if (!push_subject (cx, subject, user_for_subject, subject_is_local, subject_is_active, &error)) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error converting subject to JS object: %s", + error->message); + g_clear_error (&error); + goto out; + } + + if (duk_pcall_prop (cx, 0, 2) != DUK_ERR_NONE) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error evaluating authorization rules: ", + duk_safe_to_string (cx, -1)); + goto out; + } + + if (duk_is_null(cx, -1)) { + good = TRUE; + goto out; + } + ret_str = g_strdup (duk_require_string (cx, -1)); + if (!polkit_implicit_authorization_from_string (ret_str, &ret)) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Returned result `%s' is not valid", + ret_str); + goto out; + } + + good = TRUE; + + out: + if (!good) + ret = POLKIT_IMPLICIT_AUTHORIZATION_NOT_AUTHORIZED; + g_free (ret_str); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static duk_ret_t +js_polkit_log (duk_context *cx) +{ + const char *str = duk_require_string (cx, 0); + fprintf (stderr, "%s\n", str); + return 0; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static const gchar * +get_signal_name (gint signal_number) +{ + switch (signal_number) + { +#define _HANDLE_SIG(sig) case sig: return #sig; + _HANDLE_SIG (SIGHUP); + _HANDLE_SIG (SIGINT); + _HANDLE_SIG (SIGQUIT); + _HANDLE_SIG (SIGILL); + _HANDLE_SIG (SIGABRT); + _HANDLE_SIG (SIGFPE); + _HANDLE_SIG (SIGKILL); + _HANDLE_SIG (SIGSEGV); + _HANDLE_SIG (SIGPIPE); + _HANDLE_SIG (SIGALRM); + _HANDLE_SIG (SIGTERM); + _HANDLE_SIG (SIGUSR1); + _HANDLE_SIG (SIGUSR2); + _HANDLE_SIG (SIGCHLD); + _HANDLE_SIG (SIGCONT); + _HANDLE_SIG (SIGSTOP); + _HANDLE_SIG (SIGTSTP); + _HANDLE_SIG (SIGTTIN); + _HANDLE_SIG (SIGTTOU); + _HANDLE_SIG (SIGBUS); +#ifdef SIGPOLL + _HANDLE_SIG (SIGPOLL); +#endif + _HANDLE_SIG (SIGPROF); + _HANDLE_SIG (SIGSYS); + _HANDLE_SIG (SIGTRAP); + _HANDLE_SIG (SIGURG); + _HANDLE_SIG (SIGVTALRM); + _HANDLE_SIG (SIGXCPU); + _HANDLE_SIG (SIGXFSZ); +#undef _HANDLE_SIG + default: + break; + } + return "UNKNOWN_SIGNAL"; +} + +typedef struct +{ + GMainLoop *loop; + GAsyncResult *res; +} SpawnData; + +static void +spawn_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SpawnData *data = user_data; + data->res = g_object_ref (res); + g_main_loop_quit (data->loop); +} + +static duk_ret_t +js_polkit_spawn (duk_context *cx) +{ +#if (DUK_VERSION >= 20000) + duk_ret_t ret = DUK_RET_ERROR; +#else + duk_ret_t ret = DUK_RET_INTERNAL_ERROR; +#endif + gchar *standard_output = NULL; + gchar *standard_error = NULL; + gint exit_status; + GError *error = NULL; + guint32 array_len; + gchar **argv = NULL; + GMainContext *context = NULL; + GMainLoop *loop = NULL; + SpawnData data = {0}; + char *err_str = NULL; + guint n; + + if (!duk_is_array (cx, 0)) + goto out; + + array_len = duk_get_length (cx, 0); + + argv = g_new0 (gchar*, array_len + 1); + for (n = 0; n < array_len; n++) + { + duk_get_prop_index (cx, 0, n); + argv[n] = g_strdup (duk_to_string (cx, -1)); + duk_pop (cx); + } + + context = g_main_context_new (); + loop = g_main_loop_new (context, FALSE); + + g_main_context_push_thread_default (context); + + data.loop = loop; + utils_spawn ((const gchar *const *) argv, + 10, /* timeout_seconds */ + NULL, /* cancellable */ + spawn_cb, + &data); + + g_main_loop_run (loop); + + g_main_context_pop_thread_default (context); + + if (!utils_spawn_finish (data.res, + &exit_status, + &standard_output, + &standard_error, + &error)) + { + err_str = g_strdup_printf ("Error spawning helper: %s (%s, %d)", + error->message, g_quark_to_string (error->domain), error->code); + g_clear_error (&error); + goto out; + } + + if (!(WIFEXITED (exit_status) && WEXITSTATUS (exit_status) == 0)) + { + GString *gstr; + gstr = g_string_new (NULL); + if (WIFEXITED (exit_status)) + { + g_string_append_printf (gstr, + "Helper exited with non-zero exit status %d", + WEXITSTATUS (exit_status)); + } + else if (WIFSIGNALED (exit_status)) + { + g_string_append_printf (gstr, + "Helper was signaled with signal %s (%d)", + get_signal_name (WTERMSIG (exit_status)), + WTERMSIG (exit_status)); + } + g_string_append_printf (gstr, ", stdout=`%s', stderr=`%s'", + standard_output, standard_error); + err_str = g_string_free (gstr, FALSE); + goto out; + } + + duk_push_string (cx, standard_output); + ret = 1; + + out: + g_strfreev (argv); + g_free (standard_output); + g_free (standard_error); + g_clear_object (&data.res); + if (loop != NULL) + g_main_loop_unref (loop); + if (context != NULL) + g_main_context_unref (context); + + if (err_str) + duk_error (cx, DUK_ERR_ERROR, err_str); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + + +static duk_ret_t +js_polkit_user_is_in_netgroup (duk_context *cx) +{ + const char *user; + const char *netgroup; + gboolean is_in_netgroup = FALSE; + + user = duk_require_string (cx, 0); + netgroup = duk_require_string (cx, 1); + + if (innetgr (netgroup, + NULL, /* host */ + user, + NULL)) /* domain */ + { + is_in_netgroup = TRUE; + } + + duk_push_boolean (cx, is_in_netgroup); + return 1; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + GSimpleAsyncResult *simple; /* borrowed reference */ + GMainContext *main_context; /* may be NULL */ + + GCancellable *cancellable; /* may be NULL */ + gulong cancellable_handler_id; + + GPid child_pid; + gint child_stdout_fd; + gint child_stderr_fd; + + GIOChannel *child_stdout_channel; + GIOChannel *child_stderr_channel; + + GSource *child_watch_source; + GSource *child_stdout_source; + GSource *child_stderr_source; + + guint timeout_seconds; + gboolean timed_out; + GSource *timeout_source; + + GString *child_stdout; + GString *child_stderr; + + gint exit_status; +} UtilsSpawnData; + +static void +utils_child_watch_from_release_cb (GPid pid, + gint status, + gpointer user_data) +{ +} + +static void +utils_spawn_data_free (UtilsSpawnData *data) +{ + if (data->timeout_source != NULL) + { + g_source_destroy (data->timeout_source); + data->timeout_source = NULL; + } + + /* Nuke the child, if necessary */ + if (data->child_watch_source != NULL) + { + g_source_destroy (data->child_watch_source); + data->child_watch_source = NULL; + } + + if (data->child_pid != 0) + { + GSource *source; + kill (data->child_pid, SIGTERM); + /* OK, we need to reap for the child ourselves - we don't want + * to use waitpid() because that might block the calling + * thread (the child might handle SIGTERM and use several + * seconds for cleanup/rollback). + * + * So we use GChildWatch instead. + * + * Avoid taking a references to ourselves. but note that we need + * to pass the GSource so we can nuke it once handled. + */ + source = g_child_watch_source_new (data->child_pid); + g_source_set_callback (source, + (GSourceFunc) utils_child_watch_from_release_cb, + source, + (GDestroyNotify) g_source_destroy); + g_source_attach (source, data->main_context); + g_source_unref (source); + data->child_pid = 0; + } + + if (data->child_stdout != NULL) + { + g_string_free (data->child_stdout, TRUE); + data->child_stdout = NULL; + } + + if (data->child_stderr != NULL) + { + g_string_free (data->child_stderr, TRUE); + data->child_stderr = NULL; + } + + if (data->child_stdout_channel != NULL) + { + g_io_channel_unref (data->child_stdout_channel); + data->child_stdout_channel = NULL; + } + if (data->child_stderr_channel != NULL) + { + g_io_channel_unref (data->child_stderr_channel); + data->child_stderr_channel = NULL; + } + + if (data->child_stdout_source != NULL) + { + g_source_destroy (data->child_stdout_source); + data->child_stdout_source = NULL; + } + if (data->child_stderr_source != NULL) + { + g_source_destroy (data->child_stderr_source); + data->child_stderr_source = NULL; + } + + if (data->child_stdout_fd != -1) + { + g_warn_if_fail (close (data->child_stdout_fd) == 0); + data->child_stdout_fd = -1; + } + if (data->child_stderr_fd != -1) + { + g_warn_if_fail (close (data->child_stderr_fd) == 0); + data->child_stderr_fd = -1; + } + + if (data->cancellable_handler_id > 0) + { + g_cancellable_disconnect (data->cancellable, data->cancellable_handler_id); + data->cancellable_handler_id = 0; + } + + if (data->main_context != NULL) + g_main_context_unref (data->main_context); + + if (data->cancellable != NULL) + g_object_unref (data->cancellable); + + g_slice_free (UtilsSpawnData, data); +} + +/* called in the thread where @cancellable was cancelled */ +static void +utils_on_cancelled (GCancellable *cancellable, + gpointer user_data) +{ + UtilsSpawnData *data = user_data; + GError *error; + + error = NULL; + g_warn_if_fail (g_cancellable_set_error_if_cancelled (cancellable, &error)); + g_simple_async_result_take_error (data->simple, error); + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); +} + +static gboolean +utils_read_child_stderr (GIOChannel *channel, + GIOCondition condition, + gpointer user_data) +{ + UtilsSpawnData *data = user_data; + gchar buf[1024]; + gsize bytes_read; + + g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); + g_string_append_len (data->child_stderr, buf, bytes_read); + return TRUE; +} + +static gboolean +utils_read_child_stdout (GIOChannel *channel, + GIOCondition condition, + gpointer user_data) +{ + UtilsSpawnData *data = user_data; + gchar buf[1024]; + gsize bytes_read; + + g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); + g_string_append_len (data->child_stdout, buf, bytes_read); + return TRUE; +} + +static void +utils_child_watch_cb (GPid pid, + gint status, + gpointer user_data) +{ + UtilsSpawnData *data = user_data; + gchar *buf; + gsize buf_size; + + if (g_io_channel_read_to_end (data->child_stdout_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) + { + g_string_append_len (data->child_stdout, buf, buf_size); + g_free (buf); + } + if (g_io_channel_read_to_end (data->child_stderr_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) + { + g_string_append_len (data->child_stderr, buf, buf_size); + g_free (buf); + } + + data->exit_status = status; + + /* ok, child watch is history, make sure we don't free it in spawn_data_free() */ + data->child_pid = 0; + data->child_watch_source = NULL; + + /* we're done */ + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); +} + +static gboolean +utils_timeout_cb (gpointer user_data) +{ + UtilsSpawnData *data = user_data; + + data->timed_out = TRUE; + + /* ok, timeout is history, make sure we don't free it in spawn_data_free() */ + data->timeout_source = NULL; + + /* we're done */ + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); + + return FALSE; /* remove source */ +} + +static void +utils_spawn (const gchar *const *argv, + guint timeout_seconds, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + UtilsSpawnData *data; + GError *error; + + data = g_slice_new0 (UtilsSpawnData); + data->timeout_seconds = timeout_seconds; + data->simple = g_simple_async_result_new (NULL, + callback, + user_data, + utils_spawn); + data->main_context = g_main_context_get_thread_default (); + if (data->main_context != NULL) + g_main_context_ref (data->main_context); + + data->cancellable = cancellable != NULL ? g_object_ref (cancellable) : NULL; + + data->child_stdout = g_string_new (NULL); + data->child_stderr = g_string_new (NULL); + data->child_stdout_fd = -1; + data->child_stderr_fd = -1; + + /* the life-cycle of UtilsSpawnData is tied to its GSimpleAsyncResult */ + g_simple_async_result_set_op_res_gpointer (data->simple, data, (GDestroyNotify) utils_spawn_data_free); + + error = NULL; + if (data->cancellable != NULL) + { + /* could already be cancelled */ + error = NULL; + if (g_cancellable_set_error_if_cancelled (data->cancellable, &error)) + { + g_simple_async_result_take_error (data->simple, error); + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); + goto out; + } + + data->cancellable_handler_id = g_cancellable_connect (data->cancellable, + G_CALLBACK (utils_on_cancelled), + data, + NULL); + } + + error = NULL; + if (!g_spawn_async_with_pipes (NULL, /* working directory */ + (gchar **) argv, + NULL, /* envp */ + G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, + NULL, /* child_setup */ + NULL, /* child_setup's user_data */ + &(data->child_pid), + NULL, /* gint *stdin_fd */ + &(data->child_stdout_fd), + &(data->child_stderr_fd), + &error)) + { + g_prefix_error (&error, "Error spawning: "); + g_simple_async_result_take_error (data->simple, error); + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); + goto out; + } + + if (timeout_seconds > 0) + { + data->timeout_source = g_timeout_source_new_seconds (timeout_seconds); + g_source_set_priority (data->timeout_source, G_PRIORITY_DEFAULT); + g_source_set_callback (data->timeout_source, utils_timeout_cb, data, NULL); + g_source_attach (data->timeout_source, data->main_context); + g_source_unref (data->timeout_source); + } + + data->child_watch_source = g_child_watch_source_new (data->child_pid); + g_source_set_callback (data->child_watch_source, (GSourceFunc) utils_child_watch_cb, data, NULL); + g_source_attach (data->child_watch_source, data->main_context); + g_source_unref (data->child_watch_source); + + data->child_stdout_channel = g_io_channel_unix_new (data->child_stdout_fd); + g_io_channel_set_flags (data->child_stdout_channel, G_IO_FLAG_NONBLOCK, NULL); + data->child_stdout_source = g_io_create_watch (data->child_stdout_channel, G_IO_IN); + g_source_set_callback (data->child_stdout_source, (GSourceFunc) utils_read_child_stdout, data, NULL); + g_source_attach (data->child_stdout_source, data->main_context); + g_source_unref (data->child_stdout_source); + + data->child_stderr_channel = g_io_channel_unix_new (data->child_stderr_fd); + g_io_channel_set_flags (data->child_stderr_channel, G_IO_FLAG_NONBLOCK, NULL); + data->child_stderr_source = g_io_create_watch (data->child_stderr_channel, G_IO_IN); + g_source_set_callback (data->child_stderr_source, (GSourceFunc) utils_read_child_stderr, data, NULL); + g_source_attach (data->child_stderr_source, data->main_context); + g_source_unref (data->child_stderr_source); + + out: + ; +} + +gboolean +utils_spawn_finish (GAsyncResult *res, + gint *out_exit_status, + gchar **out_standard_output, + gchar **out_standard_error, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + UtilsSpawnData *data; + gboolean ret = FALSE; + + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == utils_spawn); + + if (g_simple_async_result_propagate_error (simple, error)) + goto out; + + data = g_simple_async_result_get_op_res_gpointer (simple); + + if (data->timed_out) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_TIMED_OUT, + "Timed out after %d seconds", + data->timeout_seconds); + goto out; + } + + if (out_exit_status != NULL) + *out_exit_status = data->exit_status; + + if (out_standard_output != NULL) + *out_standard_output = g_strdup (data->child_stdout->str); + + if (out_standard_error != NULL) + *out_standard_error = g_strdup (data->child_stderr->str); + + ret = TRUE; + + out: + return ret; +} -- GitLab From d74aad8152a7c51999fffa9abe28e4306a052399 Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 13:15:17 +0800 Subject: [PATCH 02/16] check netgroup.h header file Signed-off-by: Gustavo Lima Chaves --- src/polkitbackend/polkitbackendduktapeauthority.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index ae98453..543d6fd 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -26,7 +26,11 @@ #include #include #include +#ifdef HAVE_NETGROUP_H +#include +#else #include +#endif #include #include #include -- GitLab From 69c761506cbe458807e4ae2742c9e05bc60dad3d Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 10:59:03 +0800 Subject: [PATCH 03/16] check return value Signed-off-by: Gustavo Lima Chaves --- src/polkitbackend/polkitbackendduktapeauthority.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index 543d6fd..a54ed5b 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -249,7 +249,11 @@ reload_scripts (PolkitBackendJsAuthority *authority) duk_context *cx = authority->priv->cx; duk_set_top (cx, 0); - duk_get_global_string (cx, "polkit"); + if (!duk_get_global_string (cx, "polkit")) { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error deleting old rules, not loading new ones"); + return; + } duk_push_string (cx, "_deleteRules"); duk_call_prop (cx, 0, 0); -- GitLab From f1536c4899934fd3c8243fda2d084a472fe57d2e Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 11:22:39 +0800 Subject: [PATCH 04/16] check return value Signed-off-by: Gustavo Lima Chaves --- src/polkitbackend/polkitbackendduktapeauthority.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index a54ed5b..1a7e6d3 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -656,7 +656,10 @@ push_action_and_details (duk_context *cx, gchar **keys; guint n; - duk_get_global_string (cx, "Action"); + if (!duk_get_global_string (cx, "Action")) { + return FALSE; + } + duk_new (cx, 0); set_property_str (cx, "id", action_id); @@ -699,7 +702,12 @@ polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveA duk_context *cx = authority->priv->cx; duk_set_top (cx, 0); - duk_get_global_string (cx, "polkit"); + if (!duk_get_global_string (cx, "polkit")) { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error deleting old rules, not loading new ones"); + goto out; + } + duk_push_string (cx, "_runAdminRules"); if (!push_action_and_details (cx, action_id, details, &error)) -- GitLab From ca15eecf5dc7755947515c1bfc651fd8770aaf8f Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 13:17:16 +0800 Subject: [PATCH 05/16] check return value Signed-off-by: Gustavo Lima Chaves --- src/polkitbackend/polkitbackendduktapeauthority.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index 1a7e6d3..3f1b32d 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -550,7 +550,10 @@ push_subject (duk_context *cx, char *seat_str = NULL; char *session_str = NULL; - duk_get_global_string (cx, "Subject"); + if (!duk_get_global_string (cx, "Subject")) { + return FALSE; + } + duk_new (cx, 0); if (POLKIT_IS_UNIX_PROCESS (subject)) @@ -789,8 +792,11 @@ polkit_backend_js_authority_check_authorization_sync (PolkitBackendInteractiveAu gboolean good = FALSE; duk_context *cx = authority->priv->cx; + if (!duk_get_global_string (cx, "polkit")) { + goto out; + } + duk_set_top (cx, 0); - duk_get_global_string (cx, "polkit"); duk_push_string (cx, "_runRules"); if (!push_action_and_details (cx, action_id, details, &error)) -- GitLab From 870348365cc0166e14f28e0d144ed552bba4d794 Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 13:18:13 +0800 Subject: [PATCH 06/16] check return value Signed-off-by: Gustavo Lima Chaves --- src/polkitbackend/polkitbackendduktapeauthority.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index 3f1b32d..6294ad9 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -843,7 +843,8 @@ polkit_backend_js_authority_check_authorization_sync (PolkitBackendInteractiveAu out: if (!good) ret = POLKIT_IMPLICIT_AUTHORIZATION_NOT_AUTHORIZED; - g_free (ret_str); + if (ret_str != NULL) + g_free (ret_str); return ret; } -- GitLab From 81c916ff08fdcee3c7340c4b2d4632086b89666c Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 11:23:04 +0800 Subject: [PATCH 07/16] fix typecase Signed-off-by: Gustavo Lima Chaves --- src/polkitbackend/polkitbackendduktapeauthority.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index 6294ad9..d466c9d 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -1191,7 +1191,7 @@ static void utils_on_cancelled (GCancellable *cancellable, gpointer user_data) { - UtilsSpawnData *data = user_data; + UtilsSpawnData *data = (UtilsSpawnData *)user_data; GError *error; error = NULL; @@ -1206,7 +1206,7 @@ utils_read_child_stderr (GIOChannel *channel, GIOCondition condition, gpointer user_data) { - UtilsSpawnData *data = user_data; + UtilsSpawnData *data = (UtilsSpawnData *)user_data; gchar buf[1024]; gsize bytes_read; @@ -1220,7 +1220,7 @@ utils_read_child_stdout (GIOChannel *channel, GIOCondition condition, gpointer user_data) { - UtilsSpawnData *data = user_data; + UtilsSpawnData *data = (UtilsSpawnData *)user_data; gchar buf[1024]; gsize bytes_read; @@ -1234,7 +1234,7 @@ utils_child_watch_cb (GPid pid, gint status, gpointer user_data) { - UtilsSpawnData *data = user_data; + UtilsSpawnData *data = (UtilsSpawnData *)user_data; gchar *buf; gsize buf_size; @@ -1263,7 +1263,7 @@ utils_child_watch_cb (GPid pid, static gboolean utils_timeout_cb (gpointer user_data) { - UtilsSpawnData *data = user_data; + UtilsSpawnData *data = (UtilsSpawnData *)user_data; data->timed_out = TRUE; -- GitLab From acb956bf52f0a78bf7aaf925876f96e97a146995 Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 18:04:27 +0800 Subject: [PATCH 08/16] typecase Signed-off-by: Gustavo Lima Chaves --- src/polkitbackend/polkitbackendduktapeauthority.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index d466c9d..237b1ad 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -915,8 +915,8 @@ spawn_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { - SpawnData *data = user_data; - data->res = g_object_ref (res); + SpawnData *data = (SpawnData *)user_data; + data->res = (GAsyncResult*)g_object_ref (res); g_main_loop_quit (data->loop); } @@ -1292,12 +1292,12 @@ utils_spawn (const gchar *const *argv, data->simple = g_simple_async_result_new (NULL, callback, user_data, - utils_spawn); + (gpointer*)utils_spawn); data->main_context = g_main_context_get_thread_default (); if (data->main_context != NULL) g_main_context_ref (data->main_context); - data->cancellable = cancellable != NULL ? g_object_ref (cancellable) : NULL; + data->cancellable = cancellable != NULL ? (GCancellable*)g_object_ref (cancellable) : NULL; data->child_stdout = g_string_new (NULL); data->child_stderr = g_string_new (NULL); @@ -1397,7 +1397,7 @@ utils_spawn_finish (GAsyncResult *res, if (g_simple_async_result_propagate_error (simple, error)) goto out; - data = g_simple_async_result_get_op_res_gpointer (simple); + data = (UtilsSpawnData*)g_simple_async_result_get_op_res_gpointer (simple); if (data->timed_out) { -- GitLab From be060e4d48aceb09af34868b555b6c73c7afdabb Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 13:53:23 +0800 Subject: [PATCH 09/16] some change Signed-off-by: Gustavo Lima Chaves --- .../polkitbackendduktapeauthority.c | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index 237b1ad..fad9017 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -207,18 +207,22 @@ load_scripts (PolkitBackendJsAuthority *authority) for (l = files; l != NULL; l = l->next) { - const gchar *filename = l->data; - + const gchar *filename = (gchar *)l->data; #if (DUK_VERSION >= 20000) - gchar *contents; - gsize length; - GError *error = NULL; - if (!g_file_get_contents (filename, &contents, &length, &error)){ - g_warning("Error when file contents of %s: %s\n", filename, error->message); - g_error_free (error); - continue; - } - if (duk_peval_lstring_noresult(cx, contents,length) != 0) + GFile *file = g_file_new_for_path (filename); + char *contents; + gsize len; + if (!g_file_load_contents (file, NULL, &contents, &len, NULL, NULL)) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error compiling script %s", + filename); + g_object_unref (file); + continue; + } + + g_object_unref (file); + if (duk_peval_lstring_noresult(cx, contents,len) != 0) #else if (duk_peval_file_noresult (cx, filename) != 0) #endif -- GitLab From 2ffb62048a5ebedfe3bb053feb7385c7270ede28 Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 15:25:45 +0800 Subject: [PATCH 10/16] some change Signed-off-by: Gustavo Lima Chaves --- .../polkitbackendduktapeauthority.c | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index fad9017..6fac3be 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -125,6 +125,18 @@ G_DEFINE_TYPE (PolkitBackendJsAuthority, polkit_backend_js_authority, POLKIT_BAC /* ---------------------------------------------------------------------------------------------------- */ +static duk_ret_t js_polkit_log (duk_context *cx); +static duk_ret_t js_polkit_spawn (duk_context *cx); +static duk_ret_t js_polkit_user_is_in_netgroup (duk_context *cx); + +static const duk_function_list_entry js_polkit_functions[] = +{ + { "log", js_polkit_log, 1 }, + { "spawn", js_polkit_spawn, 1 }, + { "_userIsInNetGroup", js_polkit_user_is_in_netgroup, 2 }, + { NULL, NULL, 0 }, +}; + static void polkit_backend_js_authority_init (PolkitBackendJsAuthority *authority) { @@ -347,18 +359,6 @@ setup_file_monitors (PolkitBackendJsAuthority *authority) authority->priv->dir_monitors = (GFileMonitor**) g_ptr_array_free (p, FALSE); } -static duk_ret_t js_polkit_log (duk_context *cx); -static duk_ret_t js_polkit_spawn (duk_context *cx); -static duk_ret_t js_polkit_user_is_in_netgroup (duk_context *cx); - -static const duk_function_list_entry js_polkit_functions[] = -{ - { "log", js_polkit_log, 1 }, - { "spawn", js_polkit_spawn, 1 }, - { "_userIsInNetGroup", js_polkit_user_is_in_netgroup, 2 }, - { NULL, NULL, 0 }, -}; - static void polkit_backend_js_authority_constructed (GObject *object) { -- GitLab From edb70ef69eed3275f5654510d135e680eb46c85d Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 15:25:35 +0800 Subject: [PATCH 11/16] remove WATCHDOG_TIMEOUT define Signed-off-by: Gustavo Lima Chaves --- src/polkitbackend/polkitbackendduktapeauthority.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index 6fac3be..51e03fd 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -69,7 +69,6 @@ struct _PolkitBackendJsAuthorityPrivate duk_context *cx; }; -#define WATCHDOG_TIMEOUT (15 * G_TIME_SPAN_SECOND) static void utils_spawn (const gchar *const *argv, guint timeout_seconds, -- GitLab From 906ae404f29f15ef8c529b999bf091b5d18ed7ac Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 12:46:40 +0800 Subject: [PATCH 12/16] add meson build system support Signed-off-by: Gustavo Lima Chaves --- meson.build | 11 ++++++++++- meson_options.txt | 1 + src/polkitbackend/meson.build | 10 ++++++++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/meson.build b/meson.build index 858078d..4e44723 100644 --- a/meson.build +++ b/meson.build @@ -133,7 +133,13 @@ expat_dep = dependency('expat') assert(cc.has_header('expat.h', dependencies: expat_dep), 'Can\'t find expat.h. Please install expat.') assert(cc.has_function('XML_ParserCreate', dependencies: expat_dep), 'Can\'t find expat library. Please install expat.') -mozjs_dep = dependency('mozjs-78') +js_engine = get_option('js_engine') +if js_engine == 'duktape' + js_dep = dependency('duktape') + libm_dep = cc.find_library('m') +elif js_engine == 'mozjs' + js_dep = dependency('mozjs-78') +endif dbus_dep = dependency('dbus-1', required: false) dbus_policydir = pk_prefix / pk_datadir / 'dbus-1/system.d' @@ -361,6 +367,9 @@ if enable_logind output += ' systemdsystemunitdir: ' + systemd_systemdsystemunitdir + '\n' endif output += ' polkitd user: ' + polkitd_user + ' \n' +output += ' Javascript engine: ' + js_engine + '\n' +if enable_logind +endif output += ' PAM support: ' + enable_pam.to_string() + '\n\n' if enable_pam output += ' PAM file auth: ' + pam_conf['PAM_FILE_INCLUDE_AUTH'] + '\n' diff --git a/meson_options.txt b/meson_options.txt index 25e3e77..76aa311 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -16,3 +16,4 @@ option('introspection', type: 'boolean', value: true, description: 'Enable intro option('gtk_doc', type: 'boolean', value: false, description: 'use gtk-doc to build documentation') option('man', type: 'boolean', value: false, description: 'build manual pages') +option('js_engine', type: 'combo', choices: ['mozjs', 'duktape'], value: 'duktape', description: 'javascript engine') diff --git a/src/polkitbackend/meson.build b/src/polkitbackend/meson.build index 64f0e4a..489897d 100644 --- a/src/polkitbackend/meson.build +++ b/src/polkitbackend/meson.build @@ -5,7 +5,6 @@ sources = files( 'polkitbackendactionpool.c', 'polkitbackendauthority.c', 'polkitbackendinteractiveauthority.c', - 'polkitbackendjsauthority.cpp', ) output = 'initjs.h' @@ -21,7 +20,7 @@ sources += custom_target( deps = [ expat_dep, libpolkit_gobject_dep, - mozjs_dep, + js_dep, ] c_flags = [ @@ -31,6 +30,13 @@ c_flags = [ '-DPACKAGE_SYSCONF_DIR="@0@"'.format(pk_prefix / pk_sysconfdir), ] +if js_engine == 'duktape' + sources += files('polkitbackendduktapeauthority.c') + deps += libm_dep +elif js_engine == 'mozjs' + sources += files('polkitbackendjsauthority.cpp') +endif + if enable_logind sources += files('polkitbackendsessionmonitor-systemd.c') -- GitLab From 1380b505c25be4aebe54b1b4223a570d64af83cc Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 18:49:14 +0800 Subject: [PATCH 13/16] fix run error Signed-off-by: Gustavo Lima Chaves --- src/polkitbackend/polkitbackendduktapeauthority.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index 51e03fd..4b4f8fd 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -795,11 +795,11 @@ polkit_backend_js_authority_check_authorization_sync (PolkitBackendInteractiveAu gboolean good = FALSE; duk_context *cx = authority->priv->cx; + duk_set_top (cx, 0); if (!duk_get_global_string (cx, "polkit")) { goto out; } - duk_set_top (cx, 0); duk_push_string (cx, "_runRules"); if (!push_action_and_details (cx, action_id, details, &error)) -- GitLab From 6856a704b70378948ef5f66e9b09555d97d4070b Mon Sep 17 00:00:00 2001 From: Gustavo Lima Chaves Date: Fri, 10 Sep 2021 15:17:58 -0700 Subject: [PATCH 14/16] Deduplicate code for "Add duktape as JS engine backend" effort/MR This leverages Wu Xiaotian (@yetist)'s original MR (https://gitlab.freedesktop.org/polkit/polkit/-/merge_requests/35), in an effort to complete said work. This is the first of the requests from maintainers--to reduce eliminate code duplication. The runaway-killer missing functionality will come in the sequence. Signed-off-by: Gustavo Lima Chaves --- src/polkitbackend/Makefile.am | 1 + src/polkitbackend/meson.build | 1 + src/polkitbackend/polkitbackendcommon.c | 530 +++++++++++++ src/polkitbackend/polkitbackendcommon.h | 156 ++++ .../polkitbackendduktapeauthority.c | 714 ++---------------- .../polkitbackendjsauthority.cpp | 711 ++--------------- 6 files changed, 790 insertions(+), 1323 deletions(-) create mode 100644 src/polkitbackend/polkitbackendcommon.c create mode 100644 src/polkitbackend/polkitbackendcommon.h diff --git a/src/polkitbackend/Makefile.am b/src/polkitbackend/Makefile.am index abcbc6f..6a8b4ae 100644 --- a/src/polkitbackend/Makefile.am +++ b/src/polkitbackend/Makefile.am @@ -31,6 +31,7 @@ libpolkit_backend_1_la_SOURCES = \ polkitbackend.h \ polkitbackendtypes.h \ polkitbackendprivate.h \ + polkitbackendcommon.h polkitbackendcommon.c \ polkitbackendauthority.h polkitbackendauthority.c \ polkitbackendinteractiveauthority.h polkitbackendinteractiveauthority.c \ polkitbackendjsauthority.h \ diff --git a/src/polkitbackend/meson.build b/src/polkitbackend/meson.build index 489897d..9ec01b2 100644 --- a/src/polkitbackend/meson.build +++ b/src/polkitbackend/meson.build @@ -4,6 +4,7 @@ sources = files( 'polkitbackendactionlookup.c', 'polkitbackendactionpool.c', 'polkitbackendauthority.c', + 'polkitbackendcommon.c', 'polkitbackendinteractiveauthority.c', ) diff --git a/src/polkitbackend/polkitbackendcommon.c b/src/polkitbackend/polkitbackendcommon.c new file mode 100644 index 0000000..6783dff --- /dev/null +++ b/src/polkitbackend/polkitbackendcommon.c @@ -0,0 +1,530 @@ +/* + * Copyright (C) 2008 Red Hat, 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "polkitbackendcommon.h" + +static void +utils_child_watch_from_release_cb (GPid pid, + gint status, + gpointer user_data) +{ +} + +static void +utils_spawn_data_free (UtilsSpawnData *data) +{ + if (data->timeout_source != NULL) + { + g_source_destroy (data->timeout_source); + data->timeout_source = NULL; + } + + /* Nuke the child, if necessary */ + if (data->child_watch_source != NULL) + { + g_source_destroy (data->child_watch_source); + data->child_watch_source = NULL; + } + + if (data->child_pid != 0) + { + GSource *source; + kill (data->child_pid, SIGTERM); + /* OK, we need to reap for the child ourselves - we don't want + * to use waitpid() because that might block the calling + * thread (the child might handle SIGTERM and use several + * seconds for cleanup/rollback). + * + * So we use GChildWatch instead. + * + * Avoid taking a references to ourselves. but note that we need + * to pass the GSource so we can nuke it once handled. + */ + source = g_child_watch_source_new (data->child_pid); + g_source_set_callback (source, + (GSourceFunc) utils_child_watch_from_release_cb, + source, + (GDestroyNotify) g_source_destroy); + g_source_attach (source, data->main_context); + g_source_unref (source); + data->child_pid = 0; + } + + if (data->child_stdout != NULL) + { + g_string_free (data->child_stdout, TRUE); + data->child_stdout = NULL; + } + + if (data->child_stderr != NULL) + { + g_string_free (data->child_stderr, TRUE); + data->child_stderr = NULL; + } + + if (data->child_stdout_channel != NULL) + { + g_io_channel_unref (data->child_stdout_channel); + data->child_stdout_channel = NULL; + } + if (data->child_stderr_channel != NULL) + { + g_io_channel_unref (data->child_stderr_channel); + data->child_stderr_channel = NULL; + } + + if (data->child_stdout_source != NULL) + { + g_source_destroy (data->child_stdout_source); + data->child_stdout_source = NULL; + } + if (data->child_stderr_source != NULL) + { + g_source_destroy (data->child_stderr_source); + data->child_stderr_source = NULL; + } + + if (data->child_stdout_fd != -1) + { + g_warn_if_fail (close (data->child_stdout_fd) == 0); + data->child_stdout_fd = -1; + } + if (data->child_stderr_fd != -1) + { + g_warn_if_fail (close (data->child_stderr_fd) == 0); + data->child_stderr_fd = -1; + } + + if (data->cancellable_handler_id > 0) + { + g_cancellable_disconnect (data->cancellable, data->cancellable_handler_id); + data->cancellable_handler_id = 0; + } + + if (data->main_context != NULL) + g_main_context_unref (data->main_context); + + if (data->cancellable != NULL) + g_object_unref (data->cancellable); + + g_slice_free (UtilsSpawnData, data); +} + +/* called in the thread where @cancellable was cancelled */ +static void +utils_on_cancelled (GCancellable *cancellable, + gpointer user_data) +{ + UtilsSpawnData *data = (UtilsSpawnData *)user_data; + GError *error; + + error = NULL; + g_warn_if_fail (g_cancellable_set_error_if_cancelled (cancellable, &error)); + g_simple_async_result_take_error (data->simple, error); + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); +} + +static gboolean +utils_timeout_cb (gpointer user_data) +{ + UtilsSpawnData *data = (UtilsSpawnData *)user_data; + + data->timed_out = TRUE; + + /* ok, timeout is history, make sure we don't free it in spawn_data_free() */ + data->timeout_source = NULL; + + /* we're done */ + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); + + return FALSE; /* remove source */ +} + +static void +utils_child_watch_cb (GPid pid, + gint status, + gpointer user_data) +{ + UtilsSpawnData *data = (UtilsSpawnData *)user_data; + gchar *buf; + gsize buf_size; + + if (g_io_channel_read_to_end (data->child_stdout_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) + { + g_string_append_len (data->child_stdout, buf, buf_size); + g_free (buf); + } + if (g_io_channel_read_to_end (data->child_stderr_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) + { + g_string_append_len (data->child_stderr, buf, buf_size); + g_free (buf); + } + + data->exit_status = status; + + /* ok, child watch is history, make sure we don't free it in spawn_data_free() */ + data->child_pid = 0; + data->child_watch_source = NULL; + + /* we're done */ + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); +} + +static gboolean +utils_read_child_stderr (GIOChannel *channel, + GIOCondition condition, + gpointer user_data) +{ + UtilsSpawnData *data = (UtilsSpawnData *)user_data; + gchar buf[1024]; + gsize bytes_read; + + g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); + g_string_append_len (data->child_stderr, buf, bytes_read); + return TRUE; +} + +static gboolean +utils_read_child_stdout (GIOChannel *channel, + GIOCondition condition, + gpointer user_data) +{ + UtilsSpawnData *data = (UtilsSpawnData *)user_data; + gchar buf[1024]; + gsize bytes_read; + + g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); + g_string_append_len (data->child_stdout, buf, bytes_read); + return TRUE; +} + +void +polkit_backend_common_spawn (const gchar *const *argv, + guint timeout_seconds, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + UtilsSpawnData *data; + GError *error; + + data = g_slice_new0 (UtilsSpawnData); + data->timeout_seconds = timeout_seconds; + data->simple = g_simple_async_result_new (NULL, + callback, + user_data, + (gpointer*)polkit_backend_common_spawn); + data->main_context = g_main_context_get_thread_default (); + if (data->main_context != NULL) + g_main_context_ref (data->main_context); + + data->cancellable = cancellable != NULL ? (GCancellable*)g_object_ref (cancellable) : NULL; + + data->child_stdout = g_string_new (NULL); + data->child_stderr = g_string_new (NULL); + data->child_stdout_fd = -1; + data->child_stderr_fd = -1; + + /* the life-cycle of UtilsSpawnData is tied to its GSimpleAsyncResult */ + g_simple_async_result_set_op_res_gpointer (data->simple, data, (GDestroyNotify) utils_spawn_data_free); + + error = NULL; + if (data->cancellable != NULL) + { + /* could already be cancelled */ + error = NULL; + if (g_cancellable_set_error_if_cancelled (data->cancellable, &error)) + { + g_simple_async_result_take_error (data->simple, error); + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); + goto out; + } + + data->cancellable_handler_id = g_cancellable_connect (data->cancellable, + G_CALLBACK (utils_on_cancelled), + data, + NULL); + } + + error = NULL; + if (!g_spawn_async_with_pipes (NULL, /* working directory */ + (gchar **) argv, + NULL, /* envp */ + G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, + NULL, /* child_setup */ + NULL, /* child_setup's user_data */ + &(data->child_pid), + NULL, /* gint *stdin_fd */ + &(data->child_stdout_fd), + &(data->child_stderr_fd), + &error)) + { + g_prefix_error (&error, "Error spawning: "); + g_simple_async_result_take_error (data->simple, error); + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); + goto out; + } + + if (timeout_seconds > 0) + { + data->timeout_source = g_timeout_source_new_seconds (timeout_seconds); + g_source_set_priority (data->timeout_source, G_PRIORITY_DEFAULT); + g_source_set_callback (data->timeout_source, utils_timeout_cb, data, NULL); + g_source_attach (data->timeout_source, data->main_context); + g_source_unref (data->timeout_source); + } + + data->child_watch_source = g_child_watch_source_new (data->child_pid); + g_source_set_callback (data->child_watch_source, (GSourceFunc) utils_child_watch_cb, data, NULL); + g_source_attach (data->child_watch_source, data->main_context); + g_source_unref (data->child_watch_source); + + data->child_stdout_channel = g_io_channel_unix_new (data->child_stdout_fd); + g_io_channel_set_flags (data->child_stdout_channel, G_IO_FLAG_NONBLOCK, NULL); + data->child_stdout_source = g_io_create_watch (data->child_stdout_channel, G_IO_IN); + g_source_set_callback (data->child_stdout_source, (GSourceFunc) utils_read_child_stdout, data, NULL); + g_source_attach (data->child_stdout_source, data->main_context); + g_source_unref (data->child_stdout_source); + + data->child_stderr_channel = g_io_channel_unix_new (data->child_stderr_fd); + g_io_channel_set_flags (data->child_stderr_channel, G_IO_FLAG_NONBLOCK, NULL); + data->child_stderr_source = g_io_create_watch (data->child_stderr_channel, G_IO_IN); + g_source_set_callback (data->child_stderr_source, (GSourceFunc) utils_read_child_stderr, data, NULL); + g_source_attach (data->child_stderr_source, data->main_context); + g_source_unref (data->child_stderr_source); + + out: + ; +} + +void +polkit_backend_common_on_dir_monitor_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (user_data); + + /* TODO: maybe rate-limit so storms of events are collapsed into one with a 500ms resolution? + * Because when editing a file with emacs we get 4-8 events.. + */ + + if (file != NULL) + { + gchar *name; + + name = g_file_get_basename (file); + + /* g_print ("event_type=%d file=%p name=%s\n", event_type, file, name); */ + if (!g_str_has_prefix (name, ".") && + !g_str_has_prefix (name, "#") && + g_str_has_suffix (name, ".rules") && + (event_type == G_FILE_MONITOR_EVENT_CREATED || + event_type == G_FILE_MONITOR_EVENT_DELETED || + event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Reloading rules"); + polkit_backend_common_reload_scripts (authority); + } + g_free (name); + } +} + +gboolean +polkit_backend_common_spawn_finish (GAsyncResult *res, + gint *out_exit_status, + gchar **out_standard_output, + gchar **out_standard_error, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + UtilsSpawnData *data; + gboolean ret = FALSE; + + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == polkit_backend_common_spawn); + + if (g_simple_async_result_propagate_error (simple, error)) + goto out; + + data = (UtilsSpawnData*)g_simple_async_result_get_op_res_gpointer (simple); + + if (data->timed_out) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_TIMED_OUT, + "Timed out after %d seconds", + data->timeout_seconds); + goto out; + } + + if (out_exit_status != NULL) + *out_exit_status = data->exit_status; + + if (out_standard_output != NULL) + *out_standard_output = g_strdup (data->child_stdout->str); + + if (out_standard_error != NULL) + *out_standard_error = g_strdup (data->child_stderr->str); + + ret = TRUE; + + out: + return ret; +} + +static const gchar * +polkit_backend_js_authority_get_name (PolkitBackendAuthority *authority) +{ + return "js"; +} + +static const gchar * +polkit_backend_js_authority_get_version (PolkitBackendAuthority *authority) +{ + return PACKAGE_VERSION; +} + +static PolkitAuthorityFeatures +polkit_backend_js_authority_get_features (PolkitBackendAuthority *authority) +{ + return POLKIT_AUTHORITY_FEATURES_TEMPORARY_AUTHORIZATION; +} + +void +polkit_backend_common_js_authority_class_init_common (PolkitBackendJsAuthorityClass *klass) +{ + GObjectClass *gobject_class; + PolkitBackendAuthorityClass *authority_class; + PolkitBackendInteractiveAuthorityClass *interactive_authority_class; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = polkit_backend_common_js_authority_finalize; + gobject_class->set_property = polkit_backend_common_js_authority_set_property; + gobject_class->constructed = polkit_backend_common_js_authority_constructed; + + authority_class = POLKIT_BACKEND_AUTHORITY_CLASS (klass); + authority_class->get_name = polkit_backend_js_authority_get_name; + authority_class->get_version = polkit_backend_js_authority_get_version; + authority_class->get_features = polkit_backend_js_authority_get_features; + + interactive_authority_class = POLKIT_BACKEND_INTERACTIVE_AUTHORITY_CLASS (klass); + interactive_authority_class->get_admin_identities = polkit_backend_common_js_authority_get_admin_auth_identities; + interactive_authority_class->check_authorization_sync = polkit_backend_common_js_authority_check_authorization_sync; + + g_object_class_install_property (gobject_class, + PROP_RULES_DIRS, + g_param_spec_boxed ("rules-dirs", + NULL, + NULL, + G_TYPE_STRV, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); +} + +gint +polkit_backend_common_rules_file_name_cmp (const gchar *a, + const gchar *b) +{ + gint ret; + const gchar *a_base; + const gchar *b_base; + + a_base = strrchr (a, '/'); + b_base = strrchr (b, '/'); + + g_assert (a_base != NULL); + g_assert (b_base != NULL); + a_base += 1; + b_base += 1; + + ret = g_strcmp0 (a_base, b_base); + if (ret == 0) + { + /* /etc wins over /usr */ + ret = g_strcmp0 (a, b); + g_assert (ret != 0); + } + + return ret; +} + +const gchar * +polkit_backend_common_get_signal_name (gint signal_number) +{ + switch (signal_number) + { +#define _HANDLE_SIG(sig) case sig: return #sig; + _HANDLE_SIG (SIGHUP); + _HANDLE_SIG (SIGINT); + _HANDLE_SIG (SIGQUIT); + _HANDLE_SIG (SIGILL); + _HANDLE_SIG (SIGABRT); + _HANDLE_SIG (SIGFPE); + _HANDLE_SIG (SIGKILL); + _HANDLE_SIG (SIGSEGV); + _HANDLE_SIG (SIGPIPE); + _HANDLE_SIG (SIGALRM); + _HANDLE_SIG (SIGTERM); + _HANDLE_SIG (SIGUSR1); + _HANDLE_SIG (SIGUSR2); + _HANDLE_SIG (SIGCHLD); + _HANDLE_SIG (SIGCONT); + _HANDLE_SIG (SIGSTOP); + _HANDLE_SIG (SIGTSTP); + _HANDLE_SIG (SIGTTIN); + _HANDLE_SIG (SIGTTOU); + _HANDLE_SIG (SIGBUS); +#ifdef SIGPOLL + _HANDLE_SIG (SIGPOLL); +#endif + _HANDLE_SIG (SIGPROF); + _HANDLE_SIG (SIGSYS); + _HANDLE_SIG (SIGTRAP); + _HANDLE_SIG (SIGURG); + _HANDLE_SIG (SIGVTALRM); + _HANDLE_SIG (SIGXCPU); + _HANDLE_SIG (SIGXFSZ); +#undef _HANDLE_SIG + default: + break; + } + return "UNKNOWN_SIGNAL"; +} + +void +polkit_backend_common_spawn_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SpawnData *data = (SpawnData *)user_data; + data->res = (GAsyncResult*)g_object_ref (res); + g_main_loop_quit (data->loop); +} diff --git a/src/polkitbackend/polkitbackendcommon.h b/src/polkitbackend/polkitbackendcommon.h new file mode 100644 index 0000000..6d0d267 --- /dev/null +++ b/src/polkitbackend/polkitbackendcommon.h @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2008 Red Hat, 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#if !defined (_POLKIT_BACKEND_COMPILATION) && !defined(_POLKIT_BACKEND_INSIDE_POLKIT_BACKEND_H) +#error "Only can be included directly, this file may disappear or change contents." +#endif + +#ifndef __POLKIT_BACKEND_COMMON_H +#define __POLKIT_BACKEND_COMMON_H + +#include "config.h" +#include +#include +#include +#include +#ifdef HAVE_NETGROUP_H +#include +#else +#include +#endif +#include +#include +#include +#include //here, all things glib via glib.h (including -> gspawn.h) + +#include +#include "polkitbackendjsauthority.h" + +#include + +#ifdef HAVE_LIBSYSTEMD +#include +#endif /* HAVE_LIBSYSTEMD */ + +#ifdef __cplusplus +extern "C" { +#endif + +enum +{ + PROP_0, + PROP_RULES_DIRS, +}; + +typedef struct +{ + GSimpleAsyncResult *simple; /* borrowed reference */ + GMainContext *main_context; /* may be NULL */ + + GCancellable *cancellable; /* may be NULL */ + gulong cancellable_handler_id; + + GPid child_pid; + gint child_stdout_fd; + gint child_stderr_fd; + + GIOChannel *child_stdout_channel; + GIOChannel *child_stderr_channel; + + GSource *child_watch_source; + GSource *child_stdout_source; + GSource *child_stderr_source; + + guint timeout_seconds; + gboolean timed_out; + GSource *timeout_source; + + GString *child_stdout; + GString *child_stderr; + + gint exit_status; +} UtilsSpawnData; + +typedef struct +{ + GMainLoop *loop; + GAsyncResult *res; +} SpawnData; + +void polkit_backend_common_spawn (const gchar *const *argv, + guint timeout_seconds, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +void polkit_backend_common_spawn_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data); +gboolean polkit_backend_common_spawn_finish (GAsyncResult *res, + gint *out_exit_status, + gchar **out_standard_output, + gchar **out_standard_error, + GError **error); + +void polkit_backend_common_on_dir_monitor_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data); + +void polkit_backend_common_js_authority_class_init_common (PolkitBackendJsAuthorityClass *klass); + +gint polkit_backend_common_rules_file_name_cmp (const gchar *a, + const gchar *b); + +const gchar *polkit_backend_common_get_signal_name (gint signal_number); + +/* To be provided by each JS backend, from here onwards ---------------------------------------------- */ + +void polkit_backend_common_reload_scripts (PolkitBackendJsAuthority *authority); +void polkit_backend_common_js_authority_finalize (GObject *object); +void polkit_backend_common_js_authority_constructed (GObject *object); +GList *polkit_backend_common_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *_authority, + PolkitSubject *caller, + PolkitSubject *subject, + PolkitIdentity *user_for_subject, + gboolean subject_is_local, + gboolean subject_is_active, + const gchar *action_id, + PolkitDetails *details); +void polkit_backend_common_js_authority_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +PolkitImplicitAuthorization polkit_backend_common_js_authority_check_authorization_sync (PolkitBackendInteractiveAuthority *_authority, + PolkitSubject *caller, + PolkitSubject *subject, + PolkitIdentity *user_for_subject, + gboolean subject_is_local, + gboolean subject_is_active, + const gchar *action_id, + PolkitDetails *details, + PolkitImplicitAuthorization implicit); +#ifdef __cplusplus +} +#endif + +#endif /* __POLKIT_BACKEND_COMMON_H */ + diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index 4b4f8fd..a2b4420 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -21,32 +21,12 @@ * Author: David Zeuthen */ -#include "config.h" -#include -#include -#include -#include -#ifdef HAVE_NETGROUP_H -#include -#else -#include -#endif -#include -#include -#include -#include - -#include -#include "polkitbackendjsauthority.h" - -#include +#include "polkitbackendcommon.h" -#ifdef HAVE_LIBSYSTEMD -#include -#endif /* HAVE_LIBSYSTEMD */ +#include "duktape.h" +/* Built source and not too big to worry about deduplication */ #include "initjs.h" /* init.js */ -#include "duktape.h" /** * SECTION:polkitbackendjsauthority @@ -54,10 +34,9 @@ * @short_description: JS Authority * @stability: Unstable * - * An implementation of #PolkitBackendAuthority that reads and - * evalates Javascript files and supports interaction with - * authentication agents (virtue of being based on - * #PolkitBackendInteractiveAuthority). + * An (Duktape-based) implementation of #PolkitBackendAuthority that reads and + * evaluates Javascript files and supports interaction with authentication + * agents (virtue of being based on #PolkitBackendInteractiveAuthority). */ /* ---------------------------------------------------------------------------------------------------- */ @@ -66,64 +45,16 @@ struct _PolkitBackendJsAuthorityPrivate { gchar **rules_dirs; GFileMonitor **dir_monitors; /* NULL-terminated array of GFileMonitor instances */ - duk_context *cx; -}; - - -static void utils_spawn (const gchar *const *argv, - guint timeout_seconds, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); - -gboolean utils_spawn_finish (GAsyncResult *res, - gint *out_exit_status, - gchar **out_standard_output, - gchar **out_standard_error, - GError **error); -static void on_dir_monitor_changed (GFileMonitor *monitor, - GFile *file, - GFile *other_file, - GFileMonitorEvent event_type, - gpointer user_data); - -/* ---------------------------------------------------------------------------------------------------- */ - -enum -{ - PROP_0, - PROP_RULES_DIRS, + duk_context *cx; }; /* ---------------------------------------------------------------------------------------------------- */ -static GList *polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *authority, - PolkitSubject *caller, - PolkitSubject *subject, - PolkitIdentity *user_for_subject, - gboolean subject_is_local, - gboolean subject_is_active, - const gchar *action_id, - PolkitDetails *details); - -static PolkitImplicitAuthorization polkit_backend_js_authority_check_authorization_sync ( - PolkitBackendInteractiveAuthority *authority, - PolkitSubject *caller, - PolkitSubject *subject, - PolkitIdentity *user_for_subject, - gboolean subject_is_local, - gboolean subject_is_active, - const gchar *action_id, - PolkitDetails *details, - PolkitImplicitAuthorization implicit); - G_DEFINE_TYPE (PolkitBackendJsAuthority, polkit_backend_js_authority, POLKIT_BACKEND_TYPE_INTERACTIVE_AUTHORITY); /* ---------------------------------------------------------------------------------------------------- */ -/* ---------------------------------------------------------------------------------------------------- */ - static duk_ret_t js_polkit_log (duk_context *cx); static duk_ret_t js_polkit_spawn (duk_context *cx); static duk_ret_t js_polkit_user_is_in_netgroup (duk_context *cx); @@ -144,33 +75,6 @@ polkit_backend_js_authority_init (PolkitBackendJsAuthority *authority) PolkitBackendJsAuthorityPrivate); } -static gint -rules_file_name_cmp (const gchar *a, - const gchar *b) -{ - gint ret; - const gchar *a_base; - const gchar *b_base; - - a_base = strrchr (a, '/'); - b_base = strrchr (b, '/'); - - g_assert (a_base != NULL); - g_assert (b_base != NULL); - a_base += 1; - b_base += 1; - - ret = g_strcmp0 (a_base, b_base); - if (ret == 0) - { - /* /etc wins over /usr */ - ret = g_strcmp0 (a, b); - g_assert (ret != 0); - } - - return ret; -} - static void load_scripts (PolkitBackendJsAuthority *authority) { @@ -214,7 +118,7 @@ load_scripts (PolkitBackendJsAuthority *authority) } } - files = g_list_sort (files, (GCompareFunc) rules_file_name_cmp); + files = g_list_sort (files, (GCompareFunc) polkit_backend_common_rules_file_name_cmp); for (l = files; l != NULL; l = l->next) { @@ -258,8 +162,8 @@ load_scripts (PolkitBackendJsAuthority *authority) g_list_free_full (files, g_free); } -static void -reload_scripts (PolkitBackendJsAuthority *authority) +void +polkit_backend_common_reload_scripts (PolkitBackendJsAuthority *authority) { duk_context *cx = authority->priv->cx; @@ -282,42 +186,6 @@ reload_scripts (PolkitBackendJsAuthority *authority) g_signal_emit_by_name (authority, "changed"); } -static void -on_dir_monitor_changed (GFileMonitor *monitor, - GFile *file, - GFile *other_file, - GFileMonitorEvent event_type, - gpointer user_data) -{ - PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (user_data); - - /* TODO: maybe rate-limit so storms of events are collapsed into one with a 500ms resolution? - * Because when editing a file with emacs we get 4-8 events.. - */ - - if (file != NULL) - { - gchar *name; - - name = g_file_get_basename (file); - - /* g_print ("event_type=%d file=%p name=%s\n", event_type, file, name); */ - if (!g_str_has_prefix (name, ".") && - !g_str_has_prefix (name, "#") && - g_str_has_suffix (name, ".rules") && - (event_type == G_FILE_MONITOR_EVENT_CREATED || - event_type == G_FILE_MONITOR_EVENT_DELETED || - event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)) - { - polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), - "Reloading rules"); - reload_scripts (authority); - } - g_free (name); - } -} - - static void setup_file_monitors (PolkitBackendJsAuthority *authority) { @@ -349,7 +217,7 @@ setup_file_monitors (PolkitBackendJsAuthority *authority) { g_signal_connect (monitor, "changed", - G_CALLBACK (on_dir_monitor_changed), + G_CALLBACK (polkit_backend_common_on_dir_monitor_changed), authority); g_ptr_array_add (p, monitor); } @@ -358,8 +226,8 @@ setup_file_monitors (PolkitBackendJsAuthority *authority) authority->priv->dir_monitors = (GFileMonitor**) g_ptr_array_free (p, FALSE); } -static void -polkit_backend_js_authority_constructed (GObject *object) +void +polkit_backend_common_js_authority_constructed (GObject *object) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); duk_context *cx; @@ -395,8 +263,8 @@ polkit_backend_js_authority_constructed (GObject *object) g_assert_not_reached (); } -static void -polkit_backend_js_authority_finalize (GObject *object) +void +polkit_backend_common_js_authority_finalize (GObject *object) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); guint n; @@ -405,7 +273,7 @@ polkit_backend_js_authority_finalize (GObject *object) { GFileMonitor *monitor = authority->priv->dir_monitors[n]; g_signal_handlers_disconnect_by_func (monitor, - G_CALLBACK (on_dir_monitor_changed), + G_CALLBACK (polkit_backend_common_on_dir_monitor_changed), authority); g_object_unref (monitor); } @@ -417,11 +285,11 @@ polkit_backend_js_authority_finalize (GObject *object) G_OBJECT_CLASS (polkit_backend_js_authority_parent_class)->finalize (object); } -static void -polkit_backend_js_authority_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec) +void +polkit_backend_common_js_authority_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); @@ -438,55 +306,10 @@ polkit_backend_js_authority_set_property (GObject *object, } } -static const gchar * -polkit_backend_js_authority_get_name (PolkitBackendAuthority *authority) -{ - return "js"; -} - -static const gchar * -polkit_backend_js_authority_get_version (PolkitBackendAuthority *authority) -{ - return PACKAGE_VERSION; -} - -static PolkitAuthorityFeatures -polkit_backend_js_authority_get_features (PolkitBackendAuthority *authority) -{ - return POLKIT_AUTHORITY_FEATURES_TEMPORARY_AUTHORIZATION; -} - static void polkit_backend_js_authority_class_init (PolkitBackendJsAuthorityClass *klass) { - GObjectClass *gobject_class; - PolkitBackendAuthorityClass *authority_class; - PolkitBackendInteractiveAuthorityClass *interactive_authority_class; - - - gobject_class = G_OBJECT_CLASS (klass); - gobject_class->finalize = polkit_backend_js_authority_finalize; - gobject_class->set_property = polkit_backend_js_authority_set_property; - gobject_class->constructed = polkit_backend_js_authority_constructed; - - authority_class = POLKIT_BACKEND_AUTHORITY_CLASS (klass); - authority_class->get_name = polkit_backend_js_authority_get_name; - authority_class->get_version = polkit_backend_js_authority_get_version; - authority_class->get_features = polkit_backend_js_authority_get_features; - - interactive_authority_class = POLKIT_BACKEND_INTERACTIVE_AUTHORITY_CLASS (klass); - interactive_authority_class->get_admin_identities = polkit_backend_js_authority_get_admin_auth_identities; - interactive_authority_class->check_authorization_sync = polkit_backend_js_authority_check_authorization_sync; - - g_object_class_install_property (gobject_class, - PROP_RULES_DIRS, - g_param_spec_boxed ("rules-dirs", - NULL, - NULL, - G_TYPE_STRV, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); - - + polkit_backend_common_js_authority_class_init_common (klass); g_type_class_add_private (klass, sizeof (PolkitBackendJsAuthorityPrivate)); } @@ -689,15 +512,15 @@ push_action_and_details (duk_context *cx, /* ---------------------------------------------------------------------------------------------------- */ -static GList * -polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *_authority, - PolkitSubject *caller, - PolkitSubject *subject, - PolkitIdentity *user_for_subject, - gboolean subject_is_local, - gboolean subject_is_active, - const gchar *action_id, - PolkitDetails *details) +GList * +polkit_backend_common_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *_authority, + PolkitSubject *caller, + PolkitSubject *subject, + PolkitIdentity *user_for_subject, + gboolean subject_is_local, + gboolean subject_is_active, + const gchar *action_id, + PolkitDetails *details) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (_authority); GList *ret = NULL; @@ -777,16 +600,16 @@ polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveA /* ---------------------------------------------------------------------------------------------------- */ -static PolkitImplicitAuthorization -polkit_backend_js_authority_check_authorization_sync (PolkitBackendInteractiveAuthority *_authority, - PolkitSubject *caller, - PolkitSubject *subject, - PolkitIdentity *user_for_subject, - gboolean subject_is_local, - gboolean subject_is_active, - const gchar *action_id, - PolkitDetails *details, - PolkitImplicitAuthorization implicit) +PolkitImplicitAuthorization +polkit_backend_common_js_authority_check_authorization_sync (PolkitBackendInteractiveAuthority *_authority, + PolkitSubject *caller, + PolkitSubject *subject, + PolkitIdentity *user_for_subject, + gboolean subject_is_local, + gboolean subject_is_active, + const gchar *action_id, + PolkitDetails *details, + PolkitImplicitAuthorization implicit) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (_authority); PolkitImplicitAuthorization ret = implicit; @@ -864,65 +687,6 @@ js_polkit_log (duk_context *cx) /* ---------------------------------------------------------------------------------------------------- */ -static const gchar * -get_signal_name (gint signal_number) -{ - switch (signal_number) - { -#define _HANDLE_SIG(sig) case sig: return #sig; - _HANDLE_SIG (SIGHUP); - _HANDLE_SIG (SIGINT); - _HANDLE_SIG (SIGQUIT); - _HANDLE_SIG (SIGILL); - _HANDLE_SIG (SIGABRT); - _HANDLE_SIG (SIGFPE); - _HANDLE_SIG (SIGKILL); - _HANDLE_SIG (SIGSEGV); - _HANDLE_SIG (SIGPIPE); - _HANDLE_SIG (SIGALRM); - _HANDLE_SIG (SIGTERM); - _HANDLE_SIG (SIGUSR1); - _HANDLE_SIG (SIGUSR2); - _HANDLE_SIG (SIGCHLD); - _HANDLE_SIG (SIGCONT); - _HANDLE_SIG (SIGSTOP); - _HANDLE_SIG (SIGTSTP); - _HANDLE_SIG (SIGTTIN); - _HANDLE_SIG (SIGTTOU); - _HANDLE_SIG (SIGBUS); -#ifdef SIGPOLL - _HANDLE_SIG (SIGPOLL); -#endif - _HANDLE_SIG (SIGPROF); - _HANDLE_SIG (SIGSYS); - _HANDLE_SIG (SIGTRAP); - _HANDLE_SIG (SIGURG); - _HANDLE_SIG (SIGVTALRM); - _HANDLE_SIG (SIGXCPU); - _HANDLE_SIG (SIGXFSZ); -#undef _HANDLE_SIG - default: - break; - } - return "UNKNOWN_SIGNAL"; -} - -typedef struct -{ - GMainLoop *loop; - GAsyncResult *res; -} SpawnData; - -static void -spawn_cb (GObject *source_object, - GAsyncResult *res, - gpointer user_data) -{ - SpawnData *data = (SpawnData *)user_data; - data->res = (GAsyncResult*)g_object_ref (res); - g_main_loop_quit (data->loop); -} - static duk_ret_t js_polkit_spawn (duk_context *cx) { @@ -962,21 +726,21 @@ js_polkit_spawn (duk_context *cx) g_main_context_push_thread_default (context); data.loop = loop; - utils_spawn ((const gchar *const *) argv, - 10, /* timeout_seconds */ - NULL, /* cancellable */ - spawn_cb, - &data); + polkit_backend_common_spawn ((const gchar *const *) argv, + 10, /* timeout_seconds */ + NULL, /* cancellable */ + polkit_backend_common_spawn_cb, + &data); g_main_loop_run (loop); g_main_context_pop_thread_default (context); - if (!utils_spawn_finish (data.res, - &exit_status, - &standard_output, - &standard_error, - &error)) + if (!polkit_backend_common_spawn_finish (data.res, + &exit_status, + &standard_output, + &standard_error, + &error)) { err_str = g_strdup_printf ("Error spawning helper: %s (%s, %d)", error->message, g_quark_to_string (error->domain), error->code); @@ -998,7 +762,7 @@ js_polkit_spawn (duk_context *cx) { g_string_append_printf (gstr, "Helper was signaled with signal %s (%d)", - get_signal_name (WTERMSIG (exit_status)), + polkit_backend_common_get_signal_name (WTERMSIG (exit_status)), WTERMSIG (exit_status)); } g_string_append_printf (gstr, ", stdout=`%s', stderr=`%s'", @@ -1052,377 +816,3 @@ js_polkit_user_is_in_netgroup (duk_context *cx) } /* ---------------------------------------------------------------------------------------------------- */ - -typedef struct -{ - GSimpleAsyncResult *simple; /* borrowed reference */ - GMainContext *main_context; /* may be NULL */ - - GCancellable *cancellable; /* may be NULL */ - gulong cancellable_handler_id; - - GPid child_pid; - gint child_stdout_fd; - gint child_stderr_fd; - - GIOChannel *child_stdout_channel; - GIOChannel *child_stderr_channel; - - GSource *child_watch_source; - GSource *child_stdout_source; - GSource *child_stderr_source; - - guint timeout_seconds; - gboolean timed_out; - GSource *timeout_source; - - GString *child_stdout; - GString *child_stderr; - - gint exit_status; -} UtilsSpawnData; - -static void -utils_child_watch_from_release_cb (GPid pid, - gint status, - gpointer user_data) -{ -} - -static void -utils_spawn_data_free (UtilsSpawnData *data) -{ - if (data->timeout_source != NULL) - { - g_source_destroy (data->timeout_source); - data->timeout_source = NULL; - } - - /* Nuke the child, if necessary */ - if (data->child_watch_source != NULL) - { - g_source_destroy (data->child_watch_source); - data->child_watch_source = NULL; - } - - if (data->child_pid != 0) - { - GSource *source; - kill (data->child_pid, SIGTERM); - /* OK, we need to reap for the child ourselves - we don't want - * to use waitpid() because that might block the calling - * thread (the child might handle SIGTERM and use several - * seconds for cleanup/rollback). - * - * So we use GChildWatch instead. - * - * Avoid taking a references to ourselves. but note that we need - * to pass the GSource so we can nuke it once handled. - */ - source = g_child_watch_source_new (data->child_pid); - g_source_set_callback (source, - (GSourceFunc) utils_child_watch_from_release_cb, - source, - (GDestroyNotify) g_source_destroy); - g_source_attach (source, data->main_context); - g_source_unref (source); - data->child_pid = 0; - } - - if (data->child_stdout != NULL) - { - g_string_free (data->child_stdout, TRUE); - data->child_stdout = NULL; - } - - if (data->child_stderr != NULL) - { - g_string_free (data->child_stderr, TRUE); - data->child_stderr = NULL; - } - - if (data->child_stdout_channel != NULL) - { - g_io_channel_unref (data->child_stdout_channel); - data->child_stdout_channel = NULL; - } - if (data->child_stderr_channel != NULL) - { - g_io_channel_unref (data->child_stderr_channel); - data->child_stderr_channel = NULL; - } - - if (data->child_stdout_source != NULL) - { - g_source_destroy (data->child_stdout_source); - data->child_stdout_source = NULL; - } - if (data->child_stderr_source != NULL) - { - g_source_destroy (data->child_stderr_source); - data->child_stderr_source = NULL; - } - - if (data->child_stdout_fd != -1) - { - g_warn_if_fail (close (data->child_stdout_fd) == 0); - data->child_stdout_fd = -1; - } - if (data->child_stderr_fd != -1) - { - g_warn_if_fail (close (data->child_stderr_fd) == 0); - data->child_stderr_fd = -1; - } - - if (data->cancellable_handler_id > 0) - { - g_cancellable_disconnect (data->cancellable, data->cancellable_handler_id); - data->cancellable_handler_id = 0; - } - - if (data->main_context != NULL) - g_main_context_unref (data->main_context); - - if (data->cancellable != NULL) - g_object_unref (data->cancellable); - - g_slice_free (UtilsSpawnData, data); -} - -/* called in the thread where @cancellable was cancelled */ -static void -utils_on_cancelled (GCancellable *cancellable, - gpointer user_data) -{ - UtilsSpawnData *data = (UtilsSpawnData *)user_data; - GError *error; - - error = NULL; - g_warn_if_fail (g_cancellable_set_error_if_cancelled (cancellable, &error)); - g_simple_async_result_take_error (data->simple, error); - g_simple_async_result_complete_in_idle (data->simple); - g_object_unref (data->simple); -} - -static gboolean -utils_read_child_stderr (GIOChannel *channel, - GIOCondition condition, - gpointer user_data) -{ - UtilsSpawnData *data = (UtilsSpawnData *)user_data; - gchar buf[1024]; - gsize bytes_read; - - g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); - g_string_append_len (data->child_stderr, buf, bytes_read); - return TRUE; -} - -static gboolean -utils_read_child_stdout (GIOChannel *channel, - GIOCondition condition, - gpointer user_data) -{ - UtilsSpawnData *data = (UtilsSpawnData *)user_data; - gchar buf[1024]; - gsize bytes_read; - - g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); - g_string_append_len (data->child_stdout, buf, bytes_read); - return TRUE; -} - -static void -utils_child_watch_cb (GPid pid, - gint status, - gpointer user_data) -{ - UtilsSpawnData *data = (UtilsSpawnData *)user_data; - gchar *buf; - gsize buf_size; - - if (g_io_channel_read_to_end (data->child_stdout_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) - { - g_string_append_len (data->child_stdout, buf, buf_size); - g_free (buf); - } - if (g_io_channel_read_to_end (data->child_stderr_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) - { - g_string_append_len (data->child_stderr, buf, buf_size); - g_free (buf); - } - - data->exit_status = status; - - /* ok, child watch is history, make sure we don't free it in spawn_data_free() */ - data->child_pid = 0; - data->child_watch_source = NULL; - - /* we're done */ - g_simple_async_result_complete_in_idle (data->simple); - g_object_unref (data->simple); -} - -static gboolean -utils_timeout_cb (gpointer user_data) -{ - UtilsSpawnData *data = (UtilsSpawnData *)user_data; - - data->timed_out = TRUE; - - /* ok, timeout is history, make sure we don't free it in spawn_data_free() */ - data->timeout_source = NULL; - - /* we're done */ - g_simple_async_result_complete_in_idle (data->simple); - g_object_unref (data->simple); - - return FALSE; /* remove source */ -} - -static void -utils_spawn (const gchar *const *argv, - guint timeout_seconds, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - UtilsSpawnData *data; - GError *error; - - data = g_slice_new0 (UtilsSpawnData); - data->timeout_seconds = timeout_seconds; - data->simple = g_simple_async_result_new (NULL, - callback, - user_data, - (gpointer*)utils_spawn); - data->main_context = g_main_context_get_thread_default (); - if (data->main_context != NULL) - g_main_context_ref (data->main_context); - - data->cancellable = cancellable != NULL ? (GCancellable*)g_object_ref (cancellable) : NULL; - - data->child_stdout = g_string_new (NULL); - data->child_stderr = g_string_new (NULL); - data->child_stdout_fd = -1; - data->child_stderr_fd = -1; - - /* the life-cycle of UtilsSpawnData is tied to its GSimpleAsyncResult */ - g_simple_async_result_set_op_res_gpointer (data->simple, data, (GDestroyNotify) utils_spawn_data_free); - - error = NULL; - if (data->cancellable != NULL) - { - /* could already be cancelled */ - error = NULL; - if (g_cancellable_set_error_if_cancelled (data->cancellable, &error)) - { - g_simple_async_result_take_error (data->simple, error); - g_simple_async_result_complete_in_idle (data->simple); - g_object_unref (data->simple); - goto out; - } - - data->cancellable_handler_id = g_cancellable_connect (data->cancellable, - G_CALLBACK (utils_on_cancelled), - data, - NULL); - } - - error = NULL; - if (!g_spawn_async_with_pipes (NULL, /* working directory */ - (gchar **) argv, - NULL, /* envp */ - G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, - NULL, /* child_setup */ - NULL, /* child_setup's user_data */ - &(data->child_pid), - NULL, /* gint *stdin_fd */ - &(data->child_stdout_fd), - &(data->child_stderr_fd), - &error)) - { - g_prefix_error (&error, "Error spawning: "); - g_simple_async_result_take_error (data->simple, error); - g_simple_async_result_complete_in_idle (data->simple); - g_object_unref (data->simple); - goto out; - } - - if (timeout_seconds > 0) - { - data->timeout_source = g_timeout_source_new_seconds (timeout_seconds); - g_source_set_priority (data->timeout_source, G_PRIORITY_DEFAULT); - g_source_set_callback (data->timeout_source, utils_timeout_cb, data, NULL); - g_source_attach (data->timeout_source, data->main_context); - g_source_unref (data->timeout_source); - } - - data->child_watch_source = g_child_watch_source_new (data->child_pid); - g_source_set_callback (data->child_watch_source, (GSourceFunc) utils_child_watch_cb, data, NULL); - g_source_attach (data->child_watch_source, data->main_context); - g_source_unref (data->child_watch_source); - - data->child_stdout_channel = g_io_channel_unix_new (data->child_stdout_fd); - g_io_channel_set_flags (data->child_stdout_channel, G_IO_FLAG_NONBLOCK, NULL); - data->child_stdout_source = g_io_create_watch (data->child_stdout_channel, G_IO_IN); - g_source_set_callback (data->child_stdout_source, (GSourceFunc) utils_read_child_stdout, data, NULL); - g_source_attach (data->child_stdout_source, data->main_context); - g_source_unref (data->child_stdout_source); - - data->child_stderr_channel = g_io_channel_unix_new (data->child_stderr_fd); - g_io_channel_set_flags (data->child_stderr_channel, G_IO_FLAG_NONBLOCK, NULL); - data->child_stderr_source = g_io_create_watch (data->child_stderr_channel, G_IO_IN); - g_source_set_callback (data->child_stderr_source, (GSourceFunc) utils_read_child_stderr, data, NULL); - g_source_attach (data->child_stderr_source, data->main_context); - g_source_unref (data->child_stderr_source); - - out: - ; -} - -gboolean -utils_spawn_finish (GAsyncResult *res, - gint *out_exit_status, - gchar **out_standard_output, - gchar **out_standard_error, - GError **error) -{ - GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); - UtilsSpawnData *data; - gboolean ret = FALSE; - - g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - - g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == utils_spawn); - - if (g_simple_async_result_propagate_error (simple, error)) - goto out; - - data = (UtilsSpawnData*)g_simple_async_result_get_op_res_gpointer (simple); - - if (data->timed_out) - { - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_TIMED_OUT, - "Timed out after %d seconds", - data->timeout_seconds); - goto out; - } - - if (out_exit_status != NULL) - *out_exit_status = data->exit_status; - - if (out_standard_output != NULL) - *out_standard_output = g_strdup (data->child_stdout->str); - - if (out_standard_error != NULL) - *out_standard_error = g_strdup (data->child_stderr->str); - - ret = TRUE; - - out: - return ret; -} diff --git a/src/polkitbackend/polkitbackendjsauthority.cpp b/src/polkitbackend/polkitbackendjsauthority.cpp index ca17108..e28091d 100644 --- a/src/polkitbackend/polkitbackendjsauthority.cpp +++ b/src/polkitbackend/polkitbackendjsauthority.cpp @@ -19,29 +19,7 @@ * Author: David Zeuthen */ -#include "config.h" -#include -#include -#include -#include -#ifdef HAVE_NETGROUP_H -#include -#else -#include -#endif -#include -#include -#include -#include - -#include -#include "polkitbackendjsauthority.h" - -#include - -#ifdef HAVE_LIBSYSTEMD -#include -#endif /* HAVE_LIBSYSTEMD */ +#include "polkitbackendcommon.h" #include #include @@ -52,6 +30,7 @@ #include #include +/* Built source and not too big to worry about deduplication */ #include "initjs.h" /* init.js */ #ifdef JSGC_USE_EXACT_ROOTING @@ -67,10 +46,9 @@ * @short_description: JS Authority * @stability: Unstable * - * An implementation of #PolkitBackendAuthority that reads and - * evalates Javascript files and supports interaction with - * authentication agents (virtue of being based on - * #PolkitBackendInteractiveAuthority). + * An (SpiderMonkey-based) implementation of #PolkitBackendAuthority that reads + * and evaluates Javascript files and supports interaction with authentication + * agents (virtue of being based on #PolkitBackendInteractiveAuthority). */ /* ---------------------------------------------------------------------------------------------------- */ @@ -100,57 +78,11 @@ static bool execute_script_with_runaway_killer (PolkitBackendJsAuthority *author JS::HandleScript script, JS::MutableHandleValue rval); -static void utils_spawn (const gchar *const *argv, - guint timeout_seconds, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); - -gboolean utils_spawn_finish (GAsyncResult *res, - gint *out_exit_status, - gchar **out_standard_output, - gchar **out_standard_error, - GError **error); - -static void on_dir_monitor_changed (GFileMonitor *monitor, - GFile *file, - GFile *other_file, - GFileMonitorEvent event_type, - gpointer user_data); - -/* ---------------------------------------------------------------------------------------------------- */ - -enum -{ - PROP_0, - PROP_RULES_DIRS, -}; - /* ---------------------------------------------------------------------------------------------------- */ static gpointer runaway_killer_thread_func (gpointer user_data); static void runaway_killer_terminate (PolkitBackendJsAuthority *authority); -static GList *polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *authority, - PolkitSubject *caller, - PolkitSubject *subject, - PolkitIdentity *user_for_subject, - gboolean subject_is_local, - gboolean subject_is_active, - const gchar *action_id, - PolkitDetails *details); - -static PolkitImplicitAuthorization polkit_backend_js_authority_check_authorization_sync ( - PolkitBackendInteractiveAuthority *authority, - PolkitSubject *caller, - PolkitSubject *subject, - PolkitIdentity *user_for_subject, - gboolean subject_is_local, - gboolean subject_is_active, - const gchar *action_id, - PolkitDetails *details, - PolkitImplicitAuthorization implicit); - G_DEFINE_TYPE (PolkitBackendJsAuthority, polkit_backend_js_authority, POLKIT_BACKEND_TYPE_INTERACTIVE_AUTHORITY); /* ---------------------------------------------------------------------------------------------------- */ @@ -229,33 +161,6 @@ polkit_backend_js_authority_init (PolkitBackendJsAuthority *authority) PolkitBackendJsAuthorityPrivate); } -static gint -rules_file_name_cmp (const gchar *a, - const gchar *b) -{ - gint ret; - const gchar *a_base; - const gchar *b_base; - - a_base = strrchr (a, '/'); - b_base = strrchr (b, '/'); - - g_assert (a_base != NULL); - g_assert (b_base != NULL); - a_base += 1; - b_base += 1; - - ret = g_strcmp0 (a_base, b_base); - if (ret == 0) - { - /* /etc wins over /usr */ - ret = g_strcmp0 (a, b); - g_assert (ret != 0); - } - - return ret; -} - /* authority->priv->cx must be within a request */ static void load_scripts (PolkitBackendJsAuthority *authority) @@ -299,7 +204,7 @@ load_scripts (PolkitBackendJsAuthority *authority) } } - files = g_list_sort (files, (GCompareFunc) rules_file_name_cmp); + files = g_list_sort (files, (GCompareFunc) polkit_backend_common_rules_file_name_cmp); for (l = files; l != NULL; l = l->next) { @@ -365,8 +270,8 @@ load_scripts (PolkitBackendJsAuthority *authority) g_list_free_full (files, g_free); } -static void -reload_scripts (PolkitBackendJsAuthority *authority) +void +polkit_backend_common_reload_scripts (PolkitBackendJsAuthority *authority) { JS::RootedValueArray<1> args(authority->priv->cx); JS::RootedValue rval(authority->priv->cx); @@ -395,42 +300,6 @@ reload_scripts (PolkitBackendJsAuthority *authority) g_signal_emit_by_name (authority, "changed"); } -static void -on_dir_monitor_changed (GFileMonitor *monitor, - GFile *file, - GFile *other_file, - GFileMonitorEvent event_type, - gpointer user_data) -{ - PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (user_data); - - /* TODO: maybe rate-limit so storms of events are collapsed into one with a 500ms resolution? - * Because when editing a file with emacs we get 4-8 events.. - */ - - if (file != NULL) - { - gchar *name; - - name = g_file_get_basename (file); - - /* g_print ("event_type=%d file=%p name=%s\n", event_type, file, name); */ - if (!g_str_has_prefix (name, ".") && - !g_str_has_prefix (name, "#") && - g_str_has_suffix (name, ".rules") && - (event_type == G_FILE_MONITOR_EVENT_CREATED || - event_type == G_FILE_MONITOR_EVENT_DELETED || - event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)) - { - polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), - "Reloading rules"); - reload_scripts (authority); - } - g_free (name); - } -} - - static void setup_file_monitors (PolkitBackendJsAuthority *authority) { @@ -462,7 +331,7 @@ setup_file_monitors (PolkitBackendJsAuthority *authority) { g_signal_connect (monitor, "changed", - G_CALLBACK (on_dir_monitor_changed), + G_CALLBACK (polkit_backend_common_on_dir_monitor_changed), authority); g_ptr_array_add (p, monitor); } @@ -471,8 +340,8 @@ setup_file_monitors (PolkitBackendJsAuthority *authority) authority->priv->dir_monitors = (GFileMonitor**) g_ptr_array_free (p, FALSE); } -static void -polkit_backend_js_authority_constructed (GObject *object) +void +polkit_backend_common_js_authority_constructed (GObject *object) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); @@ -561,8 +430,8 @@ polkit_backend_js_authority_constructed (GObject *object) g_assert_not_reached (); } -static void -polkit_backend_js_authority_finalize (GObject *object) +void +polkit_backend_common_js_authority_finalize (GObject *object) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); guint n; @@ -577,7 +446,7 @@ polkit_backend_js_authority_finalize (GObject *object) { GFileMonitor *monitor = authority->priv->dir_monitors[n]; g_signal_handlers_disconnect_by_func (monitor, - (gpointer*)G_CALLBACK (on_dir_monitor_changed), + (gpointer*)G_CALLBACK (polkit_backend_common_on_dir_monitor_changed), authority); g_object_unref (monitor); } @@ -594,11 +463,11 @@ polkit_backend_js_authority_finalize (GObject *object) G_OBJECT_CLASS (polkit_backend_js_authority_parent_class)->finalize (object); } -static void -polkit_backend_js_authority_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec) +void +polkit_backend_common_js_authority_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); @@ -615,57 +484,12 @@ polkit_backend_js_authority_set_property (GObject *object, } } -static const gchar * -polkit_backend_js_authority_get_name (PolkitBackendAuthority *authority) -{ - return "js"; -} - -static const gchar * -polkit_backend_js_authority_get_version (PolkitBackendAuthority *authority) -{ - return PACKAGE_VERSION; -} - -static PolkitAuthorityFeatures -polkit_backend_js_authority_get_features (PolkitBackendAuthority *authority) -{ - return POLKIT_AUTHORITY_FEATURES_TEMPORARY_AUTHORIZATION; -} - static void polkit_backend_js_authority_class_init (PolkitBackendJsAuthorityClass *klass) { - GObjectClass *gobject_class; - PolkitBackendAuthorityClass *authority_class; - PolkitBackendInteractiveAuthorityClass *interactive_authority_class; - - - gobject_class = G_OBJECT_CLASS (klass); - gobject_class->finalize = polkit_backend_js_authority_finalize; - gobject_class->set_property = polkit_backend_js_authority_set_property; - gobject_class->constructed = polkit_backend_js_authority_constructed; - - authority_class = POLKIT_BACKEND_AUTHORITY_CLASS (klass); - authority_class->get_name = polkit_backend_js_authority_get_name; - authority_class->get_version = polkit_backend_js_authority_get_version; - authority_class->get_features = polkit_backend_js_authority_get_features; - - interactive_authority_class = POLKIT_BACKEND_INTERACTIVE_AUTHORITY_CLASS (klass); - interactive_authority_class->get_admin_identities = polkit_backend_js_authority_get_admin_auth_identities; - interactive_authority_class->check_authorization_sync = polkit_backend_js_authority_check_authorization_sync; - - g_object_class_install_property (gobject_class, - PROP_RULES_DIRS, - g_param_spec_boxed ("rules-dirs", - NULL, - NULL, - G_TYPE_STRV, - GParamFlags(G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE))); - + polkit_backend_common_js_authority_class_init_common (klass); g_type_class_add_private (klass, sizeof (PolkitBackendJsAuthorityPrivate)); - JS_Init (); } @@ -1099,15 +923,15 @@ call_js_function_with_runaway_killer (PolkitBackendJsAuthority *authority, /* ---------------------------------------------------------------------------------------------------- */ -static GList * -polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *_authority, - PolkitSubject *caller, - PolkitSubject *subject, - PolkitIdentity *user_for_subject, - gboolean subject_is_local, - gboolean subject_is_active, - const gchar *action_id, - PolkitDetails *details) +GList * +polkit_backend_common_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *_authority, + PolkitSubject *caller, + PolkitSubject *subject, + PolkitIdentity *user_for_subject, + gboolean subject_is_local, + gboolean subject_is_active, + const gchar *action_id, + PolkitDetails *details) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (_authority); GList *ret = NULL; @@ -1202,16 +1026,16 @@ polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveA /* ---------------------------------------------------------------------------------------------------- */ -static PolkitImplicitAuthorization -polkit_backend_js_authority_check_authorization_sync (PolkitBackendInteractiveAuthority *_authority, - PolkitSubject *caller, - PolkitSubject *subject, - PolkitIdentity *user_for_subject, - gboolean subject_is_local, - gboolean subject_is_active, - const gchar *action_id, - PolkitDetails *details, - PolkitImplicitAuthorization implicit) +PolkitImplicitAuthorization +polkit_backend_common_js_authority_check_authorization_sync (PolkitBackendInteractiveAuthority *_authority, + PolkitSubject *caller, + PolkitSubject *subject, + PolkitIdentity *user_for_subject, + gboolean subject_is_local, + gboolean subject_is_active, + const gchar *action_id, + PolkitDetails *details, + PolkitImplicitAuthorization implicit) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (_authority); PolkitImplicitAuthorization ret = implicit; @@ -1324,65 +1148,6 @@ js_polkit_log (JSContext *cx, /* ---------------------------------------------------------------------------------------------------- */ -static const gchar * -get_signal_name (gint signal_number) -{ - switch (signal_number) - { -#define _HANDLE_SIG(sig) case sig: return #sig; - _HANDLE_SIG (SIGHUP); - _HANDLE_SIG (SIGINT); - _HANDLE_SIG (SIGQUIT); - _HANDLE_SIG (SIGILL); - _HANDLE_SIG (SIGABRT); - _HANDLE_SIG (SIGFPE); - _HANDLE_SIG (SIGKILL); - _HANDLE_SIG (SIGSEGV); - _HANDLE_SIG (SIGPIPE); - _HANDLE_SIG (SIGALRM); - _HANDLE_SIG (SIGTERM); - _HANDLE_SIG (SIGUSR1); - _HANDLE_SIG (SIGUSR2); - _HANDLE_SIG (SIGCHLD); - _HANDLE_SIG (SIGCONT); - _HANDLE_SIG (SIGSTOP); - _HANDLE_SIG (SIGTSTP); - _HANDLE_SIG (SIGTTIN); - _HANDLE_SIG (SIGTTOU); - _HANDLE_SIG (SIGBUS); -#ifdef SIGPOLL - _HANDLE_SIG (SIGPOLL); -#endif - _HANDLE_SIG (SIGPROF); - _HANDLE_SIG (SIGSYS); - _HANDLE_SIG (SIGTRAP); - _HANDLE_SIG (SIGURG); - _HANDLE_SIG (SIGVTALRM); - _HANDLE_SIG (SIGXCPU); - _HANDLE_SIG (SIGXFSZ); -#undef _HANDLE_SIG - default: - break; - } - return "UNKNOWN_SIGNAL"; -} - -typedef struct -{ - GMainLoop *loop; - GAsyncResult *res; -} SpawnData; - -static void -spawn_cb (GObject *source_object, - GAsyncResult *res, - gpointer user_data) -{ - SpawnData *data = (SpawnData *)user_data; - data->res = (GAsyncResult*)g_object_ref (res); - g_main_loop_quit (data->loop); -} - static bool js_polkit_spawn (JSContext *cx, unsigned js_argc, @@ -1440,21 +1205,21 @@ js_polkit_spawn (JSContext *cx, g_main_context_push_thread_default (context); data.loop = loop; - utils_spawn ((const gchar *const *) argv, - 10, /* timeout_seconds */ - NULL, /* cancellable */ - spawn_cb, - &data); + polkit_backend_common_spawn ((const gchar *const *) argv, + 10, /* timeout_seconds */ + NULL, /* cancellable */ + polkit_backend_common_spawn_cb, + &data); g_main_loop_run (loop); g_main_context_pop_thread_default (context); - if (!utils_spawn_finish (data.res, - &exit_status, - &standard_output, - &standard_error, - &error)) + if (!polkit_backend_common_spawn_finish (data.res, + &exit_status, + &standard_output, + &standard_error, + &error)) { JS_ReportErrorUTF8 (cx, "Error spawning helper: %s (%s, %d)", @@ -1477,7 +1242,7 @@ js_polkit_spawn (JSContext *cx, { g_string_append_printf (gstr, "Helper was signaled with signal %s (%d)", - get_signal_name (WTERMSIG (exit_status)), + polkit_backend_common_get_signal_name (WTERMSIG (exit_status)), WTERMSIG (exit_status)); } g_string_append_printf (gstr, ", stdout=`%s', stderr=`%s'", @@ -1542,381 +1307,5 @@ js_polkit_user_is_in_netgroup (JSContext *cx, return ret; } - - /* ---------------------------------------------------------------------------------------------------- */ -typedef struct -{ - GSimpleAsyncResult *simple; /* borrowed reference */ - GMainContext *main_context; /* may be NULL */ - - GCancellable *cancellable; /* may be NULL */ - gulong cancellable_handler_id; - - GPid child_pid; - gint child_stdout_fd; - gint child_stderr_fd; - - GIOChannel *child_stdout_channel; - GIOChannel *child_stderr_channel; - - GSource *child_watch_source; - GSource *child_stdout_source; - GSource *child_stderr_source; - - guint timeout_seconds; - gboolean timed_out; - GSource *timeout_source; - - GString *child_stdout; - GString *child_stderr; - - gint exit_status; -} UtilsSpawnData; - -static void -utils_child_watch_from_release_cb (GPid pid, - gint status, - gpointer user_data) -{ -} - -static void -utils_spawn_data_free (UtilsSpawnData *data) -{ - if (data->timeout_source != NULL) - { - g_source_destroy (data->timeout_source); - data->timeout_source = NULL; - } - - /* Nuke the child, if necessary */ - if (data->child_watch_source != NULL) - { - g_source_destroy (data->child_watch_source); - data->child_watch_source = NULL; - } - - if (data->child_pid != 0) - { - GSource *source; - kill (data->child_pid, SIGTERM); - /* OK, we need to reap for the child ourselves - we don't want - * to use waitpid() because that might block the calling - * thread (the child might handle SIGTERM and use several - * seconds for cleanup/rollback). - * - * So we use GChildWatch instead. - * - * Avoid taking a references to ourselves. but note that we need - * to pass the GSource so we can nuke it once handled. - */ - source = g_child_watch_source_new (data->child_pid); - g_source_set_callback (source, - (GSourceFunc) utils_child_watch_from_release_cb, - source, - (GDestroyNotify) g_source_destroy); - /* attach source to the global default main context */ - g_source_attach (source, NULL); - g_source_unref (source); - data->child_pid = 0; - } - - if (data->child_stdout != NULL) - { - g_string_free (data->child_stdout, TRUE); - data->child_stdout = NULL; - } - - if (data->child_stderr != NULL) - { - g_string_free (data->child_stderr, TRUE); - data->child_stderr = NULL; - } - - if (data->child_stdout_channel != NULL) - { - g_io_channel_unref (data->child_stdout_channel); - data->child_stdout_channel = NULL; - } - if (data->child_stderr_channel != NULL) - { - g_io_channel_unref (data->child_stderr_channel); - data->child_stderr_channel = NULL; - } - - if (data->child_stdout_source != NULL) - { - g_source_destroy (data->child_stdout_source); - data->child_stdout_source = NULL; - } - if (data->child_stderr_source != NULL) - { - g_source_destroy (data->child_stderr_source); - data->child_stderr_source = NULL; - } - - if (data->child_stdout_fd != -1) - { - g_warn_if_fail (close (data->child_stdout_fd) == 0); - data->child_stdout_fd = -1; - } - if (data->child_stderr_fd != -1) - { - g_warn_if_fail (close (data->child_stderr_fd) == 0); - data->child_stderr_fd = -1; - } - - if (data->cancellable_handler_id > 0) - { - g_cancellable_disconnect (data->cancellable, data->cancellable_handler_id); - data->cancellable_handler_id = 0; - } - - if (data->main_context != NULL) - g_main_context_unref (data->main_context); - - if (data->cancellable != NULL) - g_object_unref (data->cancellable); - - g_slice_free (UtilsSpawnData, data); -} - -/* called in the thread where @cancellable was cancelled */ -static void -utils_on_cancelled (GCancellable *cancellable, - gpointer user_data) -{ - UtilsSpawnData *data = (UtilsSpawnData *)user_data; - GError *error; - - error = NULL; - g_warn_if_fail (g_cancellable_set_error_if_cancelled (cancellable, &error)); - g_simple_async_result_take_error (data->simple, error); - g_simple_async_result_complete_in_idle (data->simple); - g_object_unref (data->simple); -} - -static gboolean -utils_read_child_stderr (GIOChannel *channel, - GIOCondition condition, - gpointer user_data) -{ - UtilsSpawnData *data = (UtilsSpawnData *)user_data; - gchar buf[1024]; - gsize bytes_read; - - g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); - g_string_append_len (data->child_stderr, buf, bytes_read); - return TRUE; -} - -static gboolean -utils_read_child_stdout (GIOChannel *channel, - GIOCondition condition, - gpointer user_data) -{ - UtilsSpawnData *data = (UtilsSpawnData *)user_data; - gchar buf[1024]; - gsize bytes_read; - - g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); - g_string_append_len (data->child_stdout, buf, bytes_read); - return TRUE; -} - -static void -utils_child_watch_cb (GPid pid, - gint status, - gpointer user_data) -{ - UtilsSpawnData *data = (UtilsSpawnData *)user_data; - gchar *buf; - gsize buf_size; - - if (g_io_channel_read_to_end (data->child_stdout_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) - { - g_string_append_len (data->child_stdout, buf, buf_size); - g_free (buf); - } - if (g_io_channel_read_to_end (data->child_stderr_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) - { - g_string_append_len (data->child_stderr, buf, buf_size); - g_free (buf); - } - - data->exit_status = status; - - /* ok, child watch is history, make sure we don't free it in spawn_data_free() */ - data->child_pid = 0; - data->child_watch_source = NULL; - - /* we're done */ - g_simple_async_result_complete_in_idle (data->simple); - g_object_unref (data->simple); -} - -static gboolean -utils_timeout_cb (gpointer user_data) -{ - UtilsSpawnData *data = (UtilsSpawnData *)user_data; - - data->timed_out = TRUE; - - /* ok, timeout is history, make sure we don't free it in spawn_data_free() */ - data->timeout_source = NULL; - - /* we're done */ - g_simple_async_result_complete_in_idle (data->simple); - g_object_unref (data->simple); - - return FALSE; /* remove source */ -} - -static void -utils_spawn (const gchar *const *argv, - guint timeout_seconds, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - UtilsSpawnData *data; - GError *error; - - data = g_slice_new0 (UtilsSpawnData); - data->timeout_seconds = timeout_seconds; - data->simple = g_simple_async_result_new (NULL, - callback, - user_data, - (gpointer*)utils_spawn); - data->main_context = g_main_context_get_thread_default (); - if (data->main_context != NULL) - g_main_context_ref (data->main_context); - - data->cancellable = cancellable != NULL ? (GCancellable*)g_object_ref (cancellable) : NULL; - - data->child_stdout = g_string_new (NULL); - data->child_stderr = g_string_new (NULL); - data->child_stdout_fd = -1; - data->child_stderr_fd = -1; - - /* the life-cycle of UtilsSpawnData is tied to its GSimpleAsyncResult */ - g_simple_async_result_set_op_res_gpointer (data->simple, data, (GDestroyNotify) utils_spawn_data_free); - - error = NULL; - if (data->cancellable != NULL) - { - /* could already be cancelled */ - error = NULL; - if (g_cancellable_set_error_if_cancelled (data->cancellable, &error)) - { - g_simple_async_result_take_error (data->simple, error); - g_simple_async_result_complete_in_idle (data->simple); - g_object_unref (data->simple); - goto out; - } - - data->cancellable_handler_id = g_cancellable_connect (data->cancellable, - G_CALLBACK (utils_on_cancelled), - data, - NULL); - } - - error = NULL; - if (!g_spawn_async_with_pipes (NULL, /* working directory */ - (gchar **) argv, - NULL, /* envp */ - GSpawnFlags(G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD), - NULL, /* child_setup */ - NULL, /* child_setup's user_data */ - &(data->child_pid), - NULL, /* gint *stdin_fd */ - &(data->child_stdout_fd), - &(data->child_stderr_fd), - &error)) - { - g_prefix_error (&error, "Error spawning: "); - g_simple_async_result_take_error (data->simple, error); - g_simple_async_result_complete_in_idle (data->simple); - g_object_unref (data->simple); - goto out; - } - - if (timeout_seconds > 0) - { - data->timeout_source = g_timeout_source_new_seconds (timeout_seconds); - g_source_set_priority (data->timeout_source, G_PRIORITY_DEFAULT); - g_source_set_callback (data->timeout_source, utils_timeout_cb, data, NULL); - g_source_attach (data->timeout_source, data->main_context); - g_source_unref (data->timeout_source); - } - - data->child_watch_source = g_child_watch_source_new (data->child_pid); - g_source_set_callback (data->child_watch_source, (GSourceFunc) utils_child_watch_cb, data, NULL); - g_source_attach (data->child_watch_source, data->main_context); - g_source_unref (data->child_watch_source); - - data->child_stdout_channel = g_io_channel_unix_new (data->child_stdout_fd); - g_io_channel_set_flags (data->child_stdout_channel, G_IO_FLAG_NONBLOCK, NULL); - data->child_stdout_source = g_io_create_watch (data->child_stdout_channel, G_IO_IN); - g_source_set_callback (data->child_stdout_source, (GSourceFunc) utils_read_child_stdout, data, NULL); - g_source_attach (data->child_stdout_source, data->main_context); - g_source_unref (data->child_stdout_source); - - data->child_stderr_channel = g_io_channel_unix_new (data->child_stderr_fd); - g_io_channel_set_flags (data->child_stderr_channel, G_IO_FLAG_NONBLOCK, NULL); - data->child_stderr_source = g_io_create_watch (data->child_stderr_channel, G_IO_IN); - g_source_set_callback (data->child_stderr_source, (GSourceFunc) utils_read_child_stderr, data, NULL); - g_source_attach (data->child_stderr_source, data->main_context); - g_source_unref (data->child_stderr_source); - - out: - ; -} - -gboolean -utils_spawn_finish (GAsyncResult *res, - gint *out_exit_status, - gchar **out_standard_output, - gchar **out_standard_error, - GError **error) -{ - GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); - UtilsSpawnData *data; - gboolean ret = FALSE; - - g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - - g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == utils_spawn); - - if (g_simple_async_result_propagate_error (simple, error)) - goto out; - - data = (UtilsSpawnData*)g_simple_async_result_get_op_res_gpointer (simple); - - if (data->timed_out) - { - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_TIMED_OUT, - "Timed out after %d seconds", - data->timeout_seconds); - goto out; - } - - if (out_exit_status != NULL) - *out_exit_status = data->exit_status; - - if (out_standard_output != NULL) - *out_standard_output = g_strdup (data->child_stdout->str); - - if (out_standard_error != NULL) - *out_standard_error = g_strdup (data->child_stderr->str); - - ret = TRUE; - - out: - return ret; -} -- GitLab From 4858128107be9c3ab11828ee8f35c5e26efd36ce Mon Sep 17 00:00:00 2001 From: Gustavo Lima Chaves Date: Tue, 14 Sep 2021 14:38:15 -0700 Subject: [PATCH 15/16] Gitlab CI: add duktape pkgconfig dependency Make way for the CI to be able to build with duktape too Signed-off-by: Gustavo Lima Chaves --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) GitLab From cd5d6da837fce95f8831a355dad88c83347c7337 Mon Sep 17 00:00:00 2001 From: Gustavo Lima Chaves Date: Mon, 20 Sep 2021 17:17:26 -0700 Subject: [PATCH 16/16] duktape: implement runaway scripts killer timeout This was missing on Duktape's JS backend proposal, now in. As discussed in https://gitlab.freedesktop.org/polkit/polkit/-/merge_requests/35 and verified by the commit author, Duktape has no interrupt injection mechanism (it has no thread-safe API entry whatsoever, even). Using DUK_USE_EXEC_TIMEOUT_CHECK is also not feasible, because: i) It must be enabled at build time and shared object builds of the lib on distros go with the default options, something we cannot change/control ii) That does not account for non-ECMAScript explicit execution contexts, like regex execution, native C calls, etc. It has been agreed, on that thread, that pthread_cond_timedwait()-ing and having proper Duktape evaluation/execution calls take place in a separate thread, to be killed after the runaway script killer's accorded timeout value, a reasonable approach. We have considered using glib wrappers for direct pthread usage, but that way would make it impossible to issue pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, ...) and we want to be paranoid in that regard. On Duktape, we don't get to err from the JS context (to be captured by the offending script), but to be forcibly killed on timeout scenarios, leading to null returns, thus polkit negation, by definition. It's a reasonable design/compromise. A fatal error handler routine, for the Duktape context, has also been added, using the polkit_backend_authority_log() logging infra to better assist users on what went wrong. Finally, the script evaluation routine has been made to use duk_peval_lstring() (previously using _noresult variant), so to able to present the user with proper error messages, should any occur. The original runaway script killer test has been adjusted to please both JS backends. Signed-off-by: Gustavo Lima Chaves --- meson.build | 1 + src/polkitbackend/meson.build | 1 + src/polkitbackend/polkitbackendcommon.h | 2 + .../polkitbackendduktapeauthority.c | 236 ++++++++++++++---- .../polkitbackendjsauthority.cpp | 10 +- .../etc/polkit-1/rules.d/10-testing.rules | 6 +- .../test-polkitbackendjsauthority.c | 2 +- 7 files changed, 209 insertions(+), 49 deletions(-) diff --git a/meson.build b/meson.build index 4e44723..46956e3 100644 --- a/meson.build +++ b/meson.build @@ -137,6 +137,7 @@ js_engine = get_option('js_engine') if js_engine == 'duktape' js_dep = dependency('duktape') libm_dep = cc.find_library('m') + libpthread_dep = cc.find_library('pthread') elif js_engine == 'mozjs' js_dep = dependency('mozjs-78') endif diff --git a/src/polkitbackend/meson.build b/src/polkitbackend/meson.build index 9ec01b2..4dfea39 100644 --- a/src/polkitbackend/meson.build +++ b/src/polkitbackend/meson.build @@ -34,6 +34,7 @@ c_flags = [ if js_engine == 'duktape' sources += files('polkitbackendduktapeauthority.c') deps += libm_dep + deps += libpthread_dep elif js_engine == 'mozjs' sources += files('polkitbackendjsauthority.cpp') endif diff --git a/src/polkitbackend/polkitbackendcommon.h b/src/polkitbackend/polkitbackendcommon.h index 6d0d267..dd700fc 100644 --- a/src/polkitbackend/polkitbackendcommon.h +++ b/src/polkitbackend/polkitbackendcommon.h @@ -50,6 +50,8 @@ #include #endif /* HAVE_LIBSYSTEMD */ +#define RUNAWAY_KILLER_TIMEOUT (15) + #ifdef __cplusplus extern "C" { #endif diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index a2b4420..80f1976 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -47,8 +47,20 @@ struct _PolkitBackendJsAuthorityPrivate GFileMonitor **dir_monitors; /* NULL-terminated array of GFileMonitor instances */ duk_context *cx; + + pthread_t runaway_killer_thread; +}; + +enum +{ + RUNAWAY_KILLER_THREAD_EXIT_STATUS_UNSET, + RUNAWAY_KILLER_THREAD_EXIT_STATUS_SUCCESS, + RUNAWAY_KILLER_THREAD_EXIT_STATUS_FAILURE, }; +static gboolean execute_script_with_runaway_killer(PolkitBackendJsAuthority *authority, + const gchar *filename); + /* ---------------------------------------------------------------------------------------------------- */ G_DEFINE_TYPE (PolkitBackendJsAuthority, polkit_backend_js_authority, POLKIT_BACKEND_TYPE_INTERACTIVE_AUTHORITY); @@ -67,6 +79,15 @@ static const duk_function_list_entry js_polkit_functions[] = { NULL, NULL, 0 }, }; +static void report_error (void *udata, + const char *msg) +{ + PolkitBackendJsAuthority *authority = udata; + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "fatal Duktape JS backend error: %s", + (msg ? msg : "no message")); +} + static void polkit_backend_js_authority_init (PolkitBackendJsAuthority *authority) { @@ -78,7 +99,6 @@ polkit_backend_js_authority_init (PolkitBackendJsAuthority *authority) static void load_scripts (PolkitBackendJsAuthority *authority) { - duk_context *cx = authority->priv->cx; GList *files = NULL; GList *l; guint num_scripts = 0; @@ -123,36 +143,9 @@ load_scripts (PolkitBackendJsAuthority *authority) for (l = files; l != NULL; l = l->next) { const gchar *filename = (gchar *)l->data; -#if (DUK_VERSION >= 20000) - GFile *file = g_file_new_for_path (filename); - char *contents; - gsize len; - if (!g_file_load_contents (file, NULL, &contents, &len, NULL, NULL)) - { - polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), - "Error compiling script %s", - filename); - g_object_unref (file); - continue; - } - g_object_unref (file); - if (duk_peval_lstring_noresult(cx, contents,len) != 0) -#else - if (duk_peval_file_noresult (cx, filename) != 0) -#endif - { - polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), - "Error compiling script %s: %s", - filename, duk_safe_to_string (authority->priv->cx, -1)); -#if (DUK_VERSION >= 20000) - g_free (contents); -#endif + if (!execute_script_with_runaway_killer(authority, filename)) continue; - } -#if (DUK_VERSION >= 20000) - g_free (contents); -#endif num_scripts++; } @@ -232,7 +225,7 @@ polkit_backend_common_js_authority_constructed (GObject *object) PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); duk_context *cx; - cx = duk_create_heap (NULL, NULL, NULL, authority, NULL); + cx = duk_create_heap (NULL, NULL, NULL, authority, report_error); if (cx == NULL) goto fail; @@ -243,6 +236,9 @@ polkit_backend_common_js_authority_constructed (GObject *object) duk_put_function_list (cx, -1, js_polkit_functions); duk_put_prop_string (cx, -2, "polkit"); + /* load polkit objects/functions into JS context (e.g. addRule(), + * _deleteRules(), _runRules() et al) + */ duk_eval_string (cx, init_js); if (authority->priv->rules_dirs == NULL) @@ -510,6 +506,167 @@ push_action_and_details (duk_context *cx, /* ---------------------------------------------------------------------------------------------------- */ +typedef struct { + PolkitBackendJsAuthority *authority; + const gchar *filename; + pthread_cond_t cond; + pthread_mutex_t mutex; + gint ret; +} RunawayKillerCtx; + +static gpointer +runaway_killer_thread_execute_js (gpointer user_data) +{ + RunawayKillerCtx *ctx = user_data; + duk_context *cx = ctx->authority->priv->cx; + + int oldtype; + + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype); + +#if (DUK_VERSION >= 20000) + GFile *file = g_file_new_for_path(ctx->filename); + char *contents; + gsize len; + + if (!g_file_load_contents(file, NULL, &contents, &len, NULL, NULL)) { + polkit_backend_authority_log(POLKIT_BACKEND_AUTHORITY(ctx->authority), + "Error compiling script %s", ctx->filename); + g_object_unref(file); + goto err; + } + + g_object_unref(file); + + /* evaluate the script, trying to print context in any syntax errors + found */ + if (duk_peval_lstring(cx, contents, len) != 0) +#else + if (duk_peval_file(cx, ctx->filename) != 0) +#endif + { + polkit_backend_authority_log(POLKIT_BACKEND_AUTHORITY(ctx->authority), + "Error compiling script %s: %s", ctx->filename, + duk_safe_to_string(cx, -1)); + duk_pop(cx); + goto free_err; + } +#if (DUK_VERSION >= 20000) + g_free(contents); +#endif + + ctx->ret = RUNAWAY_KILLER_THREAD_EXIT_STATUS_SUCCESS; + goto end; + +free_err: +#if (DUK_VERSION >= 20000) + g_free(contents); +#endif +err: + ctx->ret = RUNAWAY_KILLER_THREAD_EXIT_STATUS_FAILURE; +end: + pthread_cond_signal(&ctx->cond); + return NULL; +} + +static gpointer +runaway_killer_thread_call_js (gpointer user_data) +{ + RunawayKillerCtx *ctx = user_data; + duk_context *cx = ctx->authority->priv->cx; + int oldtype; + + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype); + + if (duk_pcall_prop (cx, 0, 2) != DUK_EXEC_SUCCESS) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (ctx->authority), + "Error evaluating admin rules: ", + duk_safe_to_string (cx, -1)); + goto err; + } + + ctx->ret = RUNAWAY_KILLER_THREAD_EXIT_STATUS_SUCCESS; + goto end; + +err: + ctx->ret = RUNAWAY_KILLER_THREAD_EXIT_STATUS_FAILURE; +end: + pthread_cond_signal(&ctx->cond); + return NULL; +} + +/* Blocking for at most for RUNAWAY_KILLER_TIMEOUT */ +static gboolean +execute_script_with_runaway_killer(PolkitBackendJsAuthority *authority, + const gchar *filename) +{ + gint64 end_time; + gboolean cancel = FALSE; + RunawayKillerCtx ctx = {.authority = authority, .filename = filename, + .ret = RUNAWAY_KILLER_THREAD_EXIT_STATUS_UNSET, + .mutex = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER}; + struct timespec abs_time; + + pthread_mutex_lock(&ctx.mutex); + + clock_gettime(CLOCK_REALTIME, &abs_time); + abs_time.tv_sec += RUNAWAY_KILLER_TIMEOUT; + + pthread_create(&authority->priv->runaway_killer_thread, NULL, runaway_killer_thread_execute_js, &ctx); + + while (ctx.ret == RUNAWAY_KILLER_THREAD_EXIT_STATUS_UNSET) /* loop to treat spurious wakeups */ + if (pthread_cond_timedwait(&ctx.cond, &ctx.mutex, &abs_time) == ETIMEDOUT) { + cancel = TRUE; + break; + } + + pthread_mutex_unlock(&ctx.mutex); + + if (cancel) + pthread_cancel (authority->priv->runaway_killer_thread); + pthread_join (authority->priv->runaway_killer_thread, NULL); + + return ctx.ret == RUNAWAY_KILLER_THREAD_EXIT_STATUS_SUCCESS; +} + +/* Calls already stacked function and args. Blocking for at most for + * RUNAWAY_KILLER_TIMEOUT + */ +static gboolean +call_js_function_with_runaway_killer(PolkitBackendJsAuthority *authority) +{ + gint64 end_time; + gboolean cancel = FALSE; + RunawayKillerCtx ctx = {.authority = authority, + .ret = RUNAWAY_KILLER_THREAD_EXIT_STATUS_UNSET, + .mutex = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER}; + struct timespec abs_time; + + pthread_mutex_lock(&ctx.mutex); + + clock_gettime(CLOCK_REALTIME, &abs_time); + abs_time.tv_sec += RUNAWAY_KILLER_TIMEOUT; + + pthread_create(&authority->priv->runaway_killer_thread, NULL, runaway_killer_thread_call_js, &ctx); + + while (ctx.ret == RUNAWAY_KILLER_THREAD_EXIT_STATUS_UNSET) /* loop to treat spurious wakeups */ + if (pthread_cond_timedwait(&ctx.cond, &ctx.mutex, &abs_time) == ETIMEDOUT) { + cancel = TRUE; + break; + } + + pthread_mutex_unlock(&ctx.mutex); + + if (cancel) + pthread_cancel (authority->priv->runaway_killer_thread); + pthread_join (authority->priv->runaway_killer_thread, NULL); + + return ctx.ret == RUNAWAY_KILLER_THREAD_EXIT_STATUS_SUCCESS; +} + /* ---------------------------------------------------------------------------------------------------- */ GList * @@ -557,13 +714,8 @@ polkit_backend_common_js_authority_get_admin_auth_identities (PolkitBackendInter goto out; } - if (duk_pcall_prop (cx, 0, 2) != DUK_ERR_NONE) - { - polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), - "Error evaluating admin rules: ", - duk_safe_to_string (cx, -1)); - goto out; - } + if (!call_js_function_with_runaway_killer (authority)) + goto out; ret_str = duk_require_string (cx, -1); @@ -643,15 +795,11 @@ polkit_backend_common_js_authority_check_authorization_sync (PolkitBackendIntera goto out; } - if (duk_pcall_prop (cx, 0, 2) != DUK_ERR_NONE) - { - polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), - "Error evaluating authorization rules: ", - duk_safe_to_string (cx, -1)); - goto out; - } + if (!call_js_function_with_runaway_killer (authority)) + goto out; if (duk_is_null(cx, -1)) { + /* this fine, means there was no match, use implicit authorizations */ good = TRUE; goto out; } diff --git a/src/polkitbackend/polkitbackendjsauthority.cpp b/src/polkitbackend/polkitbackendjsauthority.cpp index e28091d..11e91c0 100644 --- a/src/polkitbackend/polkitbackendjsauthority.cpp +++ b/src/polkitbackend/polkitbackendjsauthority.cpp @@ -829,11 +829,14 @@ runaway_killer_setup (PolkitBackendJsAuthority *authority) { g_assert (authority->priv->rkt_source == NULL); - /* set-up timer for runaway scripts, will be executed in runaway_killer_thread */ + /* set-up timer for runaway scripts, will be executed in + runaway_killer_thread, that is one, permanent thread running a glib + mainloop (rkt_loop) whose context (rkt_context) has a timeout source + (rkt_source) */ g_mutex_lock (&authority->priv->rkt_timeout_pending_mutex); authority->priv->rkt_timeout_pending = FALSE; g_mutex_unlock (&authority->priv->rkt_timeout_pending_mutex); - authority->priv->rkt_source = g_timeout_source_new_seconds (15); + authority->priv->rkt_source = g_timeout_source_new_seconds (RUNAWAY_KILLER_TIMEOUT); g_source_set_callback (authority->priv->rkt_source, rkt_on_timeout, authority, NULL); g_source_attach (authority->priv->rkt_source, authority->priv->rkt_context); @@ -893,6 +896,9 @@ execute_script_with_runaway_killer (PolkitBackendJsAuthority *authority, { bool ret; + // tries to JS_ExecuteScript(), may hang for > RUNAWAY_KILLER_TIMEOUT, + // runaway_killer_thread makes sure the call returns, due to exception + // injection runaway_killer_setup (authority); ret = JS_ExecuteScript (authority->priv->cx, script, diff --git a/test/data/etc/polkit-1/rules.d/10-testing.rules b/test/data/etc/polkit-1/rules.d/10-testing.rules index 98bf062..e346b5d 100644 --- a/test/data/etc/polkit-1/rules.d/10-testing.rules +++ b/test/data/etc/polkit-1/rules.d/10-testing.rules @@ -189,8 +189,10 @@ polkit.addRule(function(action, subject) { ; } catch (error) { if (error == "Terminating runaway script") - return polkit.Result.YES; - return polkit.Result.NO; + // Inverted logic to accomodate Duktape's model as well, which + // will always fail with negation, on timeouts + return polkit.Result.NO; + return polkit.Result.YES; } } }); diff --git a/test/polkitbackend/test-polkitbackendjsauthority.c b/test/polkitbackend/test-polkitbackendjsauthority.c index f97e0e0..2103b17 100644 --- a/test/polkitbackend/test-polkitbackendjsauthority.c +++ b/test/polkitbackend/test-polkitbackendjsauthority.c @@ -328,7 +328,7 @@ static const RulesTestCase rules_test_cases[] = { "net.company.run_away_script", "unix-user:root", NULL, - POLKIT_IMPLICIT_AUTHORIZATION_AUTHORIZED, + POLKIT_IMPLICIT_AUTHORIZATION_NOT_AUTHORIZED, }, { -- GitLab