PolicyKit: Branch 'master'
Colin Walters
walters at kemper.freedesktop.org
Mon Dec 12 15:17:13 UTC 2016
configure.ac | 3
src/polkitbackend/Makefile.am | 7
src/polkitbackend/polkitbackendjsauthority.c | 1919 -------------------------
src/polkitbackend/polkitbackendjsauthority.cpp | 1919 +++++++++++++++++++++++++
test/polkitbackend/Makefile.am | 3
5 files changed, 1931 insertions(+), 1920 deletions(-)
New commits:
commit 0676f33aa741a87634ffc3b9ef82c7d092c997ec
Author: Jeremy Linton <jeremy.linton at arm.com>
Date: Fri Aug 26 14:01:46 2016 -0500
change mozjs interface module to c++
The JSAPI is now a full C++ interface. Convert the polkit
to JavaScript interface module to C++ compilation in order to
support newer versions of spidermonkey.
Signed-off-by: Jeremy Linton <jeremy.linton at arm.com>
diff --git a/configure.ac b/configure.ac
index 97d4222..18c1f4e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -30,6 +30,8 @@ AC_PROG_MAKE_SET
AC_PROG_LN_S
AC_SYS_LARGEFILE
AM_PROG_CC_C_O
+AC_PROG_CXX
+AX_CXX_COMPILE_STDCXX_11()
# Taken from dbus
AC_ARG_ENABLE(ansi, [ --enable-ansi enable -ansi -pedantic gcc flags],enable_ansi=$enableval,enable_ansi=no)
@@ -142,6 +144,7 @@ AS_IF([test x${with_mozjs} != xauto], [
])
])
AC_SUBST(LIBJS_CFLAGS)
+AC_SUBST(LIBJS_CXXFLAGS)
AC_SUBST(LIBJS_LIBS)
EXPAT_LIB=""
diff --git a/src/polkitbackend/Makefile.am b/src/polkitbackend/Makefile.am
index a80ca36..5655d17 100644
--- a/src/polkitbackend/Makefile.am
+++ b/src/polkitbackend/Makefile.am
@@ -32,7 +32,7 @@ libpolkit_backend_1_la_SOURCES = \
polkitbackendprivate.h \
polkitbackendauthority.h polkitbackendauthority.c \
polkitbackendinteractiveauthority.h polkitbackendinteractiveauthority.c \
- polkitbackendjsauthority.h polkitbackendjsauthority.c \
+ polkitbackendjsauthority.h polkitbackendjsauthority.cpp \
polkitbackendactionpool.h polkitbackendactionpool.c \
polkitbackendactionlookup.h polkitbackendactionlookup.c \
$(NULL)
@@ -53,6 +53,8 @@ libpolkit_backend_1_la_CFLAGS = \
$(LIBJS_CFLAGS) \
$(NULL)
+libpolkit_backend_1_la_CXXFLAGS = $(libpolkit_backend_1_la_CFLAGS) -fpermissive
+
libpolkit_backend_1_la_LIBADD = \
$(GLIB_LIBS) \
$(LIBSYSTEMD_LIBS) \
@@ -73,6 +75,9 @@ polkitd_SOURCES = \
polkitd.c \
$(NULL)
+# force C++ link via dummy C++ file, (see GNU automake manual section 8.3.5)
+nodist_EXTRA_polkitd_SOURCES = dummy-force-cpp-link.cxx
+
polkitd_CFLAGS = \
-DPOLKIT_BACKEND_I_KNOW_API_IS_SUBJECT_TO_CHANGE \
-DG_LOG_DOMAIN=\"polkitd-1\" \
diff --git a/src/polkitbackend/polkitbackendjsauthority.c b/src/polkitbackend/polkitbackendjsauthority.c
deleted file mode 100644
index 2112868..0000000
--- a/src/polkitbackend/polkitbackendjsauthority.c
+++ /dev/null
@@ -1,1919 +0,0 @@
-/*
- * Copyright (C) 2008-2012 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 <davidz at redhat.com>
- */
-
-#include "config.h"
-#include <sys/wait.h>
-#include <errno.h>
-#include <pwd.h>
-#include <grp.h>
-#ifdef HAVE_NETGROUP_H
-#include <netgroup.h>
-#else
-#include <netdb.h>
-#endif
-#include <string.h>
-#include <glib/gstdio.h>
-#include <locale.h>
-#include <glib/gi18n-lib.h>
-
-#include <polkit/polkit.h>
-#include "polkitbackendjsauthority.h"
-
-#include <polkit/polkitprivate.h>
-
-#ifdef HAVE_LIBSYSTEMD
-#include <systemd/sd-login.h>
-#endif /* HAVE_LIBSYSTEMD */
-
-#include <jsapi.h>
-
-#include "initjs.h" /* init.js */
-
-#ifdef JSGC_USE_EXACT_ROOTING
-/* See https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/GC/Exact_Stack_Rooting
- * for more information about exact stack rooting.
- */
-#error "This code is not safe in SpiderMonkey exact stack rooting configurations"
-#endif
-
-/**
- * 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 */
-
- JSRuntime *rt;
- JSContext *cx;
- JSObject *js_global;
- JSObject *js_polkit;
-
- GThread *runaway_killer_thread;
- GMutex rkt_init_mutex;
- GCond rkt_init_cond;
- GMainContext *rkt_context;
- GMainLoop *rkt_loop;
- GSource *rkt_source;
- GMutex rkt_timeout_pending_mutex;
- gboolean rkt_timeout_pending;
-
- /* A list of JSObject instances */
- GList *scripts;
-};
-
-static JSBool execute_script_with_runaway_killer (PolkitBackendJsAuthority *authority,
-#if JS_VERSION == 186
- JSScript *script,
-#else
- JSObject *script,
-#endif
- jsval *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 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 JSClass js_global_class = {
- "global",
- JSCLASS_GLOBAL_FLAGS,
- JS_PropertyStub,
- JS_PropertyStub,
- JS_PropertyStub,
- JS_StrictPropertyStub,
- JS_EnumerateStub,
- JS_ResolveStub,
- JS_ConvertStub,
-#if JS_VERSION == 186
- NULL,
-#else
- JS_FinalizeStub,
-#endif
- JSCLASS_NO_OPTIONAL_MEMBERS
-};
-
-/* ---------------------------------------------------------------------------------------------------- */
-
-static JSClass js_polkit_class = {
- "Polkit",
- 0,
- JS_PropertyStub,
- JS_PropertyStub,
- JS_PropertyStub,
- JS_StrictPropertyStub,
- JS_EnumerateStub,
- JS_ResolveStub,
- JS_ConvertStub,
-#if JS_VERSION == 186
- NULL,
-#else
- JS_FinalizeStub,
-#endif
- JSCLASS_NO_OPTIONAL_MEMBERS
-};
-
-static JSBool js_polkit_log (JSContext *cx, unsigned argc, jsval *vp);
-static JSBool js_polkit_spawn (JSContext *cx, unsigned argc, jsval *vp);
-static JSBool js_polkit_user_is_in_netgroup (JSContext *cx, unsigned argc, jsval *vp);
-
-static JSFunctionSpec js_polkit_functions[] =
-{
- JS_FS("log", js_polkit_log, 0, 0),
- JS_FS("spawn", js_polkit_spawn, 0, 0),
- JS_FS("_userIsInNetGroup", js_polkit_user_is_in_netgroup, 0, 0),
- JS_FS_END
-};
-
-/* ---------------------------------------------------------------------------------------------------- */
-
-static void report_error (JSContext *cx,
- const char *message,
- JSErrorReport *report)
-{
- PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (JS_GetContextPrivate (cx));
- polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
- "%s:%u: %s",
- report->filename ? report->filename : "<no filename>",
- (unsigned int) report->lineno,
- message);
-}
-
-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;
-}
-
-/* authority->priv->cx must be within a request */
-static void
-load_scripts (PolkitBackendJsAuthority *authority)
-{
- 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 JS_VERSION == 186
- JSScript *script;
-#else
- JSObject *script;
-#endif
-
-#if JS_VERSION == 186
- script = JS_CompileUTF8File (authority->priv->cx,
- authority->priv->js_global,
- filename);
-
-#else
- script = JS_CompileFile (authority->priv->cx,
- authority->priv->js_global,
- filename);
-#endif
- if (script == NULL)
- {
- polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
- "Error compiling script %s",
- filename);
- continue;
- }
-
- /* evaluate the script */
- jsval rval;
- if (!execute_script_with_runaway_killer (authority,
- script,
- &rval))
- {
- polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
- "Error executing script %s",
- filename);
- continue;
- }
-
- //g_print ("Successfully loaded and evaluated script `%s'\n", filename);
-
- 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)
-{
- jsval argv[1] = {JSVAL_NULL};
- jsval rval = JSVAL_NULL;
-
- JS_BeginRequest (authority->priv->cx);
-
- if (!JS_CallFunctionName(authority->priv->cx,
- authority->priv->js_polkit,
- "_deleteRules",
- 0,
- argv,
- &rval))
- {
- polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
- "Error deleting old rules, not loading new ones");
- goto out;
- }
-
- polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
- "Collecting garbage unconditionally...");
-#if JS_VERSION == 186
- JS_GC (authority->priv->rt);
-#else
- JS_GC (authority->priv->cx);
-#endif
-
- load_scripts (authority);
-
- /* Let applications know we have new rules... */
- g_signal_emit_by_name (authority, "changed");
- out:
- JS_EndRequest (authority->priv->cx);
-}
-
-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 void
-polkit_backend_js_authority_constructed (GObject *object)
-{
- PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object);
- gboolean entered_request = FALSE;
-
- authority->priv->rt = JS_NewRuntime (8L * 1024L * 1024L);
- if (authority->priv->rt == NULL)
- goto fail;
-
- authority->priv->cx = JS_NewContext (authority->priv->rt, 8192);
- if (authority->priv->cx == NULL)
- goto fail;
-
- /* TODO: JIT'ing doesn't work will with killing runaway scripts... I think
- * this is just a SpiderMonkey bug. So disable the JIT for now.
- */
- JS_SetOptions (authority->priv->cx,
- JSOPTION_VAROBJFIX
- /* | JSOPTION_JIT | JSOPTION_METHODJIT*/);
- JS_SetVersion(authority->priv->cx, JSVERSION_LATEST);
- JS_SetErrorReporter(authority->priv->cx, report_error);
- JS_SetContextPrivate (authority->priv->cx, authority);
-
- JS_BeginRequest(authority->priv->cx);
- entered_request = TRUE;
-
- authority->priv->js_global =
-#if JS_VERSION == 186
- JS_NewGlobalObject (authority->priv->cx, &js_global_class, NULL);
-#else
- JS_NewCompartmentAndGlobalObject (authority->priv->cx, &js_global_class, NULL);
-#endif
-
- if (authority->priv->js_global == NULL)
- goto fail;
- JS_AddObjectRoot (authority->priv->cx, &authority->priv->js_global);
-
- if (!JS_InitStandardClasses (authority->priv->cx, authority->priv->js_global))
- goto fail;
-
- authority->priv->js_polkit = JS_DefineObject (authority->priv->cx,
- authority->priv->js_global,
- "polkit",
- &js_polkit_class,
- NULL,
- JSPROP_ENUMERATE);
- if (authority->priv->js_polkit == NULL)
- goto fail;
- JS_AddObjectRoot (authority->priv->cx, &authority->priv->js_polkit);
-
- if (!JS_DefineFunctions (authority->priv->cx,
- authority->priv->js_polkit,
- js_polkit_functions))
- goto fail;
-
- if (!JS_EvaluateScript (authority->priv->cx,
- authority->priv->js_global,
- init_js, strlen (init_js), /* init.js */
- "init.js", /* filename */
- 0, /* lineno */
- NULL)) /* rval */
- {
- goto fail;
- }
-
- 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");
- }
-
- g_mutex_init (&authority->priv->rkt_init_mutex);
- g_cond_init (&authority->priv->rkt_init_cond);
- g_mutex_init (&authority->priv->rkt_timeout_pending_mutex);
-
- authority->priv->runaway_killer_thread = g_thread_new ("runaway-killer-thread",
- runaway_killer_thread_func,
- authority);
-
- /* wait for runaway_killer_thread to set up its GMainContext */
- g_mutex_lock (&authority->priv->rkt_init_mutex);
- while (authority->priv->rkt_context == NULL)
- g_cond_wait (&authority->priv->rkt_init_cond, &authority->priv->rkt_init_mutex);
- g_mutex_unlock (&authority->priv->rkt_init_mutex);
-
- setup_file_monitors (authority);
- load_scripts (authority);
-
- JS_EndRequest (authority->priv->cx);
- entered_request = FALSE;
-
- G_OBJECT_CLASS (polkit_backend_js_authority_parent_class)->constructed (object);
- return;
-
- fail:
- if (entered_request)
- JS_EndRequest (authority->priv->cx);
- 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;
-
- g_mutex_clear (&authority->priv->rkt_init_mutex);
- g_cond_clear (&authority->priv->rkt_init_cond);
- g_mutex_clear (&authority->priv->rkt_timeout_pending_mutex);
-
- /* shut down the killer thread */
- g_assert (authority->priv->rkt_loop != NULL);
- g_main_loop_quit (authority->priv->rkt_loop);
- g_thread_join (authority->priv->runaway_killer_thread);
- g_assert (authority->priv->rkt_loop == NULL);
-
- 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);
-
- JS_BeginRequest (authority->priv->cx);
- JS_RemoveObjectRoot (authority->priv->cx, &authority->priv->js_polkit);
- JS_RemoveObjectRoot (authority->priv->cx, &authority->priv->js_global);
- JS_EndRequest (authority->priv->cx);
-
- JS_DestroyContext (authority->priv->cx);
- JS_DestroyRuntime (authority->priv->rt);
- /* JS_ShutDown (); */
-
- 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));
-}
-
-/* ---------------------------------------------------------------------------------------------------- */
-
-/* authority->priv->cx must be within a request */
-static void
-set_property_str (PolkitBackendJsAuthority *authority,
- JSObject *obj,
- const gchar *name,
- const gchar *value)
-{
- JSString *value_jsstr;
- jsval value_jsval;
- value_jsstr = JS_NewStringCopyZ (authority->priv->cx, value);
- value_jsval = STRING_TO_JSVAL (value_jsstr);
- JS_SetProperty (authority->priv->cx, obj, name, &value_jsval);
-}
-
-/* authority->priv->cx must be within a request */
-static void
-set_property_strv (PolkitBackendJsAuthority *authority,
- JSObject *obj,
- const gchar *name,
- GPtrArray *value)
-{
- jsval value_jsval;
- JSObject *array_object;
- guint n;
-
- array_object = JS_NewArrayObject (authority->priv->cx, 0, NULL);
-
- for (n = 0; n < value->len; n++)
- {
- JSString *jsstr;
- jsval val;
-
- jsstr = JS_NewStringCopyZ (authority->priv->cx, g_ptr_array_index(value, n));
- val = STRING_TO_JSVAL (jsstr);
- JS_SetElement (authority->priv->cx, array_object, n, &val);
- }
-
- value_jsval = OBJECT_TO_JSVAL (array_object);
- JS_SetProperty (authority->priv->cx, obj, name, &value_jsval);
-}
-
-/* authority->priv->cx must be within a request */
-static void
-set_property_int32 (PolkitBackendJsAuthority *authority,
- JSObject *obj,
- const gchar *name,
- gint32 value)
-{
- jsval value_jsval;
- value_jsval = INT_TO_JSVAL ((gint32) value);
- JS_SetProperty (authority->priv->cx, obj, name, &value_jsval);
-}
-
-/* authority->priv->cx must be within a request */
-static void
-set_property_bool (PolkitBackendJsAuthority *authority,
- JSObject *obj,
- const gchar *name,
- gboolean value)
-{
- jsval value_jsval;
- value_jsval = BOOLEAN_TO_JSVAL ((JSBool) value);
- JS_SetProperty (authority->priv->cx, obj, name, &value_jsval);
-}
-
-/* ---------------------------------------------------------------------------------------------------- */
-
-/* authority->priv->cx must be within a request */
-static gboolean
-subject_to_jsval (PolkitBackendJsAuthority *authority,
- PolkitSubject *subject,
- PolkitIdentity *user_for_subject,
- gboolean subject_is_local,
- gboolean subject_is_active,
- jsval *out_jsval,
- GError **error)
-{
- gboolean ret = FALSE;
- jsval ret_jsval;
- const char *src;
- JSObject *obj;
- pid_t pid;
- uid_t uid;
- gchar *user_name = NULL;
- GPtrArray *groups = NULL;
- struct passwd *passwd;
- char *seat_str = NULL;
- char *session_str = NULL;
-
- src = "new Subject();";
-
- if (!JS_EvaluateScript (authority->priv->cx,
- authority->priv->js_global,
- src, strlen (src),
- __FILE__, __LINE__,
- &ret_jsval))
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Evaluating '%s' failed", src);
- goto out;
- }
-
- obj = JSVAL_TO_OBJECT (ret_jsval);
-
- 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 (authority, obj, "pid", pid);
- set_property_str (authority, obj, "user", user_name);
- set_property_strv (authority, obj, "groups", groups);
- set_property_str (authority, obj, "seat", seat_str);
- set_property_str (authority, obj, "session", session_str);
- set_property_bool (authority, obj, "local", subject_is_local);
- set_property_bool (authority, obj, "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);
-
- if (ret && out_jsval != NULL)
- *out_jsval = ret_jsval;
-
- return ret;
-}
-
-/* ---------------------------------------------------------------------------------------------------- */
-
-/* authority->priv->cx must be within a request */
-static gboolean
-action_and_details_to_jsval (PolkitBackendJsAuthority *authority,
- const gchar *action_id,
- PolkitDetails *details,
- jsval *out_jsval,
- GError **error)
-{
- gboolean ret = FALSE;
- jsval ret_jsval;
- const char *src;
- JSObject *obj;
- gchar **keys;
- guint n;
-
- src = "new Action();";
- if (!JS_EvaluateScript (authority->priv->cx,
- authority->priv->js_global,
- src, strlen (src),
- __FILE__, __LINE__,
- &ret_jsval))
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Evaluating '%s' failed", src);
- goto out;
- }
-
- obj = JSVAL_TO_OBJECT (ret_jsval);
-
- set_property_str (authority, obj, "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 (authority, obj, key, value);
- g_free (key);
- }
- g_strfreev (keys);
-
- ret = TRUE;
-
- out:
- if (ret && out_jsval != NULL)
- *out_jsval = ret_jsval;
-
- return ret;
-}
-
-/* ---------------------------------------------------------------------------------------------------- */
-
-static gpointer
-runaway_killer_thread_func (gpointer user_data)
-{
- PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (user_data);
-
- g_mutex_lock (&authority->priv->rkt_init_mutex);
-
- authority->priv->rkt_context = g_main_context_new ();
- authority->priv->rkt_loop = g_main_loop_new (authority->priv->rkt_context, FALSE);
- g_main_context_push_thread_default (authority->priv->rkt_context);
-
- /* Signal the main thread that we're done constructing */
- g_cond_signal (&authority->priv->rkt_init_cond);
- g_mutex_unlock (&authority->priv->rkt_init_mutex);
-
- g_main_loop_run (authority->priv->rkt_loop);
-
- g_main_context_pop_thread_default (authority->priv->rkt_context);
-
- g_main_loop_unref (authority->priv->rkt_loop);
- authority->priv->rkt_loop = NULL;
- g_main_context_unref (authority->priv->rkt_context);
- authority->priv->rkt_context = NULL;
-
- return NULL;
-}
-
-/* ---------------------------------------------------------------------------------------------------- */
-
-static JSBool
-js_operation_callback (JSContext *cx)
-{
- PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (JS_GetContextPrivate (cx));
- JSString *val_str;
- jsval val;
-
- /* This callback can be called by the runtime at any time without us causing
- * it by JS_TriggerOperationCallback().
- */
- g_mutex_lock (&authority->priv->rkt_timeout_pending_mutex);
- if (!authority->priv->rkt_timeout_pending)
- {
- g_mutex_unlock (&authority->priv->rkt_timeout_pending_mutex);
- return JS_TRUE;
- }
- authority->priv->rkt_timeout_pending = FALSE;
- g_mutex_unlock (&authority->priv->rkt_timeout_pending_mutex);
-
- /* Log that we are terminating the script */
- polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), "Terminating runaway script");
-
- /* Throw an exception - this way the JS code can ignore the runaway script handling */
- JS_SetOperationCallback (authority->priv->cx, NULL);
- val_str = JS_NewStringCopyZ (cx, "Terminating runaway script");
- val = STRING_TO_JSVAL (val_str);
- JS_SetPendingException (authority->priv->cx, val);
- JS_SetOperationCallback (authority->priv->cx, js_operation_callback);
- return JS_FALSE;
-}
-
-static gboolean
-rkt_on_timeout (gpointer user_data)
-{
- PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (user_data);
-
- g_mutex_lock (&authority->priv->rkt_timeout_pending_mutex);
- authority->priv->rkt_timeout_pending = TRUE;
- g_mutex_unlock (&authority->priv->rkt_timeout_pending_mutex);
-
- /* Supposedly this is thread-safe... */
-#if JS_VERSION == 186
- JS_TriggerOperationCallback (authority->priv->rt);
-#else
- JS_TriggerOperationCallback (authority->priv->cx);
-#endif
-
- /* keep source around so we keep trying to kill even if the JS bit catches the exception
- * thrown in js_operation_callback()
- */
- return TRUE;
-}
-
-static void
-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 */
- 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);
- g_source_set_callback (authority->priv->rkt_source, rkt_on_timeout, authority, NULL);
- g_source_attach (authority->priv->rkt_source, authority->priv->rkt_context);
-
- /* ... rkt_on_timeout() will then poke the JSContext so js_operation_callback() is
- * called... and from there we throw an exception
- */
- JS_SetOperationCallback (authority->priv->cx, js_operation_callback);
-}
-
-static void
-runaway_killer_teardown (PolkitBackendJsAuthority *authority)
-{
- JS_SetOperationCallback (authority->priv->cx, NULL);
- g_source_destroy (authority->priv->rkt_source);
- g_source_unref (authority->priv->rkt_source);
- authority->priv->rkt_source = NULL;
-}
-
-static JSBool
-execute_script_with_runaway_killer (PolkitBackendJsAuthority *authority,
-#if JS_VERSION == 186
- JSScript *script,
-#else
- JSObject *script,
-#endif
- jsval *rval)
-{
- JSBool ret;
-
- runaway_killer_setup (authority);
- ret = JS_ExecuteScript (authority->priv->cx,
- authority->priv->js_global,
- script,
- rval);
- runaway_killer_teardown (authority);
-
- return ret;
-}
-
-static JSBool
-call_js_function_with_runaway_killer (PolkitBackendJsAuthority *authority,
- const char *function_name,
- unsigned argc,
- jsval *argv,
- jsval *rval)
-{
- JSBool ret;
- runaway_killer_setup (authority);
- ret = JS_CallFunctionName(authority->priv->cx,
- authority->priv->js_polkit,
- function_name,
- argc,
- argv,
- rval);
- runaway_killer_teardown (authority);
- return ret;
-}
-
-/* ---------------------------------------------------------------------------------------------------- */
-
-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;
- jsval argv[2] = {JSVAL_NULL, JSVAL_NULL};
- jsval rval = JSVAL_NULL;
- guint n;
- GError *error = NULL;
- JSString *ret_jsstr;
- gchar *ret_str = NULL;
- gchar **ret_strs = NULL;
-
- JS_BeginRequest (authority->priv->cx);
-
- if (!action_and_details_to_jsval (authority, action_id, details, &argv[0], &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 (!subject_to_jsval (authority,
- subject,
- user_for_subject,
- subject_is_local,
- subject_is_active,
- &argv[1],
- &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 (!call_js_function_with_runaway_killer (authority,
- "_runAdminRules",
- G_N_ELEMENTS (argv),
- argv,
- &rval))
- {
- polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
- "Error evaluating admin rules");
- goto out;
- }
-
- if (!JSVAL_IS_STRING (rval))
- {
- g_warning ("Expected a string");
- goto out;
- }
-
- ret_jsstr = JSVAL_TO_STRING (rval);
- ret_str = g_utf16_to_utf8 (JS_GetStringCharsZ (authority->priv->cx, ret_jsstr), -1, NULL, NULL, NULL);
- if (ret_str == NULL)
- {
- g_warning ("Error converting resulting string to UTF-8: %s", error->message);
- goto out;
- }
-
- 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",
- identity_str);
- }
- else
- {
- ret = g_list_prepend (ret, identity);
- }
- }
- ret = g_list_reverse (ret);
-
- out:
- g_strfreev (ret_strs);
- g_free (ret_str);
- /* fallback to root password auth */
- if (ret == NULL)
- ret = g_list_prepend (ret, polkit_unix_user_new (0));
-
- JS_MaybeGC (authority->priv->cx);
-
- JS_EndRequest (authority->priv->cx);
-
- 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;
- jsval argv[2] = {JSVAL_NULL, JSVAL_NULL};
- jsval rval = JSVAL_NULL;
- GError *error = NULL;
- JSString *ret_jsstr;
- const jschar *ret_utf16;
- gchar *ret_str = NULL;
- gboolean good = FALSE;
-
- JS_BeginRequest (authority->priv->cx);
-
- if (!action_and_details_to_jsval (authority, action_id, details, &argv[0], &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 (!subject_to_jsval (authority,
- subject,
- user_for_subject,
- subject_is_local,
- subject_is_active,
- &argv[1],
- &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 (!call_js_function_with_runaway_killer (authority,
- "_runRules",
- G_N_ELEMENTS (argv),
- argv,
- &rval))
- {
- polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
- "Error evaluating authorization rules");
- goto out;
- }
-
- if (JSVAL_IS_NULL (rval))
- {
- /* this fine, means there was no match, use implicit authorizations */
- good = TRUE;
- goto out;
- }
-
- if (!JSVAL_IS_STRING (rval))
- {
- g_warning ("Expected a string");
- goto out;
- }
-
- ret_jsstr = JSVAL_TO_STRING (rval);
- ret_utf16 = JS_GetStringCharsZ (authority->priv->cx, ret_jsstr);
- ret_str = g_utf16_to_utf8 (ret_utf16, -1, NULL, NULL, &error);
- if (ret_str == NULL)
- {
- g_warning ("Error converting resulting string to UTF-8: %s", error->message);
- g_clear_error (&error);
- goto out;
- }
-
- g_strstrip (ret_str);
- 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);
-
- JS_MaybeGC (authority->priv->cx);
-
- JS_EndRequest (authority->priv->cx);
-
- return ret;
-}
-
-/* ---------------------------------------------------------------------------------------------------- */
-
-static JSBool
-js_polkit_log (JSContext *cx,
- unsigned argc,
- jsval *vp)
-{
- /* PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (JS_GetContextPrivate (cx)); */
- JSBool ret = JS_FALSE;
- JSString *str;
- char *s;
-
- if (!JS_ConvertArguments (cx, argc, JS_ARGV (cx, vp), "S", &str))
- goto out;
-
- s = JS_EncodeString (cx, str);
- JS_ReportWarning (cx, s);
- JS_free (cx, s);
-
- ret = JS_TRUE;
-
- JS_SET_RVAL (cx, vp, JSVAL_VOID); /* return undefined */
- out:
- return ret;
-}
-
-/* ---------------------------------------------------------------------------------------------------- */
-
-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 JSBool
-js_polkit_spawn (JSContext *cx,
- unsigned js_argc,
- jsval *vp)
-{
- /* PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (JS_GetContextPrivate (cx)); */
- JSBool ret = JS_FALSE;
- JSObject *array_object;
- gchar *standard_output = NULL;
- gchar *standard_error = NULL;
- gint exit_status;
- GError *error = NULL;
- JSString *ret_jsstr;
- guint32 array_len;
- gchar **argv = NULL;
- GMainContext *context = NULL;
- GMainLoop *loop = NULL;
- SpawnData data = {0};
- guint n;
-
- if (!JS_ConvertArguments (cx, js_argc, JS_ARGV (cx, vp), "o", &array_object))
- goto out;
-
- if (!JS_GetArrayLength (cx, array_object, &array_len))
- {
- JS_ReportError (cx, "Failed to get array length");
- goto out;
- }
-
- argv = g_new0 (gchar*, array_len + 1);
- for (n = 0; n < array_len; n++)
- {
- jsval elem_val;
- char *s;
-
- if (!JS_GetElement (cx, array_object, n, &elem_val))
- {
- JS_ReportError (cx, "Failed to get element %d", n);
- goto out;
- }
- if (!JSVAL_IS_STRING (elem_val))
- {
- JS_ReportError (cx, "Element %d is not a string", n);
- goto out;
- }
- s = JS_EncodeString (cx, JSVAL_TO_STRING (elem_val));
- argv[n] = g_strdup (s);
- JS_free (cx, s);
- }
-
- 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))
- {
- JS_ReportError (cx,
- "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);
- JS_ReportError (cx, gstr->str);
- g_string_free (gstr, TRUE);
- goto out;
- }
-
- ret = JS_TRUE;
-
- ret_jsstr = JS_NewStringCopyZ (cx, standard_output);
- JS_SET_RVAL (cx, vp, STRING_TO_JSVAL (ret_jsstr));
-
- 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);
- return ret;
-}
-
-/* ---------------------------------------------------------------------------------------------------- */
-
-
-static JSBool
-js_polkit_user_is_in_netgroup (JSContext *cx,
- unsigned argc,
- jsval *vp)
-{
- /* PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (JS_GetContextPrivate (cx)); */
- JSBool ret = JS_FALSE;
- JSString *user_str;
- JSString *netgroup_str;
- char *user;
- char *netgroup;
- JSBool is_in_netgroup = JS_FALSE;
-
- if (!JS_ConvertArguments (cx, argc, JS_ARGV (cx, vp), "SS", &user_str, &netgroup_str))
- goto out;
-
- user = JS_EncodeString (cx, user_str);
- netgroup = JS_EncodeString (cx, netgroup_str);
-
- if (innetgr (netgroup,
- NULL, /* host */
- user,
- NULL)) /* domain */
- {
- is_in_netgroup = JS_TRUE;
- }
-
- JS_free (cx, netgroup);
- JS_free (cx, user);
-
- ret = JS_TRUE;
-
- JS_SET_RVAL (cx, vp, BOOLEAN_TO_JSVAL (is_in_netgroup));
- out:
- 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);
- 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;
-}
diff --git a/src/polkitbackend/polkitbackendjsauthority.cpp b/src/polkitbackend/polkitbackendjsauthority.cpp
new file mode 100644
index 0000000..2112868
--- /dev/null
+++ b/src/polkitbackend/polkitbackendjsauthority.cpp
@@ -0,0 +1,1919 @@
+/*
+ * Copyright (C) 2008-2012 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 <davidz at redhat.com>
+ */
+
+#include "config.h"
+#include <sys/wait.h>
+#include <errno.h>
+#include <pwd.h>
+#include <grp.h>
+#ifdef HAVE_NETGROUP_H
+#include <netgroup.h>
+#else
+#include <netdb.h>
+#endif
+#include <string.h>
+#include <glib/gstdio.h>
+#include <locale.h>
+#include <glib/gi18n-lib.h>
+
+#include <polkit/polkit.h>
+#include "polkitbackendjsauthority.h"
+
+#include <polkit/polkitprivate.h>
+
+#ifdef HAVE_LIBSYSTEMD
+#include <systemd/sd-login.h>
+#endif /* HAVE_LIBSYSTEMD */
+
+#include <jsapi.h>
+
+#include "initjs.h" /* init.js */
+
+#ifdef JSGC_USE_EXACT_ROOTING
+/* See https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/GC/Exact_Stack_Rooting
+ * for more information about exact stack rooting.
+ */
+#error "This code is not safe in SpiderMonkey exact stack rooting configurations"
+#endif
+
+/**
+ * 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 */
+
+ JSRuntime *rt;
+ JSContext *cx;
+ JSObject *js_global;
+ JSObject *js_polkit;
+
+ GThread *runaway_killer_thread;
+ GMutex rkt_init_mutex;
+ GCond rkt_init_cond;
+ GMainContext *rkt_context;
+ GMainLoop *rkt_loop;
+ GSource *rkt_source;
+ GMutex rkt_timeout_pending_mutex;
+ gboolean rkt_timeout_pending;
+
+ /* A list of JSObject instances */
+ GList *scripts;
+};
+
+static JSBool execute_script_with_runaway_killer (PolkitBackendJsAuthority *authority,
+#if JS_VERSION == 186
+ JSScript *script,
+#else
+ JSObject *script,
+#endif
+ jsval *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 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 JSClass js_global_class = {
+ "global",
+ JSCLASS_GLOBAL_FLAGS,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_StrictPropertyStub,
+ JS_EnumerateStub,
+ JS_ResolveStub,
+ JS_ConvertStub,
+#if JS_VERSION == 186
+ NULL,
+#else
+ JS_FinalizeStub,
+#endif
+ JSCLASS_NO_OPTIONAL_MEMBERS
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static JSClass js_polkit_class = {
+ "Polkit",
+ 0,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_StrictPropertyStub,
+ JS_EnumerateStub,
+ JS_ResolveStub,
+ JS_ConvertStub,
+#if JS_VERSION == 186
+ NULL,
+#else
+ JS_FinalizeStub,
+#endif
+ JSCLASS_NO_OPTIONAL_MEMBERS
+};
+
+static JSBool js_polkit_log (JSContext *cx, unsigned argc, jsval *vp);
+static JSBool js_polkit_spawn (JSContext *cx, unsigned argc, jsval *vp);
+static JSBool js_polkit_user_is_in_netgroup (JSContext *cx, unsigned argc, jsval *vp);
+
+static JSFunctionSpec js_polkit_functions[] =
+{
+ JS_FS("log", js_polkit_log, 0, 0),
+ JS_FS("spawn", js_polkit_spawn, 0, 0),
+ JS_FS("_userIsInNetGroup", js_polkit_user_is_in_netgroup, 0, 0),
+ JS_FS_END
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void report_error (JSContext *cx,
+ const char *message,
+ JSErrorReport *report)
+{
+ PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (JS_GetContextPrivate (cx));
+ polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+ "%s:%u: %s",
+ report->filename ? report->filename : "<no filename>",
+ (unsigned int) report->lineno,
+ message);
+}
+
+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;
+}
+
+/* authority->priv->cx must be within a request */
+static void
+load_scripts (PolkitBackendJsAuthority *authority)
+{
+ 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 JS_VERSION == 186
+ JSScript *script;
+#else
+ JSObject *script;
+#endif
+
+#if JS_VERSION == 186
+ script = JS_CompileUTF8File (authority->priv->cx,
+ authority->priv->js_global,
+ filename);
+
+#else
+ script = JS_CompileFile (authority->priv->cx,
+ authority->priv->js_global,
+ filename);
+#endif
+ if (script == NULL)
+ {
+ polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+ "Error compiling script %s",
+ filename);
+ continue;
+ }
+
+ /* evaluate the script */
+ jsval rval;
+ if (!execute_script_with_runaway_killer (authority,
+ script,
+ &rval))
+ {
+ polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+ "Error executing script %s",
+ filename);
+ continue;
+ }
+
+ //g_print ("Successfully loaded and evaluated script `%s'\n", filename);
+
+ 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)
+{
+ jsval argv[1] = {JSVAL_NULL};
+ jsval rval = JSVAL_NULL;
+
+ JS_BeginRequest (authority->priv->cx);
+
+ if (!JS_CallFunctionName(authority->priv->cx,
+ authority->priv->js_polkit,
+ "_deleteRules",
+ 0,
+ argv,
+ &rval))
+ {
+ polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+ "Error deleting old rules, not loading new ones");
+ goto out;
+ }
+
+ polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+ "Collecting garbage unconditionally...");
+#if JS_VERSION == 186
+ JS_GC (authority->priv->rt);
+#else
+ JS_GC (authority->priv->cx);
+#endif
+
+ load_scripts (authority);
+
+ /* Let applications know we have new rules... */
+ g_signal_emit_by_name (authority, "changed");
+ out:
+ JS_EndRequest (authority->priv->cx);
+}
+
+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 void
+polkit_backend_js_authority_constructed (GObject *object)
+{
+ PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object);
+ gboolean entered_request = FALSE;
+
+ authority->priv->rt = JS_NewRuntime (8L * 1024L * 1024L);
+ if (authority->priv->rt == NULL)
+ goto fail;
+
+ authority->priv->cx = JS_NewContext (authority->priv->rt, 8192);
+ if (authority->priv->cx == NULL)
+ goto fail;
+
+ /* TODO: JIT'ing doesn't work will with killing runaway scripts... I think
+ * this is just a SpiderMonkey bug. So disable the JIT for now.
+ */
+ JS_SetOptions (authority->priv->cx,
+ JSOPTION_VAROBJFIX
+ /* | JSOPTION_JIT | JSOPTION_METHODJIT*/);
+ JS_SetVersion(authority->priv->cx, JSVERSION_LATEST);
+ JS_SetErrorReporter(authority->priv->cx, report_error);
+ JS_SetContextPrivate (authority->priv->cx, authority);
+
+ JS_BeginRequest(authority->priv->cx);
+ entered_request = TRUE;
+
+ authority->priv->js_global =
+#if JS_VERSION == 186
+ JS_NewGlobalObject (authority->priv->cx, &js_global_class, NULL);
+#else
+ JS_NewCompartmentAndGlobalObject (authority->priv->cx, &js_global_class, NULL);
+#endif
+
+ if (authority->priv->js_global == NULL)
+ goto fail;
+ JS_AddObjectRoot (authority->priv->cx, &authority->priv->js_global);
+
+ if (!JS_InitStandardClasses (authority->priv->cx, authority->priv->js_global))
+ goto fail;
+
+ authority->priv->js_polkit = JS_DefineObject (authority->priv->cx,
+ authority->priv->js_global,
+ "polkit",
+ &js_polkit_class,
+ NULL,
+ JSPROP_ENUMERATE);
+ if (authority->priv->js_polkit == NULL)
+ goto fail;
+ JS_AddObjectRoot (authority->priv->cx, &authority->priv->js_polkit);
+
+ if (!JS_DefineFunctions (authority->priv->cx,
+ authority->priv->js_polkit,
+ js_polkit_functions))
+ goto fail;
+
+ if (!JS_EvaluateScript (authority->priv->cx,
+ authority->priv->js_global,
+ init_js, strlen (init_js), /* init.js */
+ "init.js", /* filename */
+ 0, /* lineno */
+ NULL)) /* rval */
+ {
+ goto fail;
+ }
+
+ 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");
+ }
+
+ g_mutex_init (&authority->priv->rkt_init_mutex);
+ g_cond_init (&authority->priv->rkt_init_cond);
+ g_mutex_init (&authority->priv->rkt_timeout_pending_mutex);
+
+ authority->priv->runaway_killer_thread = g_thread_new ("runaway-killer-thread",
+ runaway_killer_thread_func,
+ authority);
+
+ /* wait for runaway_killer_thread to set up its GMainContext */
+ g_mutex_lock (&authority->priv->rkt_init_mutex);
+ while (authority->priv->rkt_context == NULL)
+ g_cond_wait (&authority->priv->rkt_init_cond, &authority->priv->rkt_init_mutex);
+ g_mutex_unlock (&authority->priv->rkt_init_mutex);
+
+ setup_file_monitors (authority);
+ load_scripts (authority);
+
+ JS_EndRequest (authority->priv->cx);
+ entered_request = FALSE;
+
+ G_OBJECT_CLASS (polkit_backend_js_authority_parent_class)->constructed (object);
+ return;
+
+ fail:
+ if (entered_request)
+ JS_EndRequest (authority->priv->cx);
+ 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;
+
+ g_mutex_clear (&authority->priv->rkt_init_mutex);
+ g_cond_clear (&authority->priv->rkt_init_cond);
+ g_mutex_clear (&authority->priv->rkt_timeout_pending_mutex);
+
+ /* shut down the killer thread */
+ g_assert (authority->priv->rkt_loop != NULL);
+ g_main_loop_quit (authority->priv->rkt_loop);
+ g_thread_join (authority->priv->runaway_killer_thread);
+ g_assert (authority->priv->rkt_loop == NULL);
+
+ 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);
+
+ JS_BeginRequest (authority->priv->cx);
+ JS_RemoveObjectRoot (authority->priv->cx, &authority->priv->js_polkit);
+ JS_RemoveObjectRoot (authority->priv->cx, &authority->priv->js_global);
+ JS_EndRequest (authority->priv->cx);
+
+ JS_DestroyContext (authority->priv->cx);
+ JS_DestroyRuntime (authority->priv->rt);
+ /* JS_ShutDown (); */
+
+ 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));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* authority->priv->cx must be within a request */
+static void
+set_property_str (PolkitBackendJsAuthority *authority,
+ JSObject *obj,
+ const gchar *name,
+ const gchar *value)
+{
+ JSString *value_jsstr;
+ jsval value_jsval;
+ value_jsstr = JS_NewStringCopyZ (authority->priv->cx, value);
+ value_jsval = STRING_TO_JSVAL (value_jsstr);
+ JS_SetProperty (authority->priv->cx, obj, name, &value_jsval);
+}
+
+/* authority->priv->cx must be within a request */
+static void
+set_property_strv (PolkitBackendJsAuthority *authority,
+ JSObject *obj,
+ const gchar *name,
+ GPtrArray *value)
+{
+ jsval value_jsval;
+ JSObject *array_object;
+ guint n;
+
+ array_object = JS_NewArrayObject (authority->priv->cx, 0, NULL);
+
+ for (n = 0; n < value->len; n++)
+ {
+ JSString *jsstr;
+ jsval val;
+
+ jsstr = JS_NewStringCopyZ (authority->priv->cx, g_ptr_array_index(value, n));
+ val = STRING_TO_JSVAL (jsstr);
+ JS_SetElement (authority->priv->cx, array_object, n, &val);
+ }
+
+ value_jsval = OBJECT_TO_JSVAL (array_object);
+ JS_SetProperty (authority->priv->cx, obj, name, &value_jsval);
+}
+
+/* authority->priv->cx must be within a request */
+static void
+set_property_int32 (PolkitBackendJsAuthority *authority,
+ JSObject *obj,
+ const gchar *name,
+ gint32 value)
+{
+ jsval value_jsval;
+ value_jsval = INT_TO_JSVAL ((gint32) value);
+ JS_SetProperty (authority->priv->cx, obj, name, &value_jsval);
+}
+
+/* authority->priv->cx must be within a request */
+static void
+set_property_bool (PolkitBackendJsAuthority *authority,
+ JSObject *obj,
+ const gchar *name,
+ gboolean value)
+{
+ jsval value_jsval;
+ value_jsval = BOOLEAN_TO_JSVAL ((JSBool) value);
+ JS_SetProperty (authority->priv->cx, obj, name, &value_jsval);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* authority->priv->cx must be within a request */
+static gboolean
+subject_to_jsval (PolkitBackendJsAuthority *authority,
+ PolkitSubject *subject,
+ PolkitIdentity *user_for_subject,
+ gboolean subject_is_local,
+ gboolean subject_is_active,
+ jsval *out_jsval,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ jsval ret_jsval;
+ const char *src;
+ JSObject *obj;
+ pid_t pid;
+ uid_t uid;
+ gchar *user_name = NULL;
+ GPtrArray *groups = NULL;
+ struct passwd *passwd;
+ char *seat_str = NULL;
+ char *session_str = NULL;
+
+ src = "new Subject();";
+
+ if (!JS_EvaluateScript (authority->priv->cx,
+ authority->priv->js_global,
+ src, strlen (src),
+ __FILE__, __LINE__,
+ &ret_jsval))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Evaluating '%s' failed", src);
+ goto out;
+ }
+
+ obj = JSVAL_TO_OBJECT (ret_jsval);
+
+ 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 (authority, obj, "pid", pid);
+ set_property_str (authority, obj, "user", user_name);
+ set_property_strv (authority, obj, "groups", groups);
+ set_property_str (authority, obj, "seat", seat_str);
+ set_property_str (authority, obj, "session", session_str);
+ set_property_bool (authority, obj, "local", subject_is_local);
+ set_property_bool (authority, obj, "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);
+
+ if (ret && out_jsval != NULL)
+ *out_jsval = ret_jsval;
+
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* authority->priv->cx must be within a request */
+static gboolean
+action_and_details_to_jsval (PolkitBackendJsAuthority *authority,
+ const gchar *action_id,
+ PolkitDetails *details,
+ jsval *out_jsval,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ jsval ret_jsval;
+ const char *src;
+ JSObject *obj;
+ gchar **keys;
+ guint n;
+
+ src = "new Action();";
+ if (!JS_EvaluateScript (authority->priv->cx,
+ authority->priv->js_global,
+ src, strlen (src),
+ __FILE__, __LINE__,
+ &ret_jsval))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Evaluating '%s' failed", src);
+ goto out;
+ }
+
+ obj = JSVAL_TO_OBJECT (ret_jsval);
+
+ set_property_str (authority, obj, "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 (authority, obj, key, value);
+ g_free (key);
+ }
+ g_strfreev (keys);
+
+ ret = TRUE;
+
+ out:
+ if (ret && out_jsval != NULL)
+ *out_jsval = ret_jsval;
+
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gpointer
+runaway_killer_thread_func (gpointer user_data)
+{
+ PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (user_data);
+
+ g_mutex_lock (&authority->priv->rkt_init_mutex);
+
+ authority->priv->rkt_context = g_main_context_new ();
+ authority->priv->rkt_loop = g_main_loop_new (authority->priv->rkt_context, FALSE);
+ g_main_context_push_thread_default (authority->priv->rkt_context);
+
+ /* Signal the main thread that we're done constructing */
+ g_cond_signal (&authority->priv->rkt_init_cond);
+ g_mutex_unlock (&authority->priv->rkt_init_mutex);
+
+ g_main_loop_run (authority->priv->rkt_loop);
+
+ g_main_context_pop_thread_default (authority->priv->rkt_context);
+
+ g_main_loop_unref (authority->priv->rkt_loop);
+ authority->priv->rkt_loop = NULL;
+ g_main_context_unref (authority->priv->rkt_context);
+ authority->priv->rkt_context = NULL;
+
+ return NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static JSBool
+js_operation_callback (JSContext *cx)
+{
+ PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (JS_GetContextPrivate (cx));
+ JSString *val_str;
+ jsval val;
+
+ /* This callback can be called by the runtime at any time without us causing
+ * it by JS_TriggerOperationCallback().
+ */
+ g_mutex_lock (&authority->priv->rkt_timeout_pending_mutex);
+ if (!authority->priv->rkt_timeout_pending)
+ {
+ g_mutex_unlock (&authority->priv->rkt_timeout_pending_mutex);
+ return JS_TRUE;
+ }
+ authority->priv->rkt_timeout_pending = FALSE;
+ g_mutex_unlock (&authority->priv->rkt_timeout_pending_mutex);
+
+ /* Log that we are terminating the script */
+ polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), "Terminating runaway script");
+
+ /* Throw an exception - this way the JS code can ignore the runaway script handling */
+ JS_SetOperationCallback (authority->priv->cx, NULL);
+ val_str = JS_NewStringCopyZ (cx, "Terminating runaway script");
+ val = STRING_TO_JSVAL (val_str);
+ JS_SetPendingException (authority->priv->cx, val);
+ JS_SetOperationCallback (authority->priv->cx, js_operation_callback);
+ return JS_FALSE;
+}
+
+static gboolean
+rkt_on_timeout (gpointer user_data)
+{
+ PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (user_data);
+
+ g_mutex_lock (&authority->priv->rkt_timeout_pending_mutex);
+ authority->priv->rkt_timeout_pending = TRUE;
+ g_mutex_unlock (&authority->priv->rkt_timeout_pending_mutex);
+
+ /* Supposedly this is thread-safe... */
+#if JS_VERSION == 186
+ JS_TriggerOperationCallback (authority->priv->rt);
+#else
+ JS_TriggerOperationCallback (authority->priv->cx);
+#endif
+
+ /* keep source around so we keep trying to kill even if the JS bit catches the exception
+ * thrown in js_operation_callback()
+ */
+ return TRUE;
+}
+
+static void
+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 */
+ 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);
+ g_source_set_callback (authority->priv->rkt_source, rkt_on_timeout, authority, NULL);
+ g_source_attach (authority->priv->rkt_source, authority->priv->rkt_context);
+
+ /* ... rkt_on_timeout() will then poke the JSContext so js_operation_callback() is
+ * called... and from there we throw an exception
+ */
+ JS_SetOperationCallback (authority->priv->cx, js_operation_callback);
+}
+
+static void
+runaway_killer_teardown (PolkitBackendJsAuthority *authority)
+{
+ JS_SetOperationCallback (authority->priv->cx, NULL);
+ g_source_destroy (authority->priv->rkt_source);
+ g_source_unref (authority->priv->rkt_source);
+ authority->priv->rkt_source = NULL;
+}
+
+static JSBool
+execute_script_with_runaway_killer (PolkitBackendJsAuthority *authority,
+#if JS_VERSION == 186
+ JSScript *script,
+#else
+ JSObject *script,
+#endif
+ jsval *rval)
+{
+ JSBool ret;
+
+ runaway_killer_setup (authority);
+ ret = JS_ExecuteScript (authority->priv->cx,
+ authority->priv->js_global,
+ script,
+ rval);
+ runaway_killer_teardown (authority);
+
+ return ret;
+}
+
+static JSBool
+call_js_function_with_runaway_killer (PolkitBackendJsAuthority *authority,
+ const char *function_name,
+ unsigned argc,
+ jsval *argv,
+ jsval *rval)
+{
+ JSBool ret;
+ runaway_killer_setup (authority);
+ ret = JS_CallFunctionName(authority->priv->cx,
+ authority->priv->js_polkit,
+ function_name,
+ argc,
+ argv,
+ rval);
+ runaway_killer_teardown (authority);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+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;
+ jsval argv[2] = {JSVAL_NULL, JSVAL_NULL};
+ jsval rval = JSVAL_NULL;
+ guint n;
+ GError *error = NULL;
+ JSString *ret_jsstr;
+ gchar *ret_str = NULL;
+ gchar **ret_strs = NULL;
+
+ JS_BeginRequest (authority->priv->cx);
+
+ if (!action_and_details_to_jsval (authority, action_id, details, &argv[0], &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 (!subject_to_jsval (authority,
+ subject,
+ user_for_subject,
+ subject_is_local,
+ subject_is_active,
+ &argv[1],
+ &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 (!call_js_function_with_runaway_killer (authority,
+ "_runAdminRules",
+ G_N_ELEMENTS (argv),
+ argv,
+ &rval))
+ {
+ polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+ "Error evaluating admin rules");
+ goto out;
+ }
+
+ if (!JSVAL_IS_STRING (rval))
+ {
+ g_warning ("Expected a string");
+ goto out;
+ }
+
+ ret_jsstr = JSVAL_TO_STRING (rval);
+ ret_str = g_utf16_to_utf8 (JS_GetStringCharsZ (authority->priv->cx, ret_jsstr), -1, NULL, NULL, NULL);
+ if (ret_str == NULL)
+ {
+ g_warning ("Error converting resulting string to UTF-8: %s", error->message);
+ goto out;
+ }
+
+ 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",
+ identity_str);
+ }
+ else
+ {
+ ret = g_list_prepend (ret, identity);
+ }
+ }
+ ret = g_list_reverse (ret);
+
+ out:
+ g_strfreev (ret_strs);
+ g_free (ret_str);
+ /* fallback to root password auth */
+ if (ret == NULL)
+ ret = g_list_prepend (ret, polkit_unix_user_new (0));
+
+ JS_MaybeGC (authority->priv->cx);
+
+ JS_EndRequest (authority->priv->cx);
+
+ 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;
+ jsval argv[2] = {JSVAL_NULL, JSVAL_NULL};
+ jsval rval = JSVAL_NULL;
+ GError *error = NULL;
+ JSString *ret_jsstr;
+ const jschar *ret_utf16;
+ gchar *ret_str = NULL;
+ gboolean good = FALSE;
+
+ JS_BeginRequest (authority->priv->cx);
+
+ if (!action_and_details_to_jsval (authority, action_id, details, &argv[0], &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 (!subject_to_jsval (authority,
+ subject,
+ user_for_subject,
+ subject_is_local,
+ subject_is_active,
+ &argv[1],
+ &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 (!call_js_function_with_runaway_killer (authority,
+ "_runRules",
+ G_N_ELEMENTS (argv),
+ argv,
+ &rval))
+ {
+ polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority),
+ "Error evaluating authorization rules");
+ goto out;
+ }
+
+ if (JSVAL_IS_NULL (rval))
+ {
+ /* this fine, means there was no match, use implicit authorizations */
+ good = TRUE;
+ goto out;
+ }
+
+ if (!JSVAL_IS_STRING (rval))
+ {
+ g_warning ("Expected a string");
+ goto out;
+ }
+
+ ret_jsstr = JSVAL_TO_STRING (rval);
+ ret_utf16 = JS_GetStringCharsZ (authority->priv->cx, ret_jsstr);
+ ret_str = g_utf16_to_utf8 (ret_utf16, -1, NULL, NULL, &error);
+ if (ret_str == NULL)
+ {
+ g_warning ("Error converting resulting string to UTF-8: %s", error->message);
+ g_clear_error (&error);
+ goto out;
+ }
+
+ g_strstrip (ret_str);
+ 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);
+
+ JS_MaybeGC (authority->priv->cx);
+
+ JS_EndRequest (authority->priv->cx);
+
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static JSBool
+js_polkit_log (JSContext *cx,
+ unsigned argc,
+ jsval *vp)
+{
+ /* PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (JS_GetContextPrivate (cx)); */
+ JSBool ret = JS_FALSE;
+ JSString *str;
+ char *s;
+
+ if (!JS_ConvertArguments (cx, argc, JS_ARGV (cx, vp), "S", &str))
+ goto out;
+
+ s = JS_EncodeString (cx, str);
+ JS_ReportWarning (cx, s);
+ JS_free (cx, s);
+
+ ret = JS_TRUE;
+
+ JS_SET_RVAL (cx, vp, JSVAL_VOID); /* return undefined */
+ out:
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+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 JSBool
+js_polkit_spawn (JSContext *cx,
+ unsigned js_argc,
+ jsval *vp)
+{
+ /* PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (JS_GetContextPrivate (cx)); */
+ JSBool ret = JS_FALSE;
+ JSObject *array_object;
+ gchar *standard_output = NULL;
+ gchar *standard_error = NULL;
+ gint exit_status;
+ GError *error = NULL;
+ JSString *ret_jsstr;
+ guint32 array_len;
+ gchar **argv = NULL;
+ GMainContext *context = NULL;
+ GMainLoop *loop = NULL;
+ SpawnData data = {0};
+ guint n;
+
+ if (!JS_ConvertArguments (cx, js_argc, JS_ARGV (cx, vp), "o", &array_object))
+ goto out;
+
+ if (!JS_GetArrayLength (cx, array_object, &array_len))
+ {
+ JS_ReportError (cx, "Failed to get array length");
+ goto out;
+ }
+
+ argv = g_new0 (gchar*, array_len + 1);
+ for (n = 0; n < array_len; n++)
+ {
+ jsval elem_val;
+ char *s;
+
+ if (!JS_GetElement (cx, array_object, n, &elem_val))
+ {
+ JS_ReportError (cx, "Failed to get element %d", n);
+ goto out;
+ }
+ if (!JSVAL_IS_STRING (elem_val))
+ {
+ JS_ReportError (cx, "Element %d is not a string", n);
+ goto out;
+ }
+ s = JS_EncodeString (cx, JSVAL_TO_STRING (elem_val));
+ argv[n] = g_strdup (s);
+ JS_free (cx, s);
+ }
+
+ 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))
+ {
+ JS_ReportError (cx,
+ "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);
+ JS_ReportError (cx, gstr->str);
+ g_string_free (gstr, TRUE);
+ goto out;
+ }
+
+ ret = JS_TRUE;
+
+ ret_jsstr = JS_NewStringCopyZ (cx, standard_output);
+ JS_SET_RVAL (cx, vp, STRING_TO_JSVAL (ret_jsstr));
+
+ 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);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+
+static JSBool
+js_polkit_user_is_in_netgroup (JSContext *cx,
+ unsigned argc,
+ jsval *vp)
+{
+ /* PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (JS_GetContextPrivate (cx)); */
+ JSBool ret = JS_FALSE;
+ JSString *user_str;
+ JSString *netgroup_str;
+ char *user;
+ char *netgroup;
+ JSBool is_in_netgroup = JS_FALSE;
+
+ if (!JS_ConvertArguments (cx, argc, JS_ARGV (cx, vp), "SS", &user_str, &netgroup_str))
+ goto out;
+
+ user = JS_EncodeString (cx, user_str);
+ netgroup = JS_EncodeString (cx, netgroup_str);
+
+ if (innetgr (netgroup,
+ NULL, /* host */
+ user,
+ NULL)) /* domain */
+ {
+ is_in_netgroup = JS_TRUE;
+ }
+
+ JS_free (cx, netgroup);
+ JS_free (cx, user);
+
+ ret = JS_TRUE;
+
+ JS_SET_RVAL (cx, vp, BOOLEAN_TO_JSVAL (is_in_netgroup));
+ out:
+ 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);
+ 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;
+}
diff --git a/test/polkitbackend/Makefile.am b/test/polkitbackend/Makefile.am
index 8859c1f..53bfb2a 100644
--- a/test/polkitbackend/Makefile.am
+++ b/test/polkitbackend/Makefile.am
@@ -35,6 +35,9 @@ TEST_PROGS =
TEST_PROGS += polkitbackendjsauthoritytest
polkitbackendjsauthoritytest_SOURCES = test-polkitbackendjsauthority.c
+# force C++ link via dummy C++ file, (see GNU automake manual section 8.3.5)
+nodist_EXTRA_polkitbackendjsauthoritytest_SOURCES = dummy-force-cpp-link.cxx
+
# ----------------------------------------------------------------------------------------------------
More information about the hal-commit
mailing list