diff --git a/gnu/local.mk b/gnu/local.mk index 82bb9bdf6e..0952b28ea7 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -1621,6 +1621,7 @@ dist_patch_DATA = \ %D%/packages/patches/plib-CVE-2011-4620.patch \ %D%/packages/patches/plib-CVE-2012-4552.patch \ %D%/packages/patches/plotutils-spline-test.patch \ + %D%/packages/patches/polkit-use-duktape.patch \ %D%/packages/patches/portaudio-audacity-compat.patch \ %D%/packages/patches/portmidi-modular-build.patch \ %D%/packages/patches/postgresql-disable-resolve_symlinks.patch \ diff --git a/gnu/packages/patches/polkit-use-duktape.patch b/gnu/packages/patches/polkit-use-duktape.patch new file mode 100644 index 0000000000..4eaa7963c2 --- /dev/null +++ b/gnu/packages/patches/polkit-use-duktape.patch @@ -0,0 +1,5030 @@ +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 + diff --git a/gnu/packages/polkit.scm b/gnu/packages/polkit.scm index ef58f36d6c..38f8adf9cd 100644 --- a/gnu/packages/polkit.scm +++ b/gnu/packages/polkit.scm @@ -8,6 +8,7 @@ ;;; Copyright © 2018 Tobias Geerinckx-Rice ;;; Copyright © 2018 Ricardo Wurmus ;;; Copyright © 2021 Morgan Smith +;;; Copyright © 2021 Maxim Cournoyer ;;; ;;; This file is part of GNU Guix. ;;; @@ -28,14 +29,18 @@ (define-module (gnu packages polkit) #:use-module ((guix licenses) #:select (lgpl2.0+)) #:use-module (guix packages) #:use-module (guix download) + #:use-module (guix utils) + #:use-module (guix build utils) #:use-module (guix build-system cmake) #:use-module (guix build-system gnu) #:use-module (gnu packages) + #:use-module (gnu packages autotools) #:use-module (gnu packages freedesktop) #:use-module (gnu packages glib) #:use-module (gnu packages gtk) #:use-module (gnu packages docbook) #:use-module (gnu packages gnuzilla) + #:use-module (gnu packages javascript) #:use-module (gnu packages linux) #:use-module (gnu packages nss) #:use-module (gnu packages perl) @@ -63,8 +68,14 @@ (define-public polkit (substitute* "test/Makefile.in" (("SUBDIRS = mocklibc . polkit polkitbackend") "SUBDIRS = mocklibc . polkit")) - ;; Guix System's polkit service stores actions under - ;; /etc/polkit-1/actions. + ;; Disable a test that requires Python, D-Bus and a few + ;; libraries and fails with "ERROR: timed out waiting for bus + ;; process to terminate". + (substitute* "test/polkitbackend/Makefile.am" + (("TEST_PROGS \\+= polkitbackendjsauthoritytest-wrapper.py") + "")) + ;; Guix System's polkit + ;; service stores actions under /etc/polkit-1/actions. (substitute* "src/polkitbackend/polkitbackendinteractiveauthority.c" (("PACKAGE_DATA_DIR \"/polkit-1/actions\"") "PACKAGE_SYSCONF_DIR \"/polkit-1/actions\"")) @@ -136,6 +147,36 @@ (define-public polkit for unprivileged applications.") (license lgpl2.0+))) +;;; Variant of polkit built with Duktape, a lighter JavaScript engine compared +;;; to mozjs. +(define-public polkit-duktape + (package/inherit polkit + (name "polkit-duktape") + (source + (origin + (inherit (package-source polkit)) + (patches + (append + (search-patches "polkit-use-duktape.patch") + (origin-patches (package-source polkit)))))) + (arguments + (substitute-keyword-arguments (package-arguments polkit) + ((#:configure-flags flags) + `(cons "--with-duktape" ,flags)) + ((#:phases phases) + `(modify-phases ,phases + (add-after 'unpack 'force-gnu-build-system-bootstrap + (lambda _ + (delete-file "configure"))))))) + (native-inputs + (append `(("autoconf" ,autoconf) + ("automake" ,automake) + ("libtool" ,libtool) + ("pkg-config" ,pkg-config)) + (package-native-inputs polkit))) + (inputs (alist-replace "mozjs" `(,duktape) + (package-inputs polkit))))) + (define-public polkit-qt (package (name "polkit-qt")