From 4f66a9549a393e4d74b93eb85301a04ea94bc750 Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Wed, 24 Jul 2019 15:55:17 +0800 Subject: [PATCH 01/17] Add duktape as javascript engine. Signed-off-by: Gustavo Lima Chaves --- configure.ac | 28 +- src/polkitbackend/Makefile.am | 14 +- .../polkitbackendduktapeauthority.c | 1402 +++++++++++++++++ 3 files changed, 1436 insertions(+), 8 deletions(-) create mode 100644 src/polkitbackend/polkitbackendduktapeauthority.c diff --git a/configure.ac b/configure.ac index e434ca2..5a03593 100644 --- a/configure.ac +++ b/configure.ac @@ -80,11 +80,22 @@ PKG_CHECK_MODULES(GLIB, [gmodule-2.0 gio-unix-2.0 >= 2.30.0]) AC_SUBST(GLIB_CFLAGS) AC_SUBST(GLIB_LIBS) -PKG_CHECK_MODULES(LIBJS, [mozjs-78]) - -AC_SUBST(LIBJS_CFLAGS) -AC_SUBST(LIBJS_CXXFLAGS) -AC_SUBST(LIBJS_LIBS) +dnl --------------------------------------------------------------------------- +dnl - Check javascript backend +dnl --------------------------------------------------------------------------- +AC_ARG_WITH(duktape, AS_HELP_STRING([--with-duktape],[Use Duktape as javascript backend]),with_duktape=yes,with_duktape=no) +AS_IF([test x${with_duktape} == xyes], [ + PKG_CHECK_MODULES(LIBJS, [duktape >= 2.0.0 ]) + AC_SUBST(LIBJS_CFLAGS) + AC_SUBST(LIBJS_LIBS) +], [ + PKG_CHECK_MODULES(LIBJS, [mozjs-78]) + + AC_SUBST(LIBJS_CFLAGS) + AC_SUBST(LIBJS_CXXFLAGS) + AC_SUBST(LIBJS_LIBS) +]) +AM_CONDITIONAL(USE_DUKTAPE, [test x$with_duktape == xyes], [Using duktape as javascript engine library]) EXPAT_LIB="" AC_ARG_WITH(expat, [ --with-expat= Use expat from here], @@ -585,6 +596,13 @@ echo " PAM support: ${have_pam} systemdsystemunitdir: ${systemdsystemunitdir} polkitd user: ${POLKITD_USER}" +if test "x${with_duktape}" = xyes; then +echo " + Javascript engine: Duktape" +else +echo " + Javascript engine: Mozjs" +fi if test "$have_pam" = yes ; then echo " diff --git a/src/polkitbackend/Makefile.am b/src/polkitbackend/Makefile.am index 7e3c080..abcbc6f 100644 --- a/src/polkitbackend/Makefile.am +++ b/src/polkitbackend/Makefile.am @@ -33,7 +33,7 @@ libpolkit_backend_1_la_SOURCES = \ polkitbackendprivate.h \ polkitbackendauthority.h polkitbackendauthority.c \ polkitbackendinteractiveauthority.h polkitbackendinteractiveauthority.c \ - polkitbackendjsauthority.h polkitbackendjsauthority.cpp \ + polkitbackendjsauthority.h \ polkitbackendactionpool.h polkitbackendactionpool.c \ polkitbackendactionlookup.h polkitbackendactionlookup.c \ $(NULL) @@ -51,19 +51,27 @@ libpolkit_backend_1_la_CFLAGS = \ -D_POLKIT_BACKEND_COMPILATION \ $(GLIB_CFLAGS) \ $(LIBSYSTEMD_CFLAGS) \ - $(LIBJS_CFLAGS) \ + $(LIBJS_CFLAGS) \ $(NULL) libpolkit_backend_1_la_CXXFLAGS = $(libpolkit_backend_1_la_CFLAGS) libpolkit_backend_1_la_LIBADD = \ $(GLIB_LIBS) \ + $(DUKTAPE_LIBS) \ $(LIBSYSTEMD_LIBS) \ $(top_builddir)/src/polkit/libpolkit-gobject-1.la \ $(EXPAT_LIBS) \ - $(LIBJS_LIBS) \ + $(LIBJS_LIBS) \ $(NULL) +if USE_DUKTAPE +libpolkit_backend_1_la_SOURCES += polkitbackendduktapeauthority.c +libpolkit_backend_1_la_LIBADD += -lm +else +libpolkit_backend_1_la_SOURCES += polkitbackendjsauthority.cpp +endif + rulesdir = $(sysconfdir)/polkit-1/rules.d rules_DATA = 50-default.rules diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c new file mode 100644 index 0000000..ae98453 --- /dev/null +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -0,0 +1,1402 @@ +/* + * Copyright (C) 2008-2012 Red Hat, Inc. + * Copyright (C) 2015 Tangent Space + * Copyright (C) 2019 Wu Xiaotian + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "polkitbackendjsauthority.h" + +#include + +#ifdef HAVE_LIBSYSTEMD +#include +#endif /* HAVE_LIBSYSTEMD */ + +#include "initjs.h" /* init.js */ +#include "duktape.h" + +/** + * SECTION:polkitbackendjsauthority + * @title: PolkitBackendJsAuthority + * @short_description: JS Authority + * @stability: Unstable + * + * An implementation of #PolkitBackendAuthority that reads and + * evalates Javascript files and supports interaction with + * authentication agents (virtue of being based on + * #PolkitBackendInteractiveAuthority). + */ + +/* ---------------------------------------------------------------------------------------------------- */ + +struct _PolkitBackendJsAuthorityPrivate +{ + gchar **rules_dirs; + GFileMonitor **dir_monitors; /* NULL-terminated array of GFileMonitor instances */ + duk_context *cx; +}; + +#define WATCHDOG_TIMEOUT (15 * G_TIME_SPAN_SECOND) + +static void utils_spawn (const gchar *const *argv, + guint timeout_seconds, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean utils_spawn_finish (GAsyncResult *res, + gint *out_exit_status, + gchar **out_standard_output, + gchar **out_standard_error, + GError **error); + +static void on_dir_monitor_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data); + +/* ---------------------------------------------------------------------------------------------------- */ + +enum +{ + PROP_0, + PROP_RULES_DIRS, +}; + +/* ---------------------------------------------------------------------------------------------------- */ + +static GList *polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *authority, + PolkitSubject *caller, + PolkitSubject *subject, + PolkitIdentity *user_for_subject, + gboolean subject_is_local, + gboolean subject_is_active, + const gchar *action_id, + PolkitDetails *details); + +static PolkitImplicitAuthorization polkit_backend_js_authority_check_authorization_sync ( + PolkitBackendInteractiveAuthority *authority, + PolkitSubject *caller, + PolkitSubject *subject, + PolkitIdentity *user_for_subject, + gboolean subject_is_local, + gboolean subject_is_active, + const gchar *action_id, + PolkitDetails *details, + PolkitImplicitAuthorization implicit); + +G_DEFINE_TYPE (PolkitBackendJsAuthority, polkit_backend_js_authority, POLKIT_BACKEND_TYPE_INTERACTIVE_AUTHORITY); + +/* ---------------------------------------------------------------------------------------------------- */ + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +polkit_backend_js_authority_init (PolkitBackendJsAuthority *authority) +{ + authority->priv = G_TYPE_INSTANCE_GET_PRIVATE (authority, + POLKIT_BACKEND_TYPE_JS_AUTHORITY, + PolkitBackendJsAuthorityPrivate); +} + +static gint +rules_file_name_cmp (const gchar *a, + const gchar *b) +{ + gint ret; + const gchar *a_base; + const gchar *b_base; + + a_base = strrchr (a, '/'); + b_base = strrchr (b, '/'); + + g_assert (a_base != NULL); + g_assert (b_base != NULL); + a_base += 1; + b_base += 1; + + ret = g_strcmp0 (a_base, b_base); + if (ret == 0) + { + /* /etc wins over /usr */ + ret = g_strcmp0 (a, b); + g_assert (ret != 0); + } + + return ret; +} + +static void +load_scripts (PolkitBackendJsAuthority *authority) +{ + duk_context *cx = authority->priv->cx; + GList *files = NULL; + GList *l; + guint num_scripts = 0; + GError *error = NULL; + guint n; + + files = NULL; + + for (n = 0; authority->priv->rules_dirs != NULL && authority->priv->rules_dirs[n] != NULL; n++) + { + const gchar *dir_name = authority->priv->rules_dirs[n]; + GDir *dir = NULL; + + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Loading rules from directory %s", + dir_name); + + dir = g_dir_open (dir_name, + 0, + &error); + if (dir == NULL) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error opening rules directory: %s (%s, %d)", + error->message, g_quark_to_string (error->domain), error->code); + g_clear_error (&error); + } + else + { + const gchar *name; + while ((name = g_dir_read_name (dir)) != NULL) + { + if (g_str_has_suffix (name, ".rules")) + files = g_list_prepend (files, g_strdup_printf ("%s/%s", dir_name, name)); + } + g_dir_close (dir); + } + } + + files = g_list_sort (files, (GCompareFunc) rules_file_name_cmp); + + for (l = files; l != NULL; l = l->next) + { + const gchar *filename = l->data; + +#if (DUK_VERSION >= 20000) + gchar *contents; + gsize length; + GError *error = NULL; + if (!g_file_get_contents (filename, &contents, &length, &error)){ + g_warning("Error when file contents of %s: %s\n", filename, error->message); + g_error_free (error); + continue; + } + if (duk_peval_lstring_noresult(cx, contents,length) != 0) +#else + if (duk_peval_file_noresult (cx, filename) != 0) +#endif + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error compiling script %s: %s", + filename, duk_safe_to_string (authority->priv->cx, -1)); +#if (DUK_VERSION >= 20000) + g_free (contents); +#endif + continue; + } +#if (DUK_VERSION >= 20000) + g_free (contents); +#endif + num_scripts++; + } + + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Finished loading, compiling and executing %d rules", + num_scripts); + g_list_free_full (files, g_free); +} + +static void +reload_scripts (PolkitBackendJsAuthority *authority) +{ + duk_context *cx = authority->priv->cx; + + duk_set_top (cx, 0); + duk_get_global_string (cx, "polkit"); + duk_push_string (cx, "_deleteRules"); + + duk_call_prop (cx, 0, 0); + + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Collecting garbage unconditionally..."); + + load_scripts (authority); + + /* Let applications know we have new rules... */ + g_signal_emit_by_name (authority, "changed"); +} + +static void +on_dir_monitor_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (user_data); + + /* TODO: maybe rate-limit so storms of events are collapsed into one with a 500ms resolution? + * Because when editing a file with emacs we get 4-8 events.. + */ + + if (file != NULL) + { + gchar *name; + + name = g_file_get_basename (file); + + /* g_print ("event_type=%d file=%p name=%s\n", event_type, file, name); */ + if (!g_str_has_prefix (name, ".") && + !g_str_has_prefix (name, "#") && + g_str_has_suffix (name, ".rules") && + (event_type == G_FILE_MONITOR_EVENT_CREATED || + event_type == G_FILE_MONITOR_EVENT_DELETED || + event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Reloading rules"); + reload_scripts (authority); + } + g_free (name); + } +} + + +static void +setup_file_monitors (PolkitBackendJsAuthority *authority) +{ + guint n; + GPtrArray *p; + + p = g_ptr_array_new (); + for (n = 0; authority->priv->rules_dirs != NULL && authority->priv->rules_dirs[n] != NULL; n++) + { + GFile *file; + GError *error; + GFileMonitor *monitor; + + file = g_file_new_for_path (authority->priv->rules_dirs[n]); + error = NULL; + monitor = g_file_monitor_directory (file, + G_FILE_MONITOR_NONE, + NULL, + &error); + g_object_unref (file); + if (monitor == NULL) + { + g_warning ("Error monitoring directory %s: %s", + authority->priv->rules_dirs[n], + error->message); + g_clear_error (&error); + } + else + { + g_signal_connect (monitor, + "changed", + G_CALLBACK (on_dir_monitor_changed), + authority); + g_ptr_array_add (p, monitor); + } + } + g_ptr_array_add (p, NULL); + authority->priv->dir_monitors = (GFileMonitor**) g_ptr_array_free (p, FALSE); +} + +static duk_ret_t js_polkit_log (duk_context *cx); +static duk_ret_t js_polkit_spawn (duk_context *cx); +static duk_ret_t js_polkit_user_is_in_netgroup (duk_context *cx); + +static const duk_function_list_entry js_polkit_functions[] = +{ + { "log", js_polkit_log, 1 }, + { "spawn", js_polkit_spawn, 1 }, + { "_userIsInNetGroup", js_polkit_user_is_in_netgroup, 2 }, + { NULL, NULL, 0 }, +}; + +static void +polkit_backend_js_authority_constructed (GObject *object) +{ + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); + duk_context *cx; + + cx = duk_create_heap (NULL, NULL, NULL, authority, NULL); + if (cx == NULL) + goto fail; + + authority->priv->cx = cx; + + duk_push_global_object (cx); + duk_push_object (cx); + duk_put_function_list (cx, -1, js_polkit_functions); + duk_put_prop_string (cx, -2, "polkit"); + + duk_eval_string (cx, init_js); + + if (authority->priv->rules_dirs == NULL) + { + authority->priv->rules_dirs = g_new0 (gchar *, 3); + authority->priv->rules_dirs[0] = g_strdup (PACKAGE_SYSCONF_DIR "/polkit-1/rules.d"); + authority->priv->rules_dirs[1] = g_strdup (PACKAGE_DATA_DIR "/polkit-1/rules.d"); + } + + setup_file_monitors (authority); + load_scripts (authority); + + G_OBJECT_CLASS (polkit_backend_js_authority_parent_class)->constructed (object); + return; + + fail: + g_critical ("Error initializing JavaScript environment"); + g_assert_not_reached (); +} + +static void +polkit_backend_js_authority_finalize (GObject *object) +{ + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); + guint n; + + for (n = 0; authority->priv->dir_monitors != NULL && authority->priv->dir_monitors[n] != NULL; n++) + { + GFileMonitor *monitor = authority->priv->dir_monitors[n]; + g_signal_handlers_disconnect_by_func (monitor, + G_CALLBACK (on_dir_monitor_changed), + authority); + g_object_unref (monitor); + } + g_free (authority->priv->dir_monitors); + g_strfreev (authority->priv->rules_dirs); + + duk_destroy_heap (authority->priv->cx); + + G_OBJECT_CLASS (polkit_backend_js_authority_parent_class)->finalize (object); +} + +static void +polkit_backend_js_authority_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); + + switch (property_id) + { + case PROP_RULES_DIRS: + g_assert (authority->priv->rules_dirs == NULL); + authority->priv->rules_dirs = (gchar **) g_value_dup_boxed (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static const gchar * +polkit_backend_js_authority_get_name (PolkitBackendAuthority *authority) +{ + return "js"; +} + +static const gchar * +polkit_backend_js_authority_get_version (PolkitBackendAuthority *authority) +{ + return PACKAGE_VERSION; +} + +static PolkitAuthorityFeatures +polkit_backend_js_authority_get_features (PolkitBackendAuthority *authority) +{ + return POLKIT_AUTHORITY_FEATURES_TEMPORARY_AUTHORIZATION; +} + +static void +polkit_backend_js_authority_class_init (PolkitBackendJsAuthorityClass *klass) +{ + GObjectClass *gobject_class; + PolkitBackendAuthorityClass *authority_class; + PolkitBackendInteractiveAuthorityClass *interactive_authority_class; + + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = polkit_backend_js_authority_finalize; + gobject_class->set_property = polkit_backend_js_authority_set_property; + gobject_class->constructed = polkit_backend_js_authority_constructed; + + authority_class = POLKIT_BACKEND_AUTHORITY_CLASS (klass); + authority_class->get_name = polkit_backend_js_authority_get_name; + authority_class->get_version = polkit_backend_js_authority_get_version; + authority_class->get_features = polkit_backend_js_authority_get_features; + + interactive_authority_class = POLKIT_BACKEND_INTERACTIVE_AUTHORITY_CLASS (klass); + interactive_authority_class->get_admin_identities = polkit_backend_js_authority_get_admin_auth_identities; + interactive_authority_class->check_authorization_sync = polkit_backend_js_authority_check_authorization_sync; + + g_object_class_install_property (gobject_class, + PROP_RULES_DIRS, + g_param_spec_boxed ("rules-dirs", + NULL, + NULL, + G_TYPE_STRV, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); + + + g_type_class_add_private (klass, sizeof (PolkitBackendJsAuthorityPrivate)); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +set_property_str (duk_context *cx, + const gchar *name, + const gchar *value) +{ + duk_push_string (cx, value); + duk_put_prop_string (cx, -2, name); +} + +static void +set_property_strv (duk_context *cx, + const gchar *name, + GPtrArray *value) +{ + guint n; + duk_push_array (cx); + for (n = 0; n < value->len; n++) + { + duk_push_string (cx, g_ptr_array_index (value, n)); + duk_put_prop_index (cx, -2, n); + } + duk_put_prop_string (cx, -2, name); +} + +static void +set_property_int32 (duk_context *cx, + const gchar *name, + gint32 value) +{ + duk_push_int (cx, value); + duk_put_prop_string (cx, -2, name); +} + +static void +set_property_bool (duk_context *cx, + const char *name, + gboolean value) +{ + duk_push_boolean (cx, value); + duk_put_prop_string (cx, -2, name); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +push_subject (duk_context *cx, + PolkitSubject *subject, + PolkitIdentity *user_for_subject, + gboolean subject_is_local, + gboolean subject_is_active, + GError **error) +{ + gboolean ret = FALSE; + pid_t pid; + uid_t uid; + gchar *user_name = NULL; + GPtrArray *groups = NULL; + struct passwd *passwd; + char *seat_str = NULL; + char *session_str = NULL; + + duk_get_global_string (cx, "Subject"); + duk_new (cx, 0); + + if (POLKIT_IS_UNIX_PROCESS (subject)) + { + pid = polkit_unix_process_get_pid (POLKIT_UNIX_PROCESS (subject)); + } + else if (POLKIT_IS_SYSTEM_BUS_NAME (subject)) + { + PolkitSubject *process; + process = polkit_system_bus_name_get_process_sync (POLKIT_SYSTEM_BUS_NAME (subject), NULL, error); + if (process == NULL) + goto out; + pid = polkit_unix_process_get_pid (POLKIT_UNIX_PROCESS (process)); + g_object_unref (process); + } + else + { + g_assert_not_reached (); + } + +#ifdef HAVE_LIBSYSTEMD + if (sd_pid_get_session (pid, &session_str) == 0) + { + if (sd_session_get_seat (session_str, &seat_str) == 0) + { + /* do nothing */ + } + } +#endif /* HAVE_LIBSYSTEMD */ + + g_assert (POLKIT_IS_UNIX_USER (user_for_subject)); + uid = polkit_unix_user_get_uid (POLKIT_UNIX_USER (user_for_subject)); + + groups = g_ptr_array_new_with_free_func (g_free); + + passwd = getpwuid (uid); + if (passwd == NULL) + { + user_name = g_strdup_printf ("%d", (gint) uid); + g_warning ("Error looking up info for uid %d: %m", (gint) uid); + } + else + { + gid_t gids[512]; + int num_gids = 512; + + user_name = g_strdup (passwd->pw_name); + + if (getgrouplist (passwd->pw_name, + passwd->pw_gid, + gids, + &num_gids) < 0) + { + g_warning ("Error looking up groups for uid %d: %m", (gint) uid); + } + else + { + gint n; + for (n = 0; n < num_gids; n++) + { + struct group *group; + group = getgrgid (gids[n]); + if (group == NULL) + { + g_ptr_array_add (groups, g_strdup_printf ("%d", (gint) gids[n])); + } + else + { + g_ptr_array_add (groups, g_strdup (group->gr_name)); + } + } + } + } + + set_property_int32 (cx, "pid", pid); + set_property_str (cx, "user", user_name); + set_property_strv (cx, "groups", groups); + set_property_str (cx, "seat", seat_str); + set_property_str (cx, "session", session_str); + set_property_bool (cx, "local", subject_is_local); + set_property_bool (cx, "active", subject_is_active); + + ret = TRUE; + + out: + free (session_str); + free (seat_str); + g_free (user_name); + if (groups != NULL) + g_ptr_array_unref (groups); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +push_action_and_details (duk_context *cx, + const gchar *action_id, + PolkitDetails *details, + GError **error) +{ + gchar **keys; + guint n; + + duk_get_global_string (cx, "Action"); + duk_new (cx, 0); + + set_property_str (cx, "id", action_id); + + keys = polkit_details_get_keys (details); + for (n = 0; keys != NULL && keys[n] != NULL; n++) + { + gchar *key; + const gchar *value; + key = g_strdup_printf ("_detail_%s", keys[n]); + value = polkit_details_lookup (details, keys[n]); + set_property_str (cx, key, value); + g_free (key); + } + g_strfreev (keys); + + return TRUE; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* ---------------------------------------------------------------------------------------------------- */ + +static GList * +polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *_authority, + PolkitSubject *caller, + PolkitSubject *subject, + PolkitIdentity *user_for_subject, + gboolean subject_is_local, + gboolean subject_is_active, + const gchar *action_id, + PolkitDetails *details) +{ + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (_authority); + GList *ret = NULL; + guint n; + GError *error = NULL; + const char *ret_str = NULL; + gchar **ret_strs = NULL; + duk_context *cx = authority->priv->cx; + + duk_set_top (cx, 0); + duk_get_global_string (cx, "polkit"); + duk_push_string (cx, "_runAdminRules"); + + if (!push_action_and_details (cx, action_id, details, &error)) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error converting action and details to JS object: %s", + error->message); + g_clear_error (&error); + goto out; + } + + if (!push_subject (cx, subject, user_for_subject, subject_is_local, subject_is_active, &error)) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error converting subject to JS object: %s", + error->message); + g_clear_error (&error); + goto out; + } + + if (duk_pcall_prop (cx, 0, 2) != DUK_ERR_NONE) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error evaluating admin rules: ", + duk_safe_to_string (cx, -1)); + goto out; + } + + ret_str = duk_require_string (cx, -1); + + ret_strs = g_strsplit (ret_str, ",", -1); + for (n = 0; ret_strs != NULL && ret_strs[n] != NULL; n++) + { + const gchar *identity_str = ret_strs[n]; + PolkitIdentity *identity; + + error = NULL; + identity = polkit_identity_from_string (identity_str, &error); + if (identity == NULL) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Identity `%s' is not valid, ignoring: %s", + identity_str, error->message); + g_clear_error (&error); + } + else + { + ret = g_list_prepend (ret, identity); + } + } + ret = g_list_reverse (ret); + + out: + g_strfreev (ret_strs); + /* fallback to root password auth */ + if (ret == NULL) + ret = g_list_prepend (ret, polkit_unix_user_new (0)); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static PolkitImplicitAuthorization +polkit_backend_js_authority_check_authorization_sync (PolkitBackendInteractiveAuthority *_authority, + PolkitSubject *caller, + PolkitSubject *subject, + PolkitIdentity *user_for_subject, + gboolean subject_is_local, + gboolean subject_is_active, + const gchar *action_id, + PolkitDetails *details, + PolkitImplicitAuthorization implicit) +{ + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (_authority); + PolkitImplicitAuthorization ret = implicit; + GError *error = NULL; + gchar *ret_str = NULL; + gboolean good = FALSE; + duk_context *cx = authority->priv->cx; + + duk_set_top (cx, 0); + duk_get_global_string (cx, "polkit"); + duk_push_string (cx, "_runRules"); + + if (!push_action_and_details (cx, action_id, details, &error)) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error converting action and details to JS object: %s", + error->message); + g_clear_error (&error); + goto out; + } + + if (!push_subject (cx, subject, user_for_subject, subject_is_local, subject_is_active, &error)) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error converting subject to JS object: %s", + error->message); + g_clear_error (&error); + goto out; + } + + if (duk_pcall_prop (cx, 0, 2) != DUK_ERR_NONE) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error evaluating authorization rules: ", + duk_safe_to_string (cx, -1)); + goto out; + } + + if (duk_is_null(cx, -1)) { + good = TRUE; + goto out; + } + ret_str = g_strdup (duk_require_string (cx, -1)); + if (!polkit_implicit_authorization_from_string (ret_str, &ret)) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Returned result `%s' is not valid", + ret_str); + goto out; + } + + good = TRUE; + + out: + if (!good) + ret = POLKIT_IMPLICIT_AUTHORIZATION_NOT_AUTHORIZED; + g_free (ret_str); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static duk_ret_t +js_polkit_log (duk_context *cx) +{ + const char *str = duk_require_string (cx, 0); + fprintf (stderr, "%s\n", str); + return 0; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static const gchar * +get_signal_name (gint signal_number) +{ + switch (signal_number) + { +#define _HANDLE_SIG(sig) case sig: return #sig; + _HANDLE_SIG (SIGHUP); + _HANDLE_SIG (SIGINT); + _HANDLE_SIG (SIGQUIT); + _HANDLE_SIG (SIGILL); + _HANDLE_SIG (SIGABRT); + _HANDLE_SIG (SIGFPE); + _HANDLE_SIG (SIGKILL); + _HANDLE_SIG (SIGSEGV); + _HANDLE_SIG (SIGPIPE); + _HANDLE_SIG (SIGALRM); + _HANDLE_SIG (SIGTERM); + _HANDLE_SIG (SIGUSR1); + _HANDLE_SIG (SIGUSR2); + _HANDLE_SIG (SIGCHLD); + _HANDLE_SIG (SIGCONT); + _HANDLE_SIG (SIGSTOP); + _HANDLE_SIG (SIGTSTP); + _HANDLE_SIG (SIGTTIN); + _HANDLE_SIG (SIGTTOU); + _HANDLE_SIG (SIGBUS); +#ifdef SIGPOLL + _HANDLE_SIG (SIGPOLL); +#endif + _HANDLE_SIG (SIGPROF); + _HANDLE_SIG (SIGSYS); + _HANDLE_SIG (SIGTRAP); + _HANDLE_SIG (SIGURG); + _HANDLE_SIG (SIGVTALRM); + _HANDLE_SIG (SIGXCPU); + _HANDLE_SIG (SIGXFSZ); +#undef _HANDLE_SIG + default: + break; + } + return "UNKNOWN_SIGNAL"; +} + +typedef struct +{ + GMainLoop *loop; + GAsyncResult *res; +} SpawnData; + +static void +spawn_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SpawnData *data = user_data; + data->res = g_object_ref (res); + g_main_loop_quit (data->loop); +} + +static duk_ret_t +js_polkit_spawn (duk_context *cx) +{ +#if (DUK_VERSION >= 20000) + duk_ret_t ret = DUK_RET_ERROR; +#else + duk_ret_t ret = DUK_RET_INTERNAL_ERROR; +#endif + gchar *standard_output = NULL; + gchar *standard_error = NULL; + gint exit_status; + GError *error = NULL; + guint32 array_len; + gchar **argv = NULL; + GMainContext *context = NULL; + GMainLoop *loop = NULL; + SpawnData data = {0}; + char *err_str = NULL; + guint n; + + if (!duk_is_array (cx, 0)) + goto out; + + array_len = duk_get_length (cx, 0); + + argv = g_new0 (gchar*, array_len + 1); + for (n = 0; n < array_len; n++) + { + duk_get_prop_index (cx, 0, n); + argv[n] = g_strdup (duk_to_string (cx, -1)); + duk_pop (cx); + } + + context = g_main_context_new (); + loop = g_main_loop_new (context, FALSE); + + g_main_context_push_thread_default (context); + + data.loop = loop; + utils_spawn ((const gchar *const *) argv, + 10, /* timeout_seconds */ + NULL, /* cancellable */ + spawn_cb, + &data); + + g_main_loop_run (loop); + + g_main_context_pop_thread_default (context); + + if (!utils_spawn_finish (data.res, + &exit_status, + &standard_output, + &standard_error, + &error)) + { + err_str = g_strdup_printf ("Error spawning helper: %s (%s, %d)", + error->message, g_quark_to_string (error->domain), error->code); + g_clear_error (&error); + goto out; + } + + if (!(WIFEXITED (exit_status) && WEXITSTATUS (exit_status) == 0)) + { + GString *gstr; + gstr = g_string_new (NULL); + if (WIFEXITED (exit_status)) + { + g_string_append_printf (gstr, + "Helper exited with non-zero exit status %d", + WEXITSTATUS (exit_status)); + } + else if (WIFSIGNALED (exit_status)) + { + g_string_append_printf (gstr, + "Helper was signaled with signal %s (%d)", + get_signal_name (WTERMSIG (exit_status)), + WTERMSIG (exit_status)); + } + g_string_append_printf (gstr, ", stdout=`%s', stderr=`%s'", + standard_output, standard_error); + err_str = g_string_free (gstr, FALSE); + goto out; + } + + duk_push_string (cx, standard_output); + ret = 1; + + out: + g_strfreev (argv); + g_free (standard_output); + g_free (standard_error); + g_clear_object (&data.res); + if (loop != NULL) + g_main_loop_unref (loop); + if (context != NULL) + g_main_context_unref (context); + + if (err_str) + duk_error (cx, DUK_ERR_ERROR, err_str); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + + +static duk_ret_t +js_polkit_user_is_in_netgroup (duk_context *cx) +{ + const char *user; + const char *netgroup; + gboolean is_in_netgroup = FALSE; + + user = duk_require_string (cx, 0); + netgroup = duk_require_string (cx, 1); + + if (innetgr (netgroup, + NULL, /* host */ + user, + NULL)) /* domain */ + { + is_in_netgroup = TRUE; + } + + duk_push_boolean (cx, is_in_netgroup); + return 1; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + GSimpleAsyncResult *simple; /* borrowed reference */ + GMainContext *main_context; /* may be NULL */ + + GCancellable *cancellable; /* may be NULL */ + gulong cancellable_handler_id; + + GPid child_pid; + gint child_stdout_fd; + gint child_stderr_fd; + + GIOChannel *child_stdout_channel; + GIOChannel *child_stderr_channel; + + GSource *child_watch_source; + GSource *child_stdout_source; + GSource *child_stderr_source; + + guint timeout_seconds; + gboolean timed_out; + GSource *timeout_source; + + GString *child_stdout; + GString *child_stderr; + + gint exit_status; +} UtilsSpawnData; + +static void +utils_child_watch_from_release_cb (GPid pid, + gint status, + gpointer user_data) +{ +} + +static void +utils_spawn_data_free (UtilsSpawnData *data) +{ + if (data->timeout_source != NULL) + { + g_source_destroy (data->timeout_source); + data->timeout_source = NULL; + } + + /* Nuke the child, if necessary */ + if (data->child_watch_source != NULL) + { + g_source_destroy (data->child_watch_source); + data->child_watch_source = NULL; + } + + if (data->child_pid != 0) + { + GSource *source; + kill (data->child_pid, SIGTERM); + /* OK, we need to reap for the child ourselves - we don't want + * to use waitpid() because that might block the calling + * thread (the child might handle SIGTERM and use several + * seconds for cleanup/rollback). + * + * So we use GChildWatch instead. + * + * Avoid taking a references to ourselves. but note that we need + * to pass the GSource so we can nuke it once handled. + */ + source = g_child_watch_source_new (data->child_pid); + g_source_set_callback (source, + (GSourceFunc) utils_child_watch_from_release_cb, + source, + (GDestroyNotify) g_source_destroy); + g_source_attach (source, data->main_context); + g_source_unref (source); + data->child_pid = 0; + } + + if (data->child_stdout != NULL) + { + g_string_free (data->child_stdout, TRUE); + data->child_stdout = NULL; + } + + if (data->child_stderr != NULL) + { + g_string_free (data->child_stderr, TRUE); + data->child_stderr = NULL; + } + + if (data->child_stdout_channel != NULL) + { + g_io_channel_unref (data->child_stdout_channel); + data->child_stdout_channel = NULL; + } + if (data->child_stderr_channel != NULL) + { + g_io_channel_unref (data->child_stderr_channel); + data->child_stderr_channel = NULL; + } + + if (data->child_stdout_source != NULL) + { + g_source_destroy (data->child_stdout_source); + data->child_stdout_source = NULL; + } + if (data->child_stderr_source != NULL) + { + g_source_destroy (data->child_stderr_source); + data->child_stderr_source = NULL; + } + + if (data->child_stdout_fd != -1) + { + g_warn_if_fail (close (data->child_stdout_fd) == 0); + data->child_stdout_fd = -1; + } + if (data->child_stderr_fd != -1) + { + g_warn_if_fail (close (data->child_stderr_fd) == 0); + data->child_stderr_fd = -1; + } + + if (data->cancellable_handler_id > 0) + { + g_cancellable_disconnect (data->cancellable, data->cancellable_handler_id); + data->cancellable_handler_id = 0; + } + + if (data->main_context != NULL) + g_main_context_unref (data->main_context); + + if (data->cancellable != NULL) + g_object_unref (data->cancellable); + + g_slice_free (UtilsSpawnData, data); +} + +/* called in the thread where @cancellable was cancelled */ +static void +utils_on_cancelled (GCancellable *cancellable, + gpointer user_data) +{ + UtilsSpawnData *data = user_data; + GError *error; + + error = NULL; + g_warn_if_fail (g_cancellable_set_error_if_cancelled (cancellable, &error)); + g_simple_async_result_take_error (data->simple, error); + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); +} + +static gboolean +utils_read_child_stderr (GIOChannel *channel, + GIOCondition condition, + gpointer user_data) +{ + UtilsSpawnData *data = user_data; + gchar buf[1024]; + gsize bytes_read; + + g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); + g_string_append_len (data->child_stderr, buf, bytes_read); + return TRUE; +} + +static gboolean +utils_read_child_stdout (GIOChannel *channel, + GIOCondition condition, + gpointer user_data) +{ + UtilsSpawnData *data = user_data; + gchar buf[1024]; + gsize bytes_read; + + g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); + g_string_append_len (data->child_stdout, buf, bytes_read); + return TRUE; +} + +static void +utils_child_watch_cb (GPid pid, + gint status, + gpointer user_data) +{ + UtilsSpawnData *data = user_data; + gchar *buf; + gsize buf_size; + + if (g_io_channel_read_to_end (data->child_stdout_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) + { + g_string_append_len (data->child_stdout, buf, buf_size); + g_free (buf); + } + if (g_io_channel_read_to_end (data->child_stderr_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) + { + g_string_append_len (data->child_stderr, buf, buf_size); + g_free (buf); + } + + data->exit_status = status; + + /* ok, child watch is history, make sure we don't free it in spawn_data_free() */ + data->child_pid = 0; + data->child_watch_source = NULL; + + /* we're done */ + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); +} + +static gboolean +utils_timeout_cb (gpointer user_data) +{ + UtilsSpawnData *data = user_data; + + data->timed_out = TRUE; + + /* ok, timeout is history, make sure we don't free it in spawn_data_free() */ + data->timeout_source = NULL; + + /* we're done */ + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); + + return FALSE; /* remove source */ +} + +static void +utils_spawn (const gchar *const *argv, + guint timeout_seconds, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + UtilsSpawnData *data; + GError *error; + + data = g_slice_new0 (UtilsSpawnData); + data->timeout_seconds = timeout_seconds; + data->simple = g_simple_async_result_new (NULL, + callback, + user_data, + utils_spawn); + data->main_context = g_main_context_get_thread_default (); + if (data->main_context != NULL) + g_main_context_ref (data->main_context); + + data->cancellable = cancellable != NULL ? g_object_ref (cancellable) : NULL; + + data->child_stdout = g_string_new (NULL); + data->child_stderr = g_string_new (NULL); + data->child_stdout_fd = -1; + data->child_stderr_fd = -1; + + /* the life-cycle of UtilsSpawnData is tied to its GSimpleAsyncResult */ + g_simple_async_result_set_op_res_gpointer (data->simple, data, (GDestroyNotify) utils_spawn_data_free); + + error = NULL; + if (data->cancellable != NULL) + { + /* could already be cancelled */ + error = NULL; + if (g_cancellable_set_error_if_cancelled (data->cancellable, &error)) + { + g_simple_async_result_take_error (data->simple, error); + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); + goto out; + } + + data->cancellable_handler_id = g_cancellable_connect (data->cancellable, + G_CALLBACK (utils_on_cancelled), + data, + NULL); + } + + error = NULL; + if (!g_spawn_async_with_pipes (NULL, /* working directory */ + (gchar **) argv, + NULL, /* envp */ + G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, + NULL, /* child_setup */ + NULL, /* child_setup's user_data */ + &(data->child_pid), + NULL, /* gint *stdin_fd */ + &(data->child_stdout_fd), + &(data->child_stderr_fd), + &error)) + { + g_prefix_error (&error, "Error spawning: "); + g_simple_async_result_take_error (data->simple, error); + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); + goto out; + } + + if (timeout_seconds > 0) + { + data->timeout_source = g_timeout_source_new_seconds (timeout_seconds); + g_source_set_priority (data->timeout_source, G_PRIORITY_DEFAULT); + g_source_set_callback (data->timeout_source, utils_timeout_cb, data, NULL); + g_source_attach (data->timeout_source, data->main_context); + g_source_unref (data->timeout_source); + } + + data->child_watch_source = g_child_watch_source_new (data->child_pid); + g_source_set_callback (data->child_watch_source, (GSourceFunc) utils_child_watch_cb, data, NULL); + g_source_attach (data->child_watch_source, data->main_context); + g_source_unref (data->child_watch_source); + + data->child_stdout_channel = g_io_channel_unix_new (data->child_stdout_fd); + g_io_channel_set_flags (data->child_stdout_channel, G_IO_FLAG_NONBLOCK, NULL); + data->child_stdout_source = g_io_create_watch (data->child_stdout_channel, G_IO_IN); + g_source_set_callback (data->child_stdout_source, (GSourceFunc) utils_read_child_stdout, data, NULL); + g_source_attach (data->child_stdout_source, data->main_context); + g_source_unref (data->child_stdout_source); + + data->child_stderr_channel = g_io_channel_unix_new (data->child_stderr_fd); + g_io_channel_set_flags (data->child_stderr_channel, G_IO_FLAG_NONBLOCK, NULL); + data->child_stderr_source = g_io_create_watch (data->child_stderr_channel, G_IO_IN); + g_source_set_callback (data->child_stderr_source, (GSourceFunc) utils_read_child_stderr, data, NULL); + g_source_attach (data->child_stderr_source, data->main_context); + g_source_unref (data->child_stderr_source); + + out: + ; +} + +gboolean +utils_spawn_finish (GAsyncResult *res, + gint *out_exit_status, + gchar **out_standard_output, + gchar **out_standard_error, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + UtilsSpawnData *data; + gboolean ret = FALSE; + + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == utils_spawn); + + if (g_simple_async_result_propagate_error (simple, error)) + goto out; + + data = g_simple_async_result_get_op_res_gpointer (simple); + + if (data->timed_out) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_TIMED_OUT, + "Timed out after %d seconds", + data->timeout_seconds); + goto out; + } + + if (out_exit_status != NULL) + *out_exit_status = data->exit_status; + + if (out_standard_output != NULL) + *out_standard_output = g_strdup (data->child_stdout->str); + + if (out_standard_error != NULL) + *out_standard_error = g_strdup (data->child_stderr->str); + + ret = TRUE; + + out: + return ret; +} -- GitLab From d74aad8152a7c51999fffa9abe28e4306a052399 Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 13:15:17 +0800 Subject: [PATCH 02/17] check netgroup.h header file Signed-off-by: Gustavo Lima Chaves --- src/polkitbackend/polkitbackendduktapeauthority.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index ae98453..543d6fd 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -26,7 +26,11 @@ #include #include #include +#ifdef HAVE_NETGROUP_H +#include +#else #include +#endif #include #include #include -- GitLab From 69c761506cbe458807e4ae2742c9e05bc60dad3d Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 10:59:03 +0800 Subject: [PATCH 03/17] check return value Signed-off-by: Gustavo Lima Chaves --- src/polkitbackend/polkitbackendduktapeauthority.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index 543d6fd..a54ed5b 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -249,7 +249,11 @@ reload_scripts (PolkitBackendJsAuthority *authority) duk_context *cx = authority->priv->cx; duk_set_top (cx, 0); - duk_get_global_string (cx, "polkit"); + if (!duk_get_global_string (cx, "polkit")) { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error deleting old rules, not loading new ones"); + return; + } duk_push_string (cx, "_deleteRules"); duk_call_prop (cx, 0, 0); -- GitLab From f1536c4899934fd3c8243fda2d084a472fe57d2e Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 11:22:39 +0800 Subject: [PATCH 04/17] check return value Signed-off-by: Gustavo Lima Chaves --- src/polkitbackend/polkitbackendduktapeauthority.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index a54ed5b..1a7e6d3 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -656,7 +656,10 @@ push_action_and_details (duk_context *cx, gchar **keys; guint n; - duk_get_global_string (cx, "Action"); + if (!duk_get_global_string (cx, "Action")) { + return FALSE; + } + duk_new (cx, 0); set_property_str (cx, "id", action_id); @@ -699,7 +702,12 @@ polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveA duk_context *cx = authority->priv->cx; duk_set_top (cx, 0); - duk_get_global_string (cx, "polkit"); + if (!duk_get_global_string (cx, "polkit")) { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error deleting old rules, not loading new ones"); + goto out; + } + duk_push_string (cx, "_runAdminRules"); if (!push_action_and_details (cx, action_id, details, &error)) -- GitLab From ca15eecf5dc7755947515c1bfc651fd8770aaf8f Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 13:17:16 +0800 Subject: [PATCH 05/17] check return value Signed-off-by: Gustavo Lima Chaves --- src/polkitbackend/polkitbackendduktapeauthority.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index 1a7e6d3..3f1b32d 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -550,7 +550,10 @@ push_subject (duk_context *cx, char *seat_str = NULL; char *session_str = NULL; - duk_get_global_string (cx, "Subject"); + if (!duk_get_global_string (cx, "Subject")) { + return FALSE; + } + duk_new (cx, 0); if (POLKIT_IS_UNIX_PROCESS (subject)) @@ -789,8 +792,11 @@ polkit_backend_js_authority_check_authorization_sync (PolkitBackendInteractiveAu gboolean good = FALSE; duk_context *cx = authority->priv->cx; + if (!duk_get_global_string (cx, "polkit")) { + goto out; + } + duk_set_top (cx, 0); - duk_get_global_string (cx, "polkit"); duk_push_string (cx, "_runRules"); if (!push_action_and_details (cx, action_id, details, &error)) -- GitLab From 870348365cc0166e14f28e0d144ed552bba4d794 Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 13:18:13 +0800 Subject: [PATCH 06/17] check return value Signed-off-by: Gustavo Lima Chaves --- src/polkitbackend/polkitbackendduktapeauthority.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index 3f1b32d..6294ad9 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -843,7 +843,8 @@ polkit_backend_js_authority_check_authorization_sync (PolkitBackendInteractiveAu out: if (!good) ret = POLKIT_IMPLICIT_AUTHORIZATION_NOT_AUTHORIZED; - g_free (ret_str); + if (ret_str != NULL) + g_free (ret_str); return ret; } -- GitLab From 81c916ff08fdcee3c7340c4b2d4632086b89666c Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 11:23:04 +0800 Subject: [PATCH 07/17] fix typecase Signed-off-by: Gustavo Lima Chaves --- src/polkitbackend/polkitbackendduktapeauthority.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index 6294ad9..d466c9d 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -1191,7 +1191,7 @@ static void utils_on_cancelled (GCancellable *cancellable, gpointer user_data) { - UtilsSpawnData *data = user_data; + UtilsSpawnData *data = (UtilsSpawnData *)user_data; GError *error; error = NULL; @@ -1206,7 +1206,7 @@ utils_read_child_stderr (GIOChannel *channel, GIOCondition condition, gpointer user_data) { - UtilsSpawnData *data = user_data; + UtilsSpawnData *data = (UtilsSpawnData *)user_data; gchar buf[1024]; gsize bytes_read; @@ -1220,7 +1220,7 @@ utils_read_child_stdout (GIOChannel *channel, GIOCondition condition, gpointer user_data) { - UtilsSpawnData *data = user_data; + UtilsSpawnData *data = (UtilsSpawnData *)user_data; gchar buf[1024]; gsize bytes_read; @@ -1234,7 +1234,7 @@ utils_child_watch_cb (GPid pid, gint status, gpointer user_data) { - UtilsSpawnData *data = user_data; + UtilsSpawnData *data = (UtilsSpawnData *)user_data; gchar *buf; gsize buf_size; @@ -1263,7 +1263,7 @@ utils_child_watch_cb (GPid pid, static gboolean utils_timeout_cb (gpointer user_data) { - UtilsSpawnData *data = user_data; + UtilsSpawnData *data = (UtilsSpawnData *)user_data; data->timed_out = TRUE; -- GitLab From acb956bf52f0a78bf7aaf925876f96e97a146995 Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 18:04:27 +0800 Subject: [PATCH 08/17] typecase Signed-off-by: Gustavo Lima Chaves --- src/polkitbackend/polkitbackendduktapeauthority.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index d466c9d..237b1ad 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -915,8 +915,8 @@ spawn_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { - SpawnData *data = user_data; - data->res = g_object_ref (res); + SpawnData *data = (SpawnData *)user_data; + data->res = (GAsyncResult*)g_object_ref (res); g_main_loop_quit (data->loop); } @@ -1292,12 +1292,12 @@ utils_spawn (const gchar *const *argv, data->simple = g_simple_async_result_new (NULL, callback, user_data, - utils_spawn); + (gpointer*)utils_spawn); data->main_context = g_main_context_get_thread_default (); if (data->main_context != NULL) g_main_context_ref (data->main_context); - data->cancellable = cancellable != NULL ? g_object_ref (cancellable) : NULL; + data->cancellable = cancellable != NULL ? (GCancellable*)g_object_ref (cancellable) : NULL; data->child_stdout = g_string_new (NULL); data->child_stderr = g_string_new (NULL); @@ -1397,7 +1397,7 @@ utils_spawn_finish (GAsyncResult *res, if (g_simple_async_result_propagate_error (simple, error)) goto out; - data = g_simple_async_result_get_op_res_gpointer (simple); + data = (UtilsSpawnData*)g_simple_async_result_get_op_res_gpointer (simple); if (data->timed_out) { -- GitLab From be060e4d48aceb09af34868b555b6c73c7afdabb Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 13:53:23 +0800 Subject: [PATCH 09/17] some change Signed-off-by: Gustavo Lima Chaves --- .../polkitbackendduktapeauthority.c | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index 237b1ad..fad9017 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -207,18 +207,22 @@ load_scripts (PolkitBackendJsAuthority *authority) for (l = files; l != NULL; l = l->next) { - const gchar *filename = l->data; - + const gchar *filename = (gchar *)l->data; #if (DUK_VERSION >= 20000) - gchar *contents; - gsize length; - GError *error = NULL; - if (!g_file_get_contents (filename, &contents, &length, &error)){ - g_warning("Error when file contents of %s: %s\n", filename, error->message); - g_error_free (error); - continue; - } - if (duk_peval_lstring_noresult(cx, contents,length) != 0) + GFile *file = g_file_new_for_path (filename); + char *contents; + gsize len; + if (!g_file_load_contents (file, NULL, &contents, &len, NULL, NULL)) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error compiling script %s", + filename); + g_object_unref (file); + continue; + } + + g_object_unref (file); + if (duk_peval_lstring_noresult(cx, contents,len) != 0) #else if (duk_peval_file_noresult (cx, filename) != 0) #endif -- GitLab From 2ffb62048a5ebedfe3bb053feb7385c7270ede28 Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 15:25:45 +0800 Subject: [PATCH 10/17] some change Signed-off-by: Gustavo Lima Chaves --- .../polkitbackendduktapeauthority.c | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index fad9017..6fac3be 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -125,6 +125,18 @@ G_DEFINE_TYPE (PolkitBackendJsAuthority, polkit_backend_js_authority, POLKIT_BAC /* ---------------------------------------------------------------------------------------------------- */ +static duk_ret_t js_polkit_log (duk_context *cx); +static duk_ret_t js_polkit_spawn (duk_context *cx); +static duk_ret_t js_polkit_user_is_in_netgroup (duk_context *cx); + +static const duk_function_list_entry js_polkit_functions[] = +{ + { "log", js_polkit_log, 1 }, + { "spawn", js_polkit_spawn, 1 }, + { "_userIsInNetGroup", js_polkit_user_is_in_netgroup, 2 }, + { NULL, NULL, 0 }, +}; + static void polkit_backend_js_authority_init (PolkitBackendJsAuthority *authority) { @@ -347,18 +359,6 @@ setup_file_monitors (PolkitBackendJsAuthority *authority) authority->priv->dir_monitors = (GFileMonitor**) g_ptr_array_free (p, FALSE); } -static duk_ret_t js_polkit_log (duk_context *cx); -static duk_ret_t js_polkit_spawn (duk_context *cx); -static duk_ret_t js_polkit_user_is_in_netgroup (duk_context *cx); - -static const duk_function_list_entry js_polkit_functions[] = -{ - { "log", js_polkit_log, 1 }, - { "spawn", js_polkit_spawn, 1 }, - { "_userIsInNetGroup", js_polkit_user_is_in_netgroup, 2 }, - { NULL, NULL, 0 }, -}; - static void polkit_backend_js_authority_constructed (GObject *object) { -- GitLab From edb70ef69eed3275f5654510d135e680eb46c85d Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 15:25:35 +0800 Subject: [PATCH 11/17] remove WATCHDOG_TIMEOUT define Signed-off-by: Gustavo Lima Chaves --- src/polkitbackend/polkitbackendduktapeauthority.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index 6fac3be..51e03fd 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -69,7 +69,6 @@ struct _PolkitBackendJsAuthorityPrivate duk_context *cx; }; -#define WATCHDOG_TIMEOUT (15 * G_TIME_SPAN_SECOND) static void utils_spawn (const gchar *const *argv, guint timeout_seconds, -- GitLab From 906ae404f29f15ef8c529b999bf091b5d18ed7ac Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 12:46:40 +0800 Subject: [PATCH 12/17] add meson build system support Signed-off-by: Gustavo Lima Chaves --- meson.build | 11 ++++++++++- meson_options.txt | 1 + src/polkitbackend/meson.build | 10 ++++++++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/meson.build b/meson.build index 858078d..4e44723 100644 --- a/meson.build +++ b/meson.build @@ -133,7 +133,13 @@ expat_dep = dependency('expat') assert(cc.has_header('expat.h', dependencies: expat_dep), 'Can\'t find expat.h. Please install expat.') assert(cc.has_function('XML_ParserCreate', dependencies: expat_dep), 'Can\'t find expat library. Please install expat.') -mozjs_dep = dependency('mozjs-78') +js_engine = get_option('js_engine') +if js_engine == 'duktape' + js_dep = dependency('duktape') + libm_dep = cc.find_library('m') +elif js_engine == 'mozjs' + js_dep = dependency('mozjs-78') +endif dbus_dep = dependency('dbus-1', required: false) dbus_policydir = pk_prefix / pk_datadir / 'dbus-1/system.d' @@ -361,6 +367,9 @@ if enable_logind output += ' systemdsystemunitdir: ' + systemd_systemdsystemunitdir + '\n' endif output += ' polkitd user: ' + polkitd_user + ' \n' +output += ' Javascript engine: ' + js_engine + '\n' +if enable_logind +endif output += ' PAM support: ' + enable_pam.to_string() + '\n\n' if enable_pam output += ' PAM file auth: ' + pam_conf['PAM_FILE_INCLUDE_AUTH'] + '\n' diff --git a/meson_options.txt b/meson_options.txt index 25e3e77..76aa311 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -16,3 +16,4 @@ option('introspection', type: 'boolean', value: true, description: 'Enable intro option('gtk_doc', type: 'boolean', value: false, description: 'use gtk-doc to build documentation') option('man', type: 'boolean', value: false, description: 'build manual pages') +option('js_engine', type: 'combo', choices: ['mozjs', 'duktape'], value: 'duktape', description: 'javascript engine') diff --git a/src/polkitbackend/meson.build b/src/polkitbackend/meson.build index 64f0e4a..489897d 100644 --- a/src/polkitbackend/meson.build +++ b/src/polkitbackend/meson.build @@ -5,7 +5,6 @@ sources = files( 'polkitbackendactionpool.c', 'polkitbackendauthority.c', 'polkitbackendinteractiveauthority.c', - 'polkitbackendjsauthority.cpp', ) output = 'initjs.h' @@ -21,7 +20,7 @@ sources += custom_target( deps = [ expat_dep, libpolkit_gobject_dep, - mozjs_dep, + js_dep, ] c_flags = [ @@ -31,6 +30,13 @@ c_flags = [ '-DPACKAGE_SYSCONF_DIR="@0@"'.format(pk_prefix / pk_sysconfdir), ] +if js_engine == 'duktape' + sources += files('polkitbackendduktapeauthority.c') + deps += libm_dep +elif js_engine == 'mozjs' + sources += files('polkitbackendjsauthority.cpp') +endif + if enable_logind sources += files('polkitbackendsessionmonitor-systemd.c') -- GitLab From 1380b505c25be4aebe54b1b4223a570d64af83cc Mon Sep 17 00:00:00 2001 From: Wu Xiaotian Date: Sun, 22 Nov 2020 18:49:14 +0800 Subject: [PATCH 13/17] fix run error Signed-off-by: Gustavo Lima Chaves --- src/polkitbackend/polkitbackendduktapeauthority.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index 51e03fd..4b4f8fd 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -795,11 +795,11 @@ polkit_backend_js_authority_check_authorization_sync (PolkitBackendInteractiveAu gboolean good = FALSE; duk_context *cx = authority->priv->cx; + duk_set_top (cx, 0); if (!duk_get_global_string (cx, "polkit")) { goto out; } - duk_set_top (cx, 0); duk_push_string (cx, "_runRules"); if (!push_action_and_details (cx, action_id, details, &error)) -- GitLab From 6856a704b70378948ef5f66e9b09555d97d4070b Mon Sep 17 00:00:00 2001 From: Gustavo Lima Chaves Date: Fri, 10 Sep 2021 15:17:58 -0700 Subject: [PATCH 14/17] Deduplicate code for "Add duktape as JS engine backend" effort/MR This leverages Wu Xiaotian (@yetist)'s original MR (https://gitlab.freedesktop.org/polkit/polkit/-/merge_requests/35), in an effort to complete said work. This is the first of the requests from maintainers--to reduce eliminate code duplication. The runaway-killer missing functionality will come in the sequence. Signed-off-by: Gustavo Lima Chaves --- src/polkitbackend/Makefile.am | 1 + src/polkitbackend/meson.build | 1 + src/polkitbackend/polkitbackendcommon.c | 530 +++++++++++++ src/polkitbackend/polkitbackendcommon.h | 156 ++++ .../polkitbackendduktapeauthority.c | 714 ++---------------- .../polkitbackendjsauthority.cpp | 711 ++--------------- 6 files changed, 790 insertions(+), 1323 deletions(-) create mode 100644 src/polkitbackend/polkitbackendcommon.c create mode 100644 src/polkitbackend/polkitbackendcommon.h diff --git a/src/polkitbackend/Makefile.am b/src/polkitbackend/Makefile.am index abcbc6f..6a8b4ae 100644 --- a/src/polkitbackend/Makefile.am +++ b/src/polkitbackend/Makefile.am @@ -31,6 +31,7 @@ libpolkit_backend_1_la_SOURCES = \ polkitbackend.h \ polkitbackendtypes.h \ polkitbackendprivate.h \ + polkitbackendcommon.h polkitbackendcommon.c \ polkitbackendauthority.h polkitbackendauthority.c \ polkitbackendinteractiveauthority.h polkitbackendinteractiveauthority.c \ polkitbackendjsauthority.h \ diff --git a/src/polkitbackend/meson.build b/src/polkitbackend/meson.build index 489897d..9ec01b2 100644 --- a/src/polkitbackend/meson.build +++ b/src/polkitbackend/meson.build @@ -4,6 +4,7 @@ sources = files( 'polkitbackendactionlookup.c', 'polkitbackendactionpool.c', 'polkitbackendauthority.c', + 'polkitbackendcommon.c', 'polkitbackendinteractiveauthority.c', ) diff --git a/src/polkitbackend/polkitbackendcommon.c b/src/polkitbackend/polkitbackendcommon.c new file mode 100644 index 0000000..6783dff --- /dev/null +++ b/src/polkitbackend/polkitbackendcommon.c @@ -0,0 +1,530 @@ +/* + * Copyright (C) 2008 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "polkitbackendcommon.h" + +static void +utils_child_watch_from_release_cb (GPid pid, + gint status, + gpointer user_data) +{ +} + +static void +utils_spawn_data_free (UtilsSpawnData *data) +{ + if (data->timeout_source != NULL) + { + g_source_destroy (data->timeout_source); + data->timeout_source = NULL; + } + + /* Nuke the child, if necessary */ + if (data->child_watch_source != NULL) + { + g_source_destroy (data->child_watch_source); + data->child_watch_source = NULL; + } + + if (data->child_pid != 0) + { + GSource *source; + kill (data->child_pid, SIGTERM); + /* OK, we need to reap for the child ourselves - we don't want + * to use waitpid() because that might block the calling + * thread (the child might handle SIGTERM and use several + * seconds for cleanup/rollback). + * + * So we use GChildWatch instead. + * + * Avoid taking a references to ourselves. but note that we need + * to pass the GSource so we can nuke it once handled. + */ + source = g_child_watch_source_new (data->child_pid); + g_source_set_callback (source, + (GSourceFunc) utils_child_watch_from_release_cb, + source, + (GDestroyNotify) g_source_destroy); + g_source_attach (source, data->main_context); + g_source_unref (source); + data->child_pid = 0; + } + + if (data->child_stdout != NULL) + { + g_string_free (data->child_stdout, TRUE); + data->child_stdout = NULL; + } + + if (data->child_stderr != NULL) + { + g_string_free (data->child_stderr, TRUE); + data->child_stderr = NULL; + } + + if (data->child_stdout_channel != NULL) + { + g_io_channel_unref (data->child_stdout_channel); + data->child_stdout_channel = NULL; + } + if (data->child_stderr_channel != NULL) + { + g_io_channel_unref (data->child_stderr_channel); + data->child_stderr_channel = NULL; + } + + if (data->child_stdout_source != NULL) + { + g_source_destroy (data->child_stdout_source); + data->child_stdout_source = NULL; + } + if (data->child_stderr_source != NULL) + { + g_source_destroy (data->child_stderr_source); + data->child_stderr_source = NULL; + } + + if (data->child_stdout_fd != -1) + { + g_warn_if_fail (close (data->child_stdout_fd) == 0); + data->child_stdout_fd = -1; + } + if (data->child_stderr_fd != -1) + { + g_warn_if_fail (close (data->child_stderr_fd) == 0); + data->child_stderr_fd = -1; + } + + if (data->cancellable_handler_id > 0) + { + g_cancellable_disconnect (data->cancellable, data->cancellable_handler_id); + data->cancellable_handler_id = 0; + } + + if (data->main_context != NULL) + g_main_context_unref (data->main_context); + + if (data->cancellable != NULL) + g_object_unref (data->cancellable); + + g_slice_free (UtilsSpawnData, data); +} + +/* called in the thread where @cancellable was cancelled */ +static void +utils_on_cancelled (GCancellable *cancellable, + gpointer user_data) +{ + UtilsSpawnData *data = (UtilsSpawnData *)user_data; + GError *error; + + error = NULL; + g_warn_if_fail (g_cancellable_set_error_if_cancelled (cancellable, &error)); + g_simple_async_result_take_error (data->simple, error); + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); +} + +static gboolean +utils_timeout_cb (gpointer user_data) +{ + UtilsSpawnData *data = (UtilsSpawnData *)user_data; + + data->timed_out = TRUE; + + /* ok, timeout is history, make sure we don't free it in spawn_data_free() */ + data->timeout_source = NULL; + + /* we're done */ + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); + + return FALSE; /* remove source */ +} + +static void +utils_child_watch_cb (GPid pid, + gint status, + gpointer user_data) +{ + UtilsSpawnData *data = (UtilsSpawnData *)user_data; + gchar *buf; + gsize buf_size; + + if (g_io_channel_read_to_end (data->child_stdout_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) + { + g_string_append_len (data->child_stdout, buf, buf_size); + g_free (buf); + } + if (g_io_channel_read_to_end (data->child_stderr_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) + { + g_string_append_len (data->child_stderr, buf, buf_size); + g_free (buf); + } + + data->exit_status = status; + + /* ok, child watch is history, make sure we don't free it in spawn_data_free() */ + data->child_pid = 0; + data->child_watch_source = NULL; + + /* we're done */ + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); +} + +static gboolean +utils_read_child_stderr (GIOChannel *channel, + GIOCondition condition, + gpointer user_data) +{ + UtilsSpawnData *data = (UtilsSpawnData *)user_data; + gchar buf[1024]; + gsize bytes_read; + + g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); + g_string_append_len (data->child_stderr, buf, bytes_read); + return TRUE; +} + +static gboolean +utils_read_child_stdout (GIOChannel *channel, + GIOCondition condition, + gpointer user_data) +{ + UtilsSpawnData *data = (UtilsSpawnData *)user_data; + gchar buf[1024]; + gsize bytes_read; + + g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); + g_string_append_len (data->child_stdout, buf, bytes_read); + return TRUE; +} + +void +polkit_backend_common_spawn (const gchar *const *argv, + guint timeout_seconds, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + UtilsSpawnData *data; + GError *error; + + data = g_slice_new0 (UtilsSpawnData); + data->timeout_seconds = timeout_seconds; + data->simple = g_simple_async_result_new (NULL, + callback, + user_data, + (gpointer*)polkit_backend_common_spawn); + data->main_context = g_main_context_get_thread_default (); + if (data->main_context != NULL) + g_main_context_ref (data->main_context); + + data->cancellable = cancellable != NULL ? (GCancellable*)g_object_ref (cancellable) : NULL; + + data->child_stdout = g_string_new (NULL); + data->child_stderr = g_string_new (NULL); + data->child_stdout_fd = -1; + data->child_stderr_fd = -1; + + /* the life-cycle of UtilsSpawnData is tied to its GSimpleAsyncResult */ + g_simple_async_result_set_op_res_gpointer (data->simple, data, (GDestroyNotify) utils_spawn_data_free); + + error = NULL; + if (data->cancellable != NULL) + { + /* could already be cancelled */ + error = NULL; + if (g_cancellable_set_error_if_cancelled (data->cancellable, &error)) + { + g_simple_async_result_take_error (data->simple, error); + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); + goto out; + } + + data->cancellable_handler_id = g_cancellable_connect (data->cancellable, + G_CALLBACK (utils_on_cancelled), + data, + NULL); + } + + error = NULL; + if (!g_spawn_async_with_pipes (NULL, /* working directory */ + (gchar **) argv, + NULL, /* envp */ + G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, + NULL, /* child_setup */ + NULL, /* child_setup's user_data */ + &(data->child_pid), + NULL, /* gint *stdin_fd */ + &(data->child_stdout_fd), + &(data->child_stderr_fd), + &error)) + { + g_prefix_error (&error, "Error spawning: "); + g_simple_async_result_take_error (data->simple, error); + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); + goto out; + } + + if (timeout_seconds > 0) + { + data->timeout_source = g_timeout_source_new_seconds (timeout_seconds); + g_source_set_priority (data->timeout_source, G_PRIORITY_DEFAULT); + g_source_set_callback (data->timeout_source, utils_timeout_cb, data, NULL); + g_source_attach (data->timeout_source, data->main_context); + g_source_unref (data->timeout_source); + } + + data->child_watch_source = g_child_watch_source_new (data->child_pid); + g_source_set_callback (data->child_watch_source, (GSourceFunc) utils_child_watch_cb, data, NULL); + g_source_attach (data->child_watch_source, data->main_context); + g_source_unref (data->child_watch_source); + + data->child_stdout_channel = g_io_channel_unix_new (data->child_stdout_fd); + g_io_channel_set_flags (data->child_stdout_channel, G_IO_FLAG_NONBLOCK, NULL); + data->child_stdout_source = g_io_create_watch (data->child_stdout_channel, G_IO_IN); + g_source_set_callback (data->child_stdout_source, (GSourceFunc) utils_read_child_stdout, data, NULL); + g_source_attach (data->child_stdout_source, data->main_context); + g_source_unref (data->child_stdout_source); + + data->child_stderr_channel = g_io_channel_unix_new (data->child_stderr_fd); + g_io_channel_set_flags (data->child_stderr_channel, G_IO_FLAG_NONBLOCK, NULL); + data->child_stderr_source = g_io_create_watch (data->child_stderr_channel, G_IO_IN); + g_source_set_callback (data->child_stderr_source, (GSourceFunc) utils_read_child_stderr, data, NULL); + g_source_attach (data->child_stderr_source, data->main_context); + g_source_unref (data->child_stderr_source); + + out: + ; +} + +void +polkit_backend_common_on_dir_monitor_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (user_data); + + /* TODO: maybe rate-limit so storms of events are collapsed into one with a 500ms resolution? + * Because when editing a file with emacs we get 4-8 events.. + */ + + if (file != NULL) + { + gchar *name; + + name = g_file_get_basename (file); + + /* g_print ("event_type=%d file=%p name=%s\n", event_type, file, name); */ + if (!g_str_has_prefix (name, ".") && + !g_str_has_prefix (name, "#") && + g_str_has_suffix (name, ".rules") && + (event_type == G_FILE_MONITOR_EVENT_CREATED || + event_type == G_FILE_MONITOR_EVENT_DELETED || + event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Reloading rules"); + polkit_backend_common_reload_scripts (authority); + } + g_free (name); + } +} + +gboolean +polkit_backend_common_spawn_finish (GAsyncResult *res, + gint *out_exit_status, + gchar **out_standard_output, + gchar **out_standard_error, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + UtilsSpawnData *data; + gboolean ret = FALSE; + + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == polkit_backend_common_spawn); + + if (g_simple_async_result_propagate_error (simple, error)) + goto out; + + data = (UtilsSpawnData*)g_simple_async_result_get_op_res_gpointer (simple); + + if (data->timed_out) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_TIMED_OUT, + "Timed out after %d seconds", + data->timeout_seconds); + goto out; + } + + if (out_exit_status != NULL) + *out_exit_status = data->exit_status; + + if (out_standard_output != NULL) + *out_standard_output = g_strdup (data->child_stdout->str); + + if (out_standard_error != NULL) + *out_standard_error = g_strdup (data->child_stderr->str); + + ret = TRUE; + + out: + return ret; +} + +static const gchar * +polkit_backend_js_authority_get_name (PolkitBackendAuthority *authority) +{ + return "js"; +} + +static const gchar * +polkit_backend_js_authority_get_version (PolkitBackendAuthority *authority) +{ + return PACKAGE_VERSION; +} + +static PolkitAuthorityFeatures +polkit_backend_js_authority_get_features (PolkitBackendAuthority *authority) +{ + return POLKIT_AUTHORITY_FEATURES_TEMPORARY_AUTHORIZATION; +} + +void +polkit_backend_common_js_authority_class_init_common (PolkitBackendJsAuthorityClass *klass) +{ + GObjectClass *gobject_class; + PolkitBackendAuthorityClass *authority_class; + PolkitBackendInteractiveAuthorityClass *interactive_authority_class; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = polkit_backend_common_js_authority_finalize; + gobject_class->set_property = polkit_backend_common_js_authority_set_property; + gobject_class->constructed = polkit_backend_common_js_authority_constructed; + + authority_class = POLKIT_BACKEND_AUTHORITY_CLASS (klass); + authority_class->get_name = polkit_backend_js_authority_get_name; + authority_class->get_version = polkit_backend_js_authority_get_version; + authority_class->get_features = polkit_backend_js_authority_get_features; + + interactive_authority_class = POLKIT_BACKEND_INTERACTIVE_AUTHORITY_CLASS (klass); + interactive_authority_class->get_admin_identities = polkit_backend_common_js_authority_get_admin_auth_identities; + interactive_authority_class->check_authorization_sync = polkit_backend_common_js_authority_check_authorization_sync; + + g_object_class_install_property (gobject_class, + PROP_RULES_DIRS, + g_param_spec_boxed ("rules-dirs", + NULL, + NULL, + G_TYPE_STRV, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); +} + +gint +polkit_backend_common_rules_file_name_cmp (const gchar *a, + const gchar *b) +{ + gint ret; + const gchar *a_base; + const gchar *b_base; + + a_base = strrchr (a, '/'); + b_base = strrchr (b, '/'); + + g_assert (a_base != NULL); + g_assert (b_base != NULL); + a_base += 1; + b_base += 1; + + ret = g_strcmp0 (a_base, b_base); + if (ret == 0) + { + /* /etc wins over /usr */ + ret = g_strcmp0 (a, b); + g_assert (ret != 0); + } + + return ret; +} + +const gchar * +polkit_backend_common_get_signal_name (gint signal_number) +{ + switch (signal_number) + { +#define _HANDLE_SIG(sig) case sig: return #sig; + _HANDLE_SIG (SIGHUP); + _HANDLE_SIG (SIGINT); + _HANDLE_SIG (SIGQUIT); + _HANDLE_SIG (SIGILL); + _HANDLE_SIG (SIGABRT); + _HANDLE_SIG (SIGFPE); + _HANDLE_SIG (SIGKILL); + _HANDLE_SIG (SIGSEGV); + _HANDLE_SIG (SIGPIPE); + _HANDLE_SIG (SIGALRM); + _HANDLE_SIG (SIGTERM); + _HANDLE_SIG (SIGUSR1); + _HANDLE_SIG (SIGUSR2); + _HANDLE_SIG (SIGCHLD); + _HANDLE_SIG (SIGCONT); + _HANDLE_SIG (SIGSTOP); + _HANDLE_SIG (SIGTSTP); + _HANDLE_SIG (SIGTTIN); + _HANDLE_SIG (SIGTTOU); + _HANDLE_SIG (SIGBUS); +#ifdef SIGPOLL + _HANDLE_SIG (SIGPOLL); +#endif + _HANDLE_SIG (SIGPROF); + _HANDLE_SIG (SIGSYS); + _HANDLE_SIG (SIGTRAP); + _HANDLE_SIG (SIGURG); + _HANDLE_SIG (SIGVTALRM); + _HANDLE_SIG (SIGXCPU); + _HANDLE_SIG (SIGXFSZ); +#undef _HANDLE_SIG + default: + break; + } + return "UNKNOWN_SIGNAL"; +} + +void +polkit_backend_common_spawn_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SpawnData *data = (SpawnData *)user_data; + data->res = (GAsyncResult*)g_object_ref (res); + g_main_loop_quit (data->loop); +} diff --git a/src/polkitbackend/polkitbackendcommon.h b/src/polkitbackend/polkitbackendcommon.h new file mode 100644 index 0000000..6d0d267 --- /dev/null +++ b/src/polkitbackend/polkitbackendcommon.h @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2008 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#if !defined (_POLKIT_BACKEND_COMPILATION) && !defined(_POLKIT_BACKEND_INSIDE_POLKIT_BACKEND_H) +#error "Only can be included directly, this file may disappear or change contents." +#endif + +#ifndef __POLKIT_BACKEND_COMMON_H +#define __POLKIT_BACKEND_COMMON_H + +#include "config.h" +#include +#include +#include +#include +#ifdef HAVE_NETGROUP_H +#include +#else +#include +#endif +#include +#include +#include +#include //here, all things glib via glib.h (including -> gspawn.h) + +#include +#include "polkitbackendjsauthority.h" + +#include + +#ifdef HAVE_LIBSYSTEMD +#include +#endif /* HAVE_LIBSYSTEMD */ + +#ifdef __cplusplus +extern "C" { +#endif + +enum +{ + PROP_0, + PROP_RULES_DIRS, +}; + +typedef struct +{ + GSimpleAsyncResult *simple; /* borrowed reference */ + GMainContext *main_context; /* may be NULL */ + + GCancellable *cancellable; /* may be NULL */ + gulong cancellable_handler_id; + + GPid child_pid; + gint child_stdout_fd; + gint child_stderr_fd; + + GIOChannel *child_stdout_channel; + GIOChannel *child_stderr_channel; + + GSource *child_watch_source; + GSource *child_stdout_source; + GSource *child_stderr_source; + + guint timeout_seconds; + gboolean timed_out; + GSource *timeout_source; + + GString *child_stdout; + GString *child_stderr; + + gint exit_status; +} UtilsSpawnData; + +typedef struct +{ + GMainLoop *loop; + GAsyncResult *res; +} SpawnData; + +void polkit_backend_common_spawn (const gchar *const *argv, + guint timeout_seconds, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +void polkit_backend_common_spawn_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data); +gboolean polkit_backend_common_spawn_finish (GAsyncResult *res, + gint *out_exit_status, + gchar **out_standard_output, + gchar **out_standard_error, + GError **error); + +void polkit_backend_common_on_dir_monitor_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data); + +void polkit_backend_common_js_authority_class_init_common (PolkitBackendJsAuthorityClass *klass); + +gint polkit_backend_common_rules_file_name_cmp (const gchar *a, + const gchar *b); + +const gchar *polkit_backend_common_get_signal_name (gint signal_number); + +/* To be provided by each JS backend, from here onwards ---------------------------------------------- */ + +void polkit_backend_common_reload_scripts (PolkitBackendJsAuthority *authority); +void polkit_backend_common_js_authority_finalize (GObject *object); +void polkit_backend_common_js_authority_constructed (GObject *object); +GList *polkit_backend_common_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *_authority, + PolkitSubject *caller, + PolkitSubject *subject, + PolkitIdentity *user_for_subject, + gboolean subject_is_local, + gboolean subject_is_active, + const gchar *action_id, + PolkitDetails *details); +void polkit_backend_common_js_authority_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +PolkitImplicitAuthorization polkit_backend_common_js_authority_check_authorization_sync (PolkitBackendInteractiveAuthority *_authority, + PolkitSubject *caller, + PolkitSubject *subject, + PolkitIdentity *user_for_subject, + gboolean subject_is_local, + gboolean subject_is_active, + const gchar *action_id, + PolkitDetails *details, + PolkitImplicitAuthorization implicit); +#ifdef __cplusplus +} +#endif + +#endif /* __POLKIT_BACKEND_COMMON_H */ + diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index 4b4f8fd..a2b4420 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -21,32 +21,12 @@ * Author: David Zeuthen */ -#include "config.h" -#include -#include -#include -#include -#ifdef HAVE_NETGROUP_H -#include -#else -#include -#endif -#include -#include -#include -#include - -#include -#include "polkitbackendjsauthority.h" - -#include +#include "polkitbackendcommon.h" -#ifdef HAVE_LIBSYSTEMD -#include -#endif /* HAVE_LIBSYSTEMD */ +#include "duktape.h" +/* Built source and not too big to worry about deduplication */ #include "initjs.h" /* init.js */ -#include "duktape.h" /** * SECTION:polkitbackendjsauthority @@ -54,10 +34,9 @@ * @short_description: JS Authority * @stability: Unstable * - * An implementation of #PolkitBackendAuthority that reads and - * evalates Javascript files and supports interaction with - * authentication agents (virtue of being based on - * #PolkitBackendInteractiveAuthority). + * An (Duktape-based) implementation of #PolkitBackendAuthority that reads and + * evaluates Javascript files and supports interaction with authentication + * agents (virtue of being based on #PolkitBackendInteractiveAuthority). */ /* ---------------------------------------------------------------------------------------------------- */ @@ -66,64 +45,16 @@ struct _PolkitBackendJsAuthorityPrivate { gchar **rules_dirs; GFileMonitor **dir_monitors; /* NULL-terminated array of GFileMonitor instances */ - duk_context *cx; -}; - - -static void utils_spawn (const gchar *const *argv, - guint timeout_seconds, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); - -gboolean utils_spawn_finish (GAsyncResult *res, - gint *out_exit_status, - gchar **out_standard_output, - gchar **out_standard_error, - GError **error); -static void on_dir_monitor_changed (GFileMonitor *monitor, - GFile *file, - GFile *other_file, - GFileMonitorEvent event_type, - gpointer user_data); - -/* ---------------------------------------------------------------------------------------------------- */ - -enum -{ - PROP_0, - PROP_RULES_DIRS, + duk_context *cx; }; /* ---------------------------------------------------------------------------------------------------- */ -static GList *polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *authority, - PolkitSubject *caller, - PolkitSubject *subject, - PolkitIdentity *user_for_subject, - gboolean subject_is_local, - gboolean subject_is_active, - const gchar *action_id, - PolkitDetails *details); - -static PolkitImplicitAuthorization polkit_backend_js_authority_check_authorization_sync ( - PolkitBackendInteractiveAuthority *authority, - PolkitSubject *caller, - PolkitSubject *subject, - PolkitIdentity *user_for_subject, - gboolean subject_is_local, - gboolean subject_is_active, - const gchar *action_id, - PolkitDetails *details, - PolkitImplicitAuthorization implicit); - G_DEFINE_TYPE (PolkitBackendJsAuthority, polkit_backend_js_authority, POLKIT_BACKEND_TYPE_INTERACTIVE_AUTHORITY); /* ---------------------------------------------------------------------------------------------------- */ -/* ---------------------------------------------------------------------------------------------------- */ - static duk_ret_t js_polkit_log (duk_context *cx); static duk_ret_t js_polkit_spawn (duk_context *cx); static duk_ret_t js_polkit_user_is_in_netgroup (duk_context *cx); @@ -144,33 +75,6 @@ polkit_backend_js_authority_init (PolkitBackendJsAuthority *authority) PolkitBackendJsAuthorityPrivate); } -static gint -rules_file_name_cmp (const gchar *a, - const gchar *b) -{ - gint ret; - const gchar *a_base; - const gchar *b_base; - - a_base = strrchr (a, '/'); - b_base = strrchr (b, '/'); - - g_assert (a_base != NULL); - g_assert (b_base != NULL); - a_base += 1; - b_base += 1; - - ret = g_strcmp0 (a_base, b_base); - if (ret == 0) - { - /* /etc wins over /usr */ - ret = g_strcmp0 (a, b); - g_assert (ret != 0); - } - - return ret; -} - static void load_scripts (PolkitBackendJsAuthority *authority) { @@ -214,7 +118,7 @@ load_scripts (PolkitBackendJsAuthority *authority) } } - files = g_list_sort (files, (GCompareFunc) rules_file_name_cmp); + files = g_list_sort (files, (GCompareFunc) polkit_backend_common_rules_file_name_cmp); for (l = files; l != NULL; l = l->next) { @@ -258,8 +162,8 @@ load_scripts (PolkitBackendJsAuthority *authority) g_list_free_full (files, g_free); } -static void -reload_scripts (PolkitBackendJsAuthority *authority) +void +polkit_backend_common_reload_scripts (PolkitBackendJsAuthority *authority) { duk_context *cx = authority->priv->cx; @@ -282,42 +186,6 @@ reload_scripts (PolkitBackendJsAuthority *authority) g_signal_emit_by_name (authority, "changed"); } -static void -on_dir_monitor_changed (GFileMonitor *monitor, - GFile *file, - GFile *other_file, - GFileMonitorEvent event_type, - gpointer user_data) -{ - PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (user_data); - - /* TODO: maybe rate-limit so storms of events are collapsed into one with a 500ms resolution? - * Because when editing a file with emacs we get 4-8 events.. - */ - - if (file != NULL) - { - gchar *name; - - name = g_file_get_basename (file); - - /* g_print ("event_type=%d file=%p name=%s\n", event_type, file, name); */ - if (!g_str_has_prefix (name, ".") && - !g_str_has_prefix (name, "#") && - g_str_has_suffix (name, ".rules") && - (event_type == G_FILE_MONITOR_EVENT_CREATED || - event_type == G_FILE_MONITOR_EVENT_DELETED || - event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)) - { - polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), - "Reloading rules"); - reload_scripts (authority); - } - g_free (name); - } -} - - static void setup_file_monitors (PolkitBackendJsAuthority *authority) { @@ -349,7 +217,7 @@ setup_file_monitors (PolkitBackendJsAuthority *authority) { g_signal_connect (monitor, "changed", - G_CALLBACK (on_dir_monitor_changed), + G_CALLBACK (polkit_backend_common_on_dir_monitor_changed), authority); g_ptr_array_add (p, monitor); } @@ -358,8 +226,8 @@ setup_file_monitors (PolkitBackendJsAuthority *authority) authority->priv->dir_monitors = (GFileMonitor**) g_ptr_array_free (p, FALSE); } -static void -polkit_backend_js_authority_constructed (GObject *object) +void +polkit_backend_common_js_authority_constructed (GObject *object) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); duk_context *cx; @@ -395,8 +263,8 @@ polkit_backend_js_authority_constructed (GObject *object) g_assert_not_reached (); } -static void -polkit_backend_js_authority_finalize (GObject *object) +void +polkit_backend_common_js_authority_finalize (GObject *object) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); guint n; @@ -405,7 +273,7 @@ polkit_backend_js_authority_finalize (GObject *object) { GFileMonitor *monitor = authority->priv->dir_monitors[n]; g_signal_handlers_disconnect_by_func (monitor, - G_CALLBACK (on_dir_monitor_changed), + G_CALLBACK (polkit_backend_common_on_dir_monitor_changed), authority); g_object_unref (monitor); } @@ -417,11 +285,11 @@ polkit_backend_js_authority_finalize (GObject *object) G_OBJECT_CLASS (polkit_backend_js_authority_parent_class)->finalize (object); } -static void -polkit_backend_js_authority_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec) +void +polkit_backend_common_js_authority_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); @@ -438,55 +306,10 @@ polkit_backend_js_authority_set_property (GObject *object, } } -static const gchar * -polkit_backend_js_authority_get_name (PolkitBackendAuthority *authority) -{ - return "js"; -} - -static const gchar * -polkit_backend_js_authority_get_version (PolkitBackendAuthority *authority) -{ - return PACKAGE_VERSION; -} - -static PolkitAuthorityFeatures -polkit_backend_js_authority_get_features (PolkitBackendAuthority *authority) -{ - return POLKIT_AUTHORITY_FEATURES_TEMPORARY_AUTHORIZATION; -} - static void polkit_backend_js_authority_class_init (PolkitBackendJsAuthorityClass *klass) { - GObjectClass *gobject_class; - PolkitBackendAuthorityClass *authority_class; - PolkitBackendInteractiveAuthorityClass *interactive_authority_class; - - - gobject_class = G_OBJECT_CLASS (klass); - gobject_class->finalize = polkit_backend_js_authority_finalize; - gobject_class->set_property = polkit_backend_js_authority_set_property; - gobject_class->constructed = polkit_backend_js_authority_constructed; - - authority_class = POLKIT_BACKEND_AUTHORITY_CLASS (klass); - authority_class->get_name = polkit_backend_js_authority_get_name; - authority_class->get_version = polkit_backend_js_authority_get_version; - authority_class->get_features = polkit_backend_js_authority_get_features; - - interactive_authority_class = POLKIT_BACKEND_INTERACTIVE_AUTHORITY_CLASS (klass); - interactive_authority_class->get_admin_identities = polkit_backend_js_authority_get_admin_auth_identities; - interactive_authority_class->check_authorization_sync = polkit_backend_js_authority_check_authorization_sync; - - g_object_class_install_property (gobject_class, - PROP_RULES_DIRS, - g_param_spec_boxed ("rules-dirs", - NULL, - NULL, - G_TYPE_STRV, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); - - + polkit_backend_common_js_authority_class_init_common (klass); g_type_class_add_private (klass, sizeof (PolkitBackendJsAuthorityPrivate)); } @@ -689,15 +512,15 @@ push_action_and_details (duk_context *cx, /* ---------------------------------------------------------------------------------------------------- */ -static GList * -polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *_authority, - PolkitSubject *caller, - PolkitSubject *subject, - PolkitIdentity *user_for_subject, - gboolean subject_is_local, - gboolean subject_is_active, - const gchar *action_id, - PolkitDetails *details) +GList * +polkit_backend_common_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *_authority, + PolkitSubject *caller, + PolkitSubject *subject, + PolkitIdentity *user_for_subject, + gboolean subject_is_local, + gboolean subject_is_active, + const gchar *action_id, + PolkitDetails *details) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (_authority); GList *ret = NULL; @@ -777,16 +600,16 @@ polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveA /* ---------------------------------------------------------------------------------------------------- */ -static PolkitImplicitAuthorization -polkit_backend_js_authority_check_authorization_sync (PolkitBackendInteractiveAuthority *_authority, - PolkitSubject *caller, - PolkitSubject *subject, - PolkitIdentity *user_for_subject, - gboolean subject_is_local, - gboolean subject_is_active, - const gchar *action_id, - PolkitDetails *details, - PolkitImplicitAuthorization implicit) +PolkitImplicitAuthorization +polkit_backend_common_js_authority_check_authorization_sync (PolkitBackendInteractiveAuthority *_authority, + PolkitSubject *caller, + PolkitSubject *subject, + PolkitIdentity *user_for_subject, + gboolean subject_is_local, + gboolean subject_is_active, + const gchar *action_id, + PolkitDetails *details, + PolkitImplicitAuthorization implicit) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (_authority); PolkitImplicitAuthorization ret = implicit; @@ -864,65 +687,6 @@ js_polkit_log (duk_context *cx) /* ---------------------------------------------------------------------------------------------------- */ -static const gchar * -get_signal_name (gint signal_number) -{ - switch (signal_number) - { -#define _HANDLE_SIG(sig) case sig: return #sig; - _HANDLE_SIG (SIGHUP); - _HANDLE_SIG (SIGINT); - _HANDLE_SIG (SIGQUIT); - _HANDLE_SIG (SIGILL); - _HANDLE_SIG (SIGABRT); - _HANDLE_SIG (SIGFPE); - _HANDLE_SIG (SIGKILL); - _HANDLE_SIG (SIGSEGV); - _HANDLE_SIG (SIGPIPE); - _HANDLE_SIG (SIGALRM); - _HANDLE_SIG (SIGTERM); - _HANDLE_SIG (SIGUSR1); - _HANDLE_SIG (SIGUSR2); - _HANDLE_SIG (SIGCHLD); - _HANDLE_SIG (SIGCONT); - _HANDLE_SIG (SIGSTOP); - _HANDLE_SIG (SIGTSTP); - _HANDLE_SIG (SIGTTIN); - _HANDLE_SIG (SIGTTOU); - _HANDLE_SIG (SIGBUS); -#ifdef SIGPOLL - _HANDLE_SIG (SIGPOLL); -#endif - _HANDLE_SIG (SIGPROF); - _HANDLE_SIG (SIGSYS); - _HANDLE_SIG (SIGTRAP); - _HANDLE_SIG (SIGURG); - _HANDLE_SIG (SIGVTALRM); - _HANDLE_SIG (SIGXCPU); - _HANDLE_SIG (SIGXFSZ); -#undef _HANDLE_SIG - default: - break; - } - return "UNKNOWN_SIGNAL"; -} - -typedef struct -{ - GMainLoop *loop; - GAsyncResult *res; -} SpawnData; - -static void -spawn_cb (GObject *source_object, - GAsyncResult *res, - gpointer user_data) -{ - SpawnData *data = (SpawnData *)user_data; - data->res = (GAsyncResult*)g_object_ref (res); - g_main_loop_quit (data->loop); -} - static duk_ret_t js_polkit_spawn (duk_context *cx) { @@ -962,21 +726,21 @@ js_polkit_spawn (duk_context *cx) g_main_context_push_thread_default (context); data.loop = loop; - utils_spawn ((const gchar *const *) argv, - 10, /* timeout_seconds */ - NULL, /* cancellable */ - spawn_cb, - &data); + polkit_backend_common_spawn ((const gchar *const *) argv, + 10, /* timeout_seconds */ + NULL, /* cancellable */ + polkit_backend_common_spawn_cb, + &data); g_main_loop_run (loop); g_main_context_pop_thread_default (context); - if (!utils_spawn_finish (data.res, - &exit_status, - &standard_output, - &standard_error, - &error)) + if (!polkit_backend_common_spawn_finish (data.res, + &exit_status, + &standard_output, + &standard_error, + &error)) { err_str = g_strdup_printf ("Error spawning helper: %s (%s, %d)", error->message, g_quark_to_string (error->domain), error->code); @@ -998,7 +762,7 @@ js_polkit_spawn (duk_context *cx) { g_string_append_printf (gstr, "Helper was signaled with signal %s (%d)", - get_signal_name (WTERMSIG (exit_status)), + polkit_backend_common_get_signal_name (WTERMSIG (exit_status)), WTERMSIG (exit_status)); } g_string_append_printf (gstr, ", stdout=`%s', stderr=`%s'", @@ -1052,377 +816,3 @@ js_polkit_user_is_in_netgroup (duk_context *cx) } /* ---------------------------------------------------------------------------------------------------- */ - -typedef struct -{ - GSimpleAsyncResult *simple; /* borrowed reference */ - GMainContext *main_context; /* may be NULL */ - - GCancellable *cancellable; /* may be NULL */ - gulong cancellable_handler_id; - - GPid child_pid; - gint child_stdout_fd; - gint child_stderr_fd; - - GIOChannel *child_stdout_channel; - GIOChannel *child_stderr_channel; - - GSource *child_watch_source; - GSource *child_stdout_source; - GSource *child_stderr_source; - - guint timeout_seconds; - gboolean timed_out; - GSource *timeout_source; - - GString *child_stdout; - GString *child_stderr; - - gint exit_status; -} UtilsSpawnData; - -static void -utils_child_watch_from_release_cb (GPid pid, - gint status, - gpointer user_data) -{ -} - -static void -utils_spawn_data_free (UtilsSpawnData *data) -{ - if (data->timeout_source != NULL) - { - g_source_destroy (data->timeout_source); - data->timeout_source = NULL; - } - - /* Nuke the child, if necessary */ - if (data->child_watch_source != NULL) - { - g_source_destroy (data->child_watch_source); - data->child_watch_source = NULL; - } - - if (data->child_pid != 0) - { - GSource *source; - kill (data->child_pid, SIGTERM); - /* OK, we need to reap for the child ourselves - we don't want - * to use waitpid() because that might block the calling - * thread (the child might handle SIGTERM and use several - * seconds for cleanup/rollback). - * - * So we use GChildWatch instead. - * - * Avoid taking a references to ourselves. but note that we need - * to pass the GSource so we can nuke it once handled. - */ - source = g_child_watch_source_new (data->child_pid); - g_source_set_callback (source, - (GSourceFunc) utils_child_watch_from_release_cb, - source, - (GDestroyNotify) g_source_destroy); - g_source_attach (source, data->main_context); - g_source_unref (source); - data->child_pid = 0; - } - - if (data->child_stdout != NULL) - { - g_string_free (data->child_stdout, TRUE); - data->child_stdout = NULL; - } - - if (data->child_stderr != NULL) - { - g_string_free (data->child_stderr, TRUE); - data->child_stderr = NULL; - } - - if (data->child_stdout_channel != NULL) - { - g_io_channel_unref (data->child_stdout_channel); - data->child_stdout_channel = NULL; - } - if (data->child_stderr_channel != NULL) - { - g_io_channel_unref (data->child_stderr_channel); - data->child_stderr_channel = NULL; - } - - if (data->child_stdout_source != NULL) - { - g_source_destroy (data->child_stdout_source); - data->child_stdout_source = NULL; - } - if (data->child_stderr_source != NULL) - { - g_source_destroy (data->child_stderr_source); - data->child_stderr_source = NULL; - } - - if (data->child_stdout_fd != -1) - { - g_warn_if_fail (close (data->child_stdout_fd) == 0); - data->child_stdout_fd = -1; - } - if (data->child_stderr_fd != -1) - { - g_warn_if_fail (close (data->child_stderr_fd) == 0); - data->child_stderr_fd = -1; - } - - if (data->cancellable_handler_id > 0) - { - g_cancellable_disconnect (data->cancellable, data->cancellable_handler_id); - data->cancellable_handler_id = 0; - } - - if (data->main_context != NULL) - g_main_context_unref (data->main_context); - - if (data->cancellable != NULL) - g_object_unref (data->cancellable); - - g_slice_free (UtilsSpawnData, data); -} - -/* called in the thread where @cancellable was cancelled */ -static void -utils_on_cancelled (GCancellable *cancellable, - gpointer user_data) -{ - UtilsSpawnData *data = (UtilsSpawnData *)user_data; - GError *error; - - error = NULL; - g_warn_if_fail (g_cancellable_set_error_if_cancelled (cancellable, &error)); - g_simple_async_result_take_error (data->simple, error); - g_simple_async_result_complete_in_idle (data->simple); - g_object_unref (data->simple); -} - -static gboolean -utils_read_child_stderr (GIOChannel *channel, - GIOCondition condition, - gpointer user_data) -{ - UtilsSpawnData *data = (UtilsSpawnData *)user_data; - gchar buf[1024]; - gsize bytes_read; - - g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); - g_string_append_len (data->child_stderr, buf, bytes_read); - return TRUE; -} - -static gboolean -utils_read_child_stdout (GIOChannel *channel, - GIOCondition condition, - gpointer user_data) -{ - UtilsSpawnData *data = (UtilsSpawnData *)user_data; - gchar buf[1024]; - gsize bytes_read; - - g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); - g_string_append_len (data->child_stdout, buf, bytes_read); - return TRUE; -} - -static void -utils_child_watch_cb (GPid pid, - gint status, - gpointer user_data) -{ - UtilsSpawnData *data = (UtilsSpawnData *)user_data; - gchar *buf; - gsize buf_size; - - if (g_io_channel_read_to_end (data->child_stdout_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) - { - g_string_append_len (data->child_stdout, buf, buf_size); - g_free (buf); - } - if (g_io_channel_read_to_end (data->child_stderr_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) - { - g_string_append_len (data->child_stderr, buf, buf_size); - g_free (buf); - } - - data->exit_status = status; - - /* ok, child watch is history, make sure we don't free it in spawn_data_free() */ - data->child_pid = 0; - data->child_watch_source = NULL; - - /* we're done */ - g_simple_async_result_complete_in_idle (data->simple); - g_object_unref (data->simple); -} - -static gboolean -utils_timeout_cb (gpointer user_data) -{ - UtilsSpawnData *data = (UtilsSpawnData *)user_data; - - data->timed_out = TRUE; - - /* ok, timeout is history, make sure we don't free it in spawn_data_free() */ - data->timeout_source = NULL; - - /* we're done */ - g_simple_async_result_complete_in_idle (data->simple); - g_object_unref (data->simple); - - return FALSE; /* remove source */ -} - -static void -utils_spawn (const gchar *const *argv, - guint timeout_seconds, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - UtilsSpawnData *data; - GError *error; - - data = g_slice_new0 (UtilsSpawnData); - data->timeout_seconds = timeout_seconds; - data->simple = g_simple_async_result_new (NULL, - callback, - user_data, - (gpointer*)utils_spawn); - data->main_context = g_main_context_get_thread_default (); - if (data->main_context != NULL) - g_main_context_ref (data->main_context); - - data->cancellable = cancellable != NULL ? (GCancellable*)g_object_ref (cancellable) : NULL; - - data->child_stdout = g_string_new (NULL); - data->child_stderr = g_string_new (NULL); - data->child_stdout_fd = -1; - data->child_stderr_fd = -1; - - /* the life-cycle of UtilsSpawnData is tied to its GSimpleAsyncResult */ - g_simple_async_result_set_op_res_gpointer (data->simple, data, (GDestroyNotify) utils_spawn_data_free); - - error = NULL; - if (data->cancellable != NULL) - { - /* could already be cancelled */ - error = NULL; - if (g_cancellable_set_error_if_cancelled (data->cancellable, &error)) - { - g_simple_async_result_take_error (data->simple, error); - g_simple_async_result_complete_in_idle (data->simple); - g_object_unref (data->simple); - goto out; - } - - data->cancellable_handler_id = g_cancellable_connect (data->cancellable, - G_CALLBACK (utils_on_cancelled), - data, - NULL); - } - - error = NULL; - if (!g_spawn_async_with_pipes (NULL, /* working directory */ - (gchar **) argv, - NULL, /* envp */ - G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, - NULL, /* child_setup */ - NULL, /* child_setup's user_data */ - &(data->child_pid), - NULL, /* gint *stdin_fd */ - &(data->child_stdout_fd), - &(data->child_stderr_fd), - &error)) - { - g_prefix_error (&error, "Error spawning: "); - g_simple_async_result_take_error (data->simple, error); - g_simple_async_result_complete_in_idle (data->simple); - g_object_unref (data->simple); - goto out; - } - - if (timeout_seconds > 0) - { - data->timeout_source = g_timeout_source_new_seconds (timeout_seconds); - g_source_set_priority (data->timeout_source, G_PRIORITY_DEFAULT); - g_source_set_callback (data->timeout_source, utils_timeout_cb, data, NULL); - g_source_attach (data->timeout_source, data->main_context); - g_source_unref (data->timeout_source); - } - - data->child_watch_source = g_child_watch_source_new (data->child_pid); - g_source_set_callback (data->child_watch_source, (GSourceFunc) utils_child_watch_cb, data, NULL); - g_source_attach (data->child_watch_source, data->main_context); - g_source_unref (data->child_watch_source); - - data->child_stdout_channel = g_io_channel_unix_new (data->child_stdout_fd); - g_io_channel_set_flags (data->child_stdout_channel, G_IO_FLAG_NONBLOCK, NULL); - data->child_stdout_source = g_io_create_watch (data->child_stdout_channel, G_IO_IN); - g_source_set_callback (data->child_stdout_source, (GSourceFunc) utils_read_child_stdout, data, NULL); - g_source_attach (data->child_stdout_source, data->main_context); - g_source_unref (data->child_stdout_source); - - data->child_stderr_channel = g_io_channel_unix_new (data->child_stderr_fd); - g_io_channel_set_flags (data->child_stderr_channel, G_IO_FLAG_NONBLOCK, NULL); - data->child_stderr_source = g_io_create_watch (data->child_stderr_channel, G_IO_IN); - g_source_set_callback (data->child_stderr_source, (GSourceFunc) utils_read_child_stderr, data, NULL); - g_source_attach (data->child_stderr_source, data->main_context); - g_source_unref (data->child_stderr_source); - - out: - ; -} - -gboolean -utils_spawn_finish (GAsyncResult *res, - gint *out_exit_status, - gchar **out_standard_output, - gchar **out_standard_error, - GError **error) -{ - GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); - UtilsSpawnData *data; - gboolean ret = FALSE; - - g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - - g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == utils_spawn); - - if (g_simple_async_result_propagate_error (simple, error)) - goto out; - - data = (UtilsSpawnData*)g_simple_async_result_get_op_res_gpointer (simple); - - if (data->timed_out) - { - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_TIMED_OUT, - "Timed out after %d seconds", - data->timeout_seconds); - goto out; - } - - if (out_exit_status != NULL) - *out_exit_status = data->exit_status; - - if (out_standard_output != NULL) - *out_standard_output = g_strdup (data->child_stdout->str); - - if (out_standard_error != NULL) - *out_standard_error = g_strdup (data->child_stderr->str); - - ret = TRUE; - - out: - return ret; -} diff --git a/src/polkitbackend/polkitbackendjsauthority.cpp b/src/polkitbackend/polkitbackendjsauthority.cpp index ca17108..e28091d 100644 --- a/src/polkitbackend/polkitbackendjsauthority.cpp +++ b/src/polkitbackend/polkitbackendjsauthority.cpp @@ -19,29 +19,7 @@ * Author: David Zeuthen */ -#include "config.h" -#include -#include -#include -#include -#ifdef HAVE_NETGROUP_H -#include -#else -#include -#endif -#include -#include -#include -#include - -#include -#include "polkitbackendjsauthority.h" - -#include - -#ifdef HAVE_LIBSYSTEMD -#include -#endif /* HAVE_LIBSYSTEMD */ +#include "polkitbackendcommon.h" #include #include @@ -52,6 +30,7 @@ #include #include +/* Built source and not too big to worry about deduplication */ #include "initjs.h" /* init.js */ #ifdef JSGC_USE_EXACT_ROOTING @@ -67,10 +46,9 @@ * @short_description: JS Authority * @stability: Unstable * - * An implementation of #PolkitBackendAuthority that reads and - * evalates Javascript files and supports interaction with - * authentication agents (virtue of being based on - * #PolkitBackendInteractiveAuthority). + * An (SpiderMonkey-based) implementation of #PolkitBackendAuthority that reads + * and evaluates Javascript files and supports interaction with authentication + * agents (virtue of being based on #PolkitBackendInteractiveAuthority). */ /* ---------------------------------------------------------------------------------------------------- */ @@ -100,57 +78,11 @@ static bool execute_script_with_runaway_killer (PolkitBackendJsAuthority *author JS::HandleScript script, JS::MutableHandleValue rval); -static void utils_spawn (const gchar *const *argv, - guint timeout_seconds, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); - -gboolean utils_spawn_finish (GAsyncResult *res, - gint *out_exit_status, - gchar **out_standard_output, - gchar **out_standard_error, - GError **error); - -static void on_dir_monitor_changed (GFileMonitor *monitor, - GFile *file, - GFile *other_file, - GFileMonitorEvent event_type, - gpointer user_data); - -/* ---------------------------------------------------------------------------------------------------- */ - -enum -{ - PROP_0, - PROP_RULES_DIRS, -}; - /* ---------------------------------------------------------------------------------------------------- */ static gpointer runaway_killer_thread_func (gpointer user_data); static void runaway_killer_terminate (PolkitBackendJsAuthority *authority); -static GList *polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *authority, - PolkitSubject *caller, - PolkitSubject *subject, - PolkitIdentity *user_for_subject, - gboolean subject_is_local, - gboolean subject_is_active, - const gchar *action_id, - PolkitDetails *details); - -static PolkitImplicitAuthorization polkit_backend_js_authority_check_authorization_sync ( - PolkitBackendInteractiveAuthority *authority, - PolkitSubject *caller, - PolkitSubject *subject, - PolkitIdentity *user_for_subject, - gboolean subject_is_local, - gboolean subject_is_active, - const gchar *action_id, - PolkitDetails *details, - PolkitImplicitAuthorization implicit); - G_DEFINE_TYPE (PolkitBackendJsAuthority, polkit_backend_js_authority, POLKIT_BACKEND_TYPE_INTERACTIVE_AUTHORITY); /* ---------------------------------------------------------------------------------------------------- */ @@ -229,33 +161,6 @@ polkit_backend_js_authority_init (PolkitBackendJsAuthority *authority) PolkitBackendJsAuthorityPrivate); } -static gint -rules_file_name_cmp (const gchar *a, - const gchar *b) -{ - gint ret; - const gchar *a_base; - const gchar *b_base; - - a_base = strrchr (a, '/'); - b_base = strrchr (b, '/'); - - g_assert (a_base != NULL); - g_assert (b_base != NULL); - a_base += 1; - b_base += 1; - - ret = g_strcmp0 (a_base, b_base); - if (ret == 0) - { - /* /etc wins over /usr */ - ret = g_strcmp0 (a, b); - g_assert (ret != 0); - } - - return ret; -} - /* authority->priv->cx must be within a request */ static void load_scripts (PolkitBackendJsAuthority *authority) @@ -299,7 +204,7 @@ load_scripts (PolkitBackendJsAuthority *authority) } } - files = g_list_sort (files, (GCompareFunc) rules_file_name_cmp); + files = g_list_sort (files, (GCompareFunc) polkit_backend_common_rules_file_name_cmp); for (l = files; l != NULL; l = l->next) { @@ -365,8 +270,8 @@ load_scripts (PolkitBackendJsAuthority *authority) g_list_free_full (files, g_free); } -static void -reload_scripts (PolkitBackendJsAuthority *authority) +void +polkit_backend_common_reload_scripts (PolkitBackendJsAuthority *authority) { JS::RootedValueArray<1> args(authority->priv->cx); JS::RootedValue rval(authority->priv->cx); @@ -395,42 +300,6 @@ reload_scripts (PolkitBackendJsAuthority *authority) g_signal_emit_by_name (authority, "changed"); } -static void -on_dir_monitor_changed (GFileMonitor *monitor, - GFile *file, - GFile *other_file, - GFileMonitorEvent event_type, - gpointer user_data) -{ - PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (user_data); - - /* TODO: maybe rate-limit so storms of events are collapsed into one with a 500ms resolution? - * Because when editing a file with emacs we get 4-8 events.. - */ - - if (file != NULL) - { - gchar *name; - - name = g_file_get_basename (file); - - /* g_print ("event_type=%d file=%p name=%s\n", event_type, file, name); */ - if (!g_str_has_prefix (name, ".") && - !g_str_has_prefix (name, "#") && - g_str_has_suffix (name, ".rules") && - (event_type == G_FILE_MONITOR_EVENT_CREATED || - event_type == G_FILE_MONITOR_EVENT_DELETED || - event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)) - { - polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), - "Reloading rules"); - reload_scripts (authority); - } - g_free (name); - } -} - - static void setup_file_monitors (PolkitBackendJsAuthority *authority) { @@ -462,7 +331,7 @@ setup_file_monitors (PolkitBackendJsAuthority *authority) { g_signal_connect (monitor, "changed", - G_CALLBACK (on_dir_monitor_changed), + G_CALLBACK (polkit_backend_common_on_dir_monitor_changed), authority); g_ptr_array_add (p, monitor); } @@ -471,8 +340,8 @@ setup_file_monitors (PolkitBackendJsAuthority *authority) authority->priv->dir_monitors = (GFileMonitor**) g_ptr_array_free (p, FALSE); } -static void -polkit_backend_js_authority_constructed (GObject *object) +void +polkit_backend_common_js_authority_constructed (GObject *object) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); @@ -561,8 +430,8 @@ polkit_backend_js_authority_constructed (GObject *object) g_assert_not_reached (); } -static void -polkit_backend_js_authority_finalize (GObject *object) +void +polkit_backend_common_js_authority_finalize (GObject *object) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); guint n; @@ -577,7 +446,7 @@ polkit_backend_js_authority_finalize (GObject *object) { GFileMonitor *monitor = authority->priv->dir_monitors[n]; g_signal_handlers_disconnect_by_func (monitor, - (gpointer*)G_CALLBACK (on_dir_monitor_changed), + (gpointer*)G_CALLBACK (polkit_backend_common_on_dir_monitor_changed), authority); g_object_unref (monitor); } @@ -594,11 +463,11 @@ polkit_backend_js_authority_finalize (GObject *object) G_OBJECT_CLASS (polkit_backend_js_authority_parent_class)->finalize (object); } -static void -polkit_backend_js_authority_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec) +void +polkit_backend_common_js_authority_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); @@ -615,57 +484,12 @@ polkit_backend_js_authority_set_property (GObject *object, } } -static const gchar * -polkit_backend_js_authority_get_name (PolkitBackendAuthority *authority) -{ - return "js"; -} - -static const gchar * -polkit_backend_js_authority_get_version (PolkitBackendAuthority *authority) -{ - return PACKAGE_VERSION; -} - -static PolkitAuthorityFeatures -polkit_backend_js_authority_get_features (PolkitBackendAuthority *authority) -{ - return POLKIT_AUTHORITY_FEATURES_TEMPORARY_AUTHORIZATION; -} - static void polkit_backend_js_authority_class_init (PolkitBackendJsAuthorityClass *klass) { - GObjectClass *gobject_class; - PolkitBackendAuthorityClass *authority_class; - PolkitBackendInteractiveAuthorityClass *interactive_authority_class; - - - gobject_class = G_OBJECT_CLASS (klass); - gobject_class->finalize = polkit_backend_js_authority_finalize; - gobject_class->set_property = polkit_backend_js_authority_set_property; - gobject_class->constructed = polkit_backend_js_authority_constructed; - - authority_class = POLKIT_BACKEND_AUTHORITY_CLASS (klass); - authority_class->get_name = polkit_backend_js_authority_get_name; - authority_class->get_version = polkit_backend_js_authority_get_version; - authority_class->get_features = polkit_backend_js_authority_get_features; - - interactive_authority_class = POLKIT_BACKEND_INTERACTIVE_AUTHORITY_CLASS (klass); - interactive_authority_class->get_admin_identities = polkit_backend_js_authority_get_admin_auth_identities; - interactive_authority_class->check_authorization_sync = polkit_backend_js_authority_check_authorization_sync; - - g_object_class_install_property (gobject_class, - PROP_RULES_DIRS, - g_param_spec_boxed ("rules-dirs", - NULL, - NULL, - G_TYPE_STRV, - GParamFlags(G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE))); - + polkit_backend_common_js_authority_class_init_common (klass); g_type_class_add_private (klass, sizeof (PolkitBackendJsAuthorityPrivate)); - JS_Init (); } @@ -1099,15 +923,15 @@ call_js_function_with_runaway_killer (PolkitBackendJsAuthority *authority, /* ---------------------------------------------------------------------------------------------------- */ -static GList * -polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *_authority, - PolkitSubject *caller, - PolkitSubject *subject, - PolkitIdentity *user_for_subject, - gboolean subject_is_local, - gboolean subject_is_active, - const gchar *action_id, - PolkitDetails *details) +GList * +polkit_backend_common_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *_authority, + PolkitSubject *caller, + PolkitSubject *subject, + PolkitIdentity *user_for_subject, + gboolean subject_is_local, + gboolean subject_is_active, + const gchar *action_id, + PolkitDetails *details) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (_authority); GList *ret = NULL; @@ -1202,16 +1026,16 @@ polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveA /* ---------------------------------------------------------------------------------------------------- */ -static PolkitImplicitAuthorization -polkit_backend_js_authority_check_authorization_sync (PolkitBackendInteractiveAuthority *_authority, - PolkitSubject *caller, - PolkitSubject *subject, - PolkitIdentity *user_for_subject, - gboolean subject_is_local, - gboolean subject_is_active, - const gchar *action_id, - PolkitDetails *details, - PolkitImplicitAuthorization implicit) +PolkitImplicitAuthorization +polkit_backend_common_js_authority_check_authorization_sync (PolkitBackendInteractiveAuthority *_authority, + PolkitSubject *caller, + PolkitSubject *subject, + PolkitIdentity *user_for_subject, + gboolean subject_is_local, + gboolean subject_is_active, + const gchar *action_id, + PolkitDetails *details, + PolkitImplicitAuthorization implicit) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (_authority); PolkitImplicitAuthorization ret = implicit; @@ -1324,65 +1148,6 @@ js_polkit_log (JSContext *cx, /* ---------------------------------------------------------------------------------------------------- */ -static const gchar * -get_signal_name (gint signal_number) -{ - switch (signal_number) - { -#define _HANDLE_SIG(sig) case sig: return #sig; - _HANDLE_SIG (SIGHUP); - _HANDLE_SIG (SIGINT); - _HANDLE_SIG (SIGQUIT); - _HANDLE_SIG (SIGILL); - _HANDLE_SIG (SIGABRT); - _HANDLE_SIG (SIGFPE); - _HANDLE_SIG (SIGKILL); - _HANDLE_SIG (SIGSEGV); - _HANDLE_SIG (SIGPIPE); - _HANDLE_SIG (SIGALRM); - _HANDLE_SIG (SIGTERM); - _HANDLE_SIG (SIGUSR1); - _HANDLE_SIG (SIGUSR2); - _HANDLE_SIG (SIGCHLD); - _HANDLE_SIG (SIGCONT); - _HANDLE_SIG (SIGSTOP); - _HANDLE_SIG (SIGTSTP); - _HANDLE_SIG (SIGTTIN); - _HANDLE_SIG (SIGTTOU); - _HANDLE_SIG (SIGBUS); -#ifdef SIGPOLL - _HANDLE_SIG (SIGPOLL); -#endif - _HANDLE_SIG (SIGPROF); - _HANDLE_SIG (SIGSYS); - _HANDLE_SIG (SIGTRAP); - _HANDLE_SIG (SIGURG); - _HANDLE_SIG (SIGVTALRM); - _HANDLE_SIG (SIGXCPU); - _HANDLE_SIG (SIGXFSZ); -#undef _HANDLE_SIG - default: - break; - } - return "UNKNOWN_SIGNAL"; -} - -typedef struct -{ - GMainLoop *loop; - GAsyncResult *res; -} SpawnData; - -static void -spawn_cb (GObject *source_object, - GAsyncResult *res, - gpointer user_data) -{ - SpawnData *data = (SpawnData *)user_data; - data->res = (GAsyncResult*)g_object_ref (res); - g_main_loop_quit (data->loop); -} - static bool js_polkit_spawn (JSContext *cx, unsigned js_argc, @@ -1440,21 +1205,21 @@ js_polkit_spawn (JSContext *cx, g_main_context_push_thread_default (context); data.loop = loop; - utils_spawn ((const gchar *const *) argv, - 10, /* timeout_seconds */ - NULL, /* cancellable */ - spawn_cb, - &data); + polkit_backend_common_spawn ((const gchar *const *) argv, + 10, /* timeout_seconds */ + NULL, /* cancellable */ + polkit_backend_common_spawn_cb, + &data); g_main_loop_run (loop); g_main_context_pop_thread_default (context); - if (!utils_spawn_finish (data.res, - &exit_status, - &standard_output, - &standard_error, - &error)) + if (!polkit_backend_common_spawn_finish (data.res, + &exit_status, + &standard_output, + &standard_error, + &error)) { JS_ReportErrorUTF8 (cx, "Error spawning helper: %s (%s, %d)", @@ -1477,7 +1242,7 @@ js_polkit_spawn (JSContext *cx, { g_string_append_printf (gstr, "Helper was signaled with signal %s (%d)", - get_signal_name (WTERMSIG (exit_status)), + polkit_backend_common_get_signal_name (WTERMSIG (exit_status)), WTERMSIG (exit_status)); } g_string_append_printf (gstr, ", stdout=`%s', stderr=`%s'", @@ -1542,381 +1307,5 @@ js_polkit_user_is_in_netgroup (JSContext *cx, return ret; } - - /* ---------------------------------------------------------------------------------------------------- */ -typedef struct -{ - GSimpleAsyncResult *simple; /* borrowed reference */ - GMainContext *main_context; /* may be NULL */ - - GCancellable *cancellable; /* may be NULL */ - gulong cancellable_handler_id; - - GPid child_pid; - gint child_stdout_fd; - gint child_stderr_fd; - - GIOChannel *child_stdout_channel; - GIOChannel *child_stderr_channel; - - GSource *child_watch_source; - GSource *child_stdout_source; - GSource *child_stderr_source; - - guint timeout_seconds; - gboolean timed_out; - GSource *timeout_source; - - GString *child_stdout; - GString *child_stderr; - - gint exit_status; -} UtilsSpawnData; - -static void -utils_child_watch_from_release_cb (GPid pid, - gint status, - gpointer user_data) -{ -} - -static void -utils_spawn_data_free (UtilsSpawnData *data) -{ - if (data->timeout_source != NULL) - { - g_source_destroy (data->timeout_source); - data->timeout_source = NULL; - } - - /* Nuke the child, if necessary */ - if (data->child_watch_source != NULL) - { - g_source_destroy (data->child_watch_source); - data->child_watch_source = NULL; - } - - if (data->child_pid != 0) - { - GSource *source; - kill (data->child_pid, SIGTERM); - /* OK, we need to reap for the child ourselves - we don't want - * to use waitpid() because that might block the calling - * thread (the child might handle SIGTERM and use several - * seconds for cleanup/rollback). - * - * So we use GChildWatch instead. - * - * Avoid taking a references to ourselves. but note that we need - * to pass the GSource so we can nuke it once handled. - */ - source = g_child_watch_source_new (data->child_pid); - g_source_set_callback (source, - (GSourceFunc) utils_child_watch_from_release_cb, - source, - (GDestroyNotify) g_source_destroy); - /* attach source to the global default main context */ - g_source_attach (source, NULL); - g_source_unref (source); - data->child_pid = 0; - } - - if (data->child_stdout != NULL) - { - g_string_free (data->child_stdout, TRUE); - data->child_stdout = NULL; - } - - if (data->child_stderr != NULL) - { - g_string_free (data->child_stderr, TRUE); - data->child_stderr = NULL; - } - - if (data->child_stdout_channel != NULL) - { - g_io_channel_unref (data->child_stdout_channel); - data->child_stdout_channel = NULL; - } - if (data->child_stderr_channel != NULL) - { - g_io_channel_unref (data->child_stderr_channel); - data->child_stderr_channel = NULL; - } - - if (data->child_stdout_source != NULL) - { - g_source_destroy (data->child_stdout_source); - data->child_stdout_source = NULL; - } - if (data->child_stderr_source != NULL) - { - g_source_destroy (data->child_stderr_source); - data->child_stderr_source = NULL; - } - - if (data->child_stdout_fd != -1) - { - g_warn_if_fail (close (data->child_stdout_fd) == 0); - data->child_stdout_fd = -1; - } - if (data->child_stderr_fd != -1) - { - g_warn_if_fail (close (data->child_stderr_fd) == 0); - data->child_stderr_fd = -1; - } - - if (data->cancellable_handler_id > 0) - { - g_cancellable_disconnect (data->cancellable, data->cancellable_handler_id); - data->cancellable_handler_id = 0; - } - - if (data->main_context != NULL) - g_main_context_unref (data->main_context); - - if (data->cancellable != NULL) - g_object_unref (data->cancellable); - - g_slice_free (UtilsSpawnData, data); -} - -/* called in the thread where @cancellable was cancelled */ -static void -utils_on_cancelled (GCancellable *cancellable, - gpointer user_data) -{ - UtilsSpawnData *data = (UtilsSpawnData *)user_data; - GError *error; - - error = NULL; - g_warn_if_fail (g_cancellable_set_error_if_cancelled (cancellable, &error)); - g_simple_async_result_take_error (data->simple, error); - g_simple_async_result_complete_in_idle (data->simple); - g_object_unref (data->simple); -} - -static gboolean -utils_read_child_stderr (GIOChannel *channel, - GIOCondition condition, - gpointer user_data) -{ - UtilsSpawnData *data = (UtilsSpawnData *)user_data; - gchar buf[1024]; - gsize bytes_read; - - g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); - g_string_append_len (data->child_stderr, buf, bytes_read); - return TRUE; -} - -static gboolean -utils_read_child_stdout (GIOChannel *channel, - GIOCondition condition, - gpointer user_data) -{ - UtilsSpawnData *data = (UtilsSpawnData *)user_data; - gchar buf[1024]; - gsize bytes_read; - - g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); - g_string_append_len (data->child_stdout, buf, bytes_read); - return TRUE; -} - -static void -utils_child_watch_cb (GPid pid, - gint status, - gpointer user_data) -{ - UtilsSpawnData *data = (UtilsSpawnData *)user_data; - gchar *buf; - gsize buf_size; - - if (g_io_channel_read_to_end (data->child_stdout_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) - { - g_string_append_len (data->child_stdout, buf, buf_size); - g_free (buf); - } - if (g_io_channel_read_to_end (data->child_stderr_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) - { - g_string_append_len (data->child_stderr, buf, buf_size); - g_free (buf); - } - - data->exit_status = status; - - /* ok, child watch is history, make sure we don't free it in spawn_data_free() */ - data->child_pid = 0; - data->child_watch_source = NULL; - - /* we're done */ - g_simple_async_result_complete_in_idle (data->simple); - g_object_unref (data->simple); -} - -static gboolean -utils_timeout_cb (gpointer user_data) -{ - UtilsSpawnData *data = (UtilsSpawnData *)user_data; - - data->timed_out = TRUE; - - /* ok, timeout is history, make sure we don't free it in spawn_data_free() */ - data->timeout_source = NULL; - - /* we're done */ - g_simple_async_result_complete_in_idle (data->simple); - g_object_unref (data->simple); - - return FALSE; /* remove source */ -} - -static void -utils_spawn (const gchar *const *argv, - guint timeout_seconds, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - UtilsSpawnData *data; - GError *error; - - data = g_slice_new0 (UtilsSpawnData); - data->timeout_seconds = timeout_seconds; - data->simple = g_simple_async_result_new (NULL, - callback, - user_data, - (gpointer*)utils_spawn); - data->main_context = g_main_context_get_thread_default (); - if (data->main_context != NULL) - g_main_context_ref (data->main_context); - - data->cancellable = cancellable != NULL ? (GCancellable*)g_object_ref (cancellable) : NULL; - - data->child_stdout = g_string_new (NULL); - data->child_stderr = g_string_new (NULL); - data->child_stdout_fd = -1; - data->child_stderr_fd = -1; - - /* the life-cycle of UtilsSpawnData is tied to its GSimpleAsyncResult */ - g_simple_async_result_set_op_res_gpointer (data->simple, data, (GDestroyNotify) utils_spawn_data_free); - - error = NULL; - if (data->cancellable != NULL) - { - /* could already be cancelled */ - error = NULL; - if (g_cancellable_set_error_if_cancelled (data->cancellable, &error)) - { - g_simple_async_result_take_error (data->simple, error); - g_simple_async_result_complete_in_idle (data->simple); - g_object_unref (data->simple); - goto out; - } - - data->cancellable_handler_id = g_cancellable_connect (data->cancellable, - G_CALLBACK (utils_on_cancelled), - data, - NULL); - } - - error = NULL; - if (!g_spawn_async_with_pipes (NULL, /* working directory */ - (gchar **) argv, - NULL, /* envp */ - GSpawnFlags(G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD), - NULL, /* child_setup */ - NULL, /* child_setup's user_data */ - &(data->child_pid), - NULL, /* gint *stdin_fd */ - &(data->child_stdout_fd), - &(data->child_stderr_fd), - &error)) - { - g_prefix_error (&error, "Error spawning: "); - g_simple_async_result_take_error (data->simple, error); - g_simple_async_result_complete_in_idle (data->simple); - g_object_unref (data->simple); - goto out; - } - - if (timeout_seconds > 0) - { - data->timeout_source = g_timeout_source_new_seconds (timeout_seconds); - g_source_set_priority (data->timeout_source, G_PRIORITY_DEFAULT); - g_source_set_callback (data->timeout_source, utils_timeout_cb, data, NULL); - g_source_attach (data->timeout_source, data->main_context); - g_source_unref (data->timeout_source); - } - - data->child_watch_source = g_child_watch_source_new (data->child_pid); - g_source_set_callback (data->child_watch_source, (GSourceFunc) utils_child_watch_cb, data, NULL); - g_source_attach (data->child_watch_source, data->main_context); - g_source_unref (data->child_watch_source); - - data->child_stdout_channel = g_io_channel_unix_new (data->child_stdout_fd); - g_io_channel_set_flags (data->child_stdout_channel, G_IO_FLAG_NONBLOCK, NULL); - data->child_stdout_source = g_io_create_watch (data->child_stdout_channel, G_IO_IN); - g_source_set_callback (data->child_stdout_source, (GSourceFunc) utils_read_child_stdout, data, NULL); - g_source_attach (data->child_stdout_source, data->main_context); - g_source_unref (data->child_stdout_source); - - data->child_stderr_channel = g_io_channel_unix_new (data->child_stderr_fd); - g_io_channel_set_flags (data->child_stderr_channel, G_IO_FLAG_NONBLOCK, NULL); - data->child_stderr_source = g_io_create_watch (data->child_stderr_channel, G_IO_IN); - g_source_set_callback (data->child_stderr_source, (GSourceFunc) utils_read_child_stderr, data, NULL); - g_source_attach (data->child_stderr_source, data->main_context); - g_source_unref (data->child_stderr_source); - - out: - ; -} - -gboolean -utils_spawn_finish (GAsyncResult *res, - gint *out_exit_status, - gchar **out_standard_output, - gchar **out_standard_error, - GError **error) -{ - GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); - UtilsSpawnData *data; - gboolean ret = FALSE; - - g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - - g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == utils_spawn); - - if (g_simple_async_result_propagate_error (simple, error)) - goto out; - - data = (UtilsSpawnData*)g_simple_async_result_get_op_res_gpointer (simple); - - if (data->timed_out) - { - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_TIMED_OUT, - "Timed out after %d seconds", - data->timeout_seconds); - goto out; - } - - if (out_exit_status != NULL) - *out_exit_status = data->exit_status; - - if (out_standard_output != NULL) - *out_standard_output = g_strdup (data->child_stdout->str); - - if (out_standard_error != NULL) - *out_standard_error = g_strdup (data->child_stderr->str); - - ret = TRUE; - - out: - return ret; -} -- GitLab From 4858128107be9c3ab11828ee8f35c5e26efd36ce Mon Sep 17 00:00:00 2001 From: Gustavo Lima Chaves Date: Tue, 14 Sep 2021 14:38:15 -0700 Subject: [PATCH 15/17] Gitlab CI: add duktape pkgconfig dependency Make way for the CI to be able to build with duktape too Signed-off-by: Gustavo Lima Chaves --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 23cf0d6..942415d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,6 +14,7 @@ variables: libxslt pkgconfig(gio-2.0) pkgconfig(mozjs-78) + pkgconfig(duktape) expat-devel pkgconfig(libsystemd) pkgconfig(systemd) -- GitLab From 4262c8a5c952e7f5d15d233c1cd2955086b622a9 Mon Sep 17 00:00:00 2001 From: Gustavo Lima Chaves Date: Mon, 20 Sep 2021 17:17:26 -0700 Subject: [PATCH 16/17] duktape: implement runaway scripts killer timeout This was missing on Duktape's JS backend proposal, now in. As discussed in https://gitlab.freedesktop.org/polkit/polkit/-/merge_requests/35 and verified by the commit author, Duktape has no interrupt injection mechanism (it has no thread-safe API entry whatsoever, even). Using DUK_USE_EXEC_TIMEOUT_CHECK is also not feasible, because: i) It must be enabled at build time and shared object builds of the lib on distros go with the default options, something we cannot change/control ii) That does not account for non-ECMAScript explicit execution contexts, like regex execution, native C calls, etc. It has been agreed, on that thread, that pthread_cond_timedwait()-ing and having proper Duktape evaluation/execution calls take place in a separate thread, to be killed after the runaway script killer's accorded timeout value, a reasonable approach. We have considered using glib wrappers for direct pthread usage, but that way would make it impossible to issue pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, ...) natively, and we want to be paranoid in that regard. Worse than that, they have no exposure of pthread_cancel() either. We go for pthreads direct usage, then. Fancy threading build system checks courtesy of Evgeny Grin. On Duktape, we don't get to err from the JS context (to be captured by the offending script), but to be forcibly killed on timeout scenarios, leading to null returns, thus polkit negation, by definition. It's a reasonable design/compromise. A fatal error handler routine, for the Duktape context, has also been added, using the polkit_backend_authority_log() logging infra to better assist users on what went wrong. Finally, the script evaluation routine has been made to use duk_peval_lstring() (previously using _noresult variant), so to able to present the user with proper error messages, should any occur. NB: The original runaway script killer test has been adjusted to please both JS backends and we now also require Duktape version >= 2.0.0 on meson builds as well (removing the code checks for previous versions). Signed-off-by: Gustavo Lima Chaves Co-authored-by: Evgeny Grin --- buildutil/ax_pthread.m4 | 522 ++++++++++++++++++ configure.ac | 8 +- meson.build | 7 +- src/polkitbackend/Makefile.am | 2 + src/polkitbackend/meson.build | 3 + src/polkitbackend/polkitbackendcommon.h | 2 + .../polkitbackendduktapeauthority.c | 329 +++++++++-- .../polkitbackendjsauthority.cpp | 10 +- .../etc/polkit-1/rules.d/10-testing.rules | 6 +- .../test-polkitbackendjsauthority.c | 2 +- 10 files changed, 836 insertions(+), 55 deletions(-) create mode 100644 buildutil/ax_pthread.m4 diff --git a/buildutil/ax_pthread.m4 b/buildutil/ax_pthread.m4 new file mode 100644 index 0000000..9f35d13 --- /dev/null +++ b/buildutil/ax_pthread.m4 @@ -0,0 +1,522 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_pthread.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) +# +# DESCRIPTION +# +# This macro figures out how to build C programs using POSIX threads. It +# sets the PTHREAD_LIBS output variable to the threads library and linker +# flags, and the PTHREAD_CFLAGS output variable to any special C compiler +# flags that are needed. (The user can also force certain compiler +# flags/libs to be tested by setting these environment variables.) +# +# Also sets PTHREAD_CC and PTHREAD_CXX to any special C compiler that is +# needed for multi-threaded programs (defaults to the value of CC +# respectively CXX otherwise). (This is necessary on e.g. AIX to use the +# special cc_r/CC_r compiler alias.) +# +# NOTE: You are assumed to not only compile your program with these flags, +# but also to link with them as well. For example, you might link with +# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS +# $PTHREAD_CXX $CXXFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS +# +# If you are only building threaded programs, you may wish to use these +# variables in your default LIBS, CFLAGS, and CC: +# +# LIBS="$PTHREAD_LIBS $LIBS" +# CFLAGS="$CFLAGS $PTHREAD_CFLAGS" +# CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS" +# CC="$PTHREAD_CC" +# CXX="$PTHREAD_CXX" +# +# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant +# has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to +# that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX). +# +# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the +# PTHREAD_PRIO_INHERIT symbol is defined when compiling with +# PTHREAD_CFLAGS. +# +# ACTION-IF-FOUND is a list of shell commands to run if a threads library +# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it +# is not found. If ACTION-IF-FOUND is not specified, the default action +# will define HAVE_PTHREAD. +# +# Please let the authors know if this macro fails on any platform, or if +# you have any other suggestions or comments. This macro was based on work +# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help +# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by +# Alejandro Forero Cuervo to the autoconf macro repository. We are also +# grateful for the helpful feedback of numerous users. +# +# Updated for Autoconf 2.68 by Daniel Richard G. +# +# LICENSE +# +# Copyright (c) 2008 Steven G. Johnson +# Copyright (c) 2011 Daniel Richard G. +# Copyright (c) 2019 Marc Stevens +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 31 + +AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) +AC_DEFUN([AX_PTHREAD], [ +AC_REQUIRE([AC_CANONICAL_HOST]) +AC_REQUIRE([AC_PROG_CC]) +AC_REQUIRE([AC_PROG_SED]) +AC_LANG_PUSH([C]) +ax_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on Tru64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then + ax_pthread_save_CC="$CC" + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"]) + AS_IF([test "x$PTHREAD_CXX" != "x"], [CXX="$PTHREAD_CXX"]) + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS]) + AC_LINK_IFELSE([AC_LANG_CALL([], [pthread_join])], [ax_pthread_ok=yes]) + AC_MSG_RESULT([$ax_pthread_ok]) + if test "x$ax_pthread_ok" = "xno"; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + CC="$ax_pthread_save_CC" + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items with a "," contain both +# C compiler flags (before ",") and linker flags (after ","). Other items +# starting with a "-" are C compiler flags, and remaining items are +# library names, except for "none" which indicates that we try without +# any flags at all, and "pthread-config" which is a program returning +# the flags for the Pth emulation library. + +ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64 +# (Note: HP C rejects this with "bad form for `-t' option") +# -pthreads: Solaris/gcc (Note: HP C also rejects) +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads and +# -D_REENTRANT too), HP C (must be checked before -lpthread, which +# is present but should not be used directly; and before -mthreads, +# because the compiler interprets this as "-mt" + "-hreads") +# -mthreads: Mingw32/gcc, Lynx/gcc +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case $host_os in + + freebsd*) + + # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) + # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) + + ax_pthread_flags="-kthread lthread $ax_pthread_flags" + ;; + + hpux*) + + # From the cc(1) man page: "[-mt] Sets various -D flags to enable + # multi-threading and also sets -lpthread." + + ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags" + ;; + + openedition*) + + # IBM z/OS requires a feature-test macro to be defined in order to + # enable POSIX threads at all, so give the user a hint if this is + # not set. (We don't define these ourselves, as they can affect + # other portions of the system API in unpredictable ways.) + + AC_EGREP_CPP([AX_PTHREAD_ZOS_MISSING], + [ +# if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS) + AX_PTHREAD_ZOS_MISSING +# endif + ], + [AC_MSG_WARN([IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support.])]) + ;; + + solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (N.B.: The stubs are missing + # pthread_cleanup_push, or rather a function called by this macro, + # so we could check for that, but who knows whether they'll stub + # that too in a future libc.) So we'll check first for the + # standard Solaris way of linking pthreads (-mt -lpthread). + + ax_pthread_flags="-mt,-lpthread pthread $ax_pthread_flags" + ;; +esac + +# Are we compiling with Clang? + +AC_CACHE_CHECK([whether $CC is Clang], + [ax_cv_PTHREAD_CLANG], + [ax_cv_PTHREAD_CLANG=no + # Note that Autoconf sets GCC=yes for Clang as well as GCC + if test "x$GCC" = "xyes"; then + AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG], + [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */ +# if defined(__clang__) && defined(__llvm__) + AX_PTHREAD_CC_IS_CLANG +# endif + ], + [ax_cv_PTHREAD_CLANG=yes]) + fi + ]) +ax_pthread_clang="$ax_cv_PTHREAD_CLANG" + + +# GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC) + +# Note that for GCC and Clang -pthread generally implies -lpthread, +# except when -nostdlib is passed. +# This is problematic using libtool to build C++ shared libraries with pthread: +# [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25460 +# [2] https://bugzilla.redhat.com/show_bug.cgi?id=661333 +# [3] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=468555 +# To solve this, first try -pthread together with -lpthread for GCC + +AS_IF([test "x$GCC" = "xyes"], + [ax_pthread_flags="-pthread,-lpthread -pthread -pthreads $ax_pthread_flags"]) + +# Clang takes -pthread (never supported any other flag), but we'll try with -lpthread first + +AS_IF([test "x$ax_pthread_clang" = "xyes"], + [ax_pthread_flags="-pthread,-lpthread -pthread"]) + + +# The presence of a feature test macro requesting re-entrant function +# definitions is, on some systems, a strong hint that pthreads support is +# correctly enabled + +case $host_os in + darwin* | hpux* | linux* | osf* | solaris*) + ax_pthread_check_macro="_REENTRANT" + ;; + + aix*) + ax_pthread_check_macro="_THREAD_SAFE" + ;; + + *) + ax_pthread_check_macro="--" + ;; +esac +AS_IF([test "x$ax_pthread_check_macro" = "x--"], + [ax_pthread_check_cond=0], + [ax_pthread_check_cond="!defined($ax_pthread_check_macro)"]) + + +if test "x$ax_pthread_ok" = "xno"; then +for ax_pthread_try_flag in $ax_pthread_flags; do + + case $ax_pthread_try_flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + *,*) + PTHREAD_CFLAGS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\1/"` + PTHREAD_LIBS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\2/"` + AC_MSG_CHECKING([whether pthreads work with "$PTHREAD_CFLAGS" and "$PTHREAD_LIBS"]) + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag]) + PTHREAD_CFLAGS="$ax_pthread_try_flag" + ;; + + pthread-config) + AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) + AS_IF([test "x$ax_pthread_config" = "xno"], [continue]) + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag]) + PTHREAD_LIBS="-l$ax_pthread_try_flag" + ;; + esac + + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include +# if $ax_pthread_check_cond +# error "$ax_pthread_check_macro must be defined" +# endif + static void *some_global = NULL; + static void routine(void *a) + { + /* To avoid any unused-parameter or + unused-but-set-parameter warning. */ + some_global = a; + } + static void *start_routine(void *a) { return a; }], + [pthread_t th; pthread_attr_t attr; + pthread_create(&th, 0, start_routine, 0); + pthread_join(th, 0); + pthread_attr_init(&attr); + pthread_cleanup_push(routine, 0); + pthread_cleanup_pop(0) /* ; */])], + [ax_pthread_ok=yes], + []) + + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" + + AC_MSG_RESULT([$ax_pthread_ok]) + AS_IF([test "x$ax_pthread_ok" = "xyes"], [break]) + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + + +# Clang needs special handling, because older versions handle the -pthread +# option in a rather... idiosyncratic way + +if test "x$ax_pthread_clang" = "xyes"; then + + # Clang takes -pthread; it has never supported any other flag + + # (Note 1: This will need to be revisited if a system that Clang + # supports has POSIX threads in a separate library. This tends not + # to be the way of modern systems, but it's conceivable.) + + # (Note 2: On some systems, notably Darwin, -pthread is not needed + # to get POSIX threads support; the API is always present and + # active. We could reasonably leave PTHREAD_CFLAGS empty. But + # -pthread does define _REENTRANT, and while the Darwin headers + # ignore this macro, third-party headers might not.) + + # However, older versions of Clang make a point of warning the user + # that, in an invocation where only linking and no compilation is + # taking place, the -pthread option has no effect ("argument unused + # during compilation"). They expect -pthread to be passed in only + # when source code is being compiled. + # + # Problem is, this is at odds with the way Automake and most other + # C build frameworks function, which is that the same flags used in + # compilation (CFLAGS) are also used in linking. Many systems + # supported by AX_PTHREAD require exactly this for POSIX threads + # support, and in fact it is often not straightforward to specify a + # flag that is used only in the compilation phase and not in + # linking. Such a scenario is extremely rare in practice. + # + # Even though use of the -pthread flag in linking would only print + # a warning, this can be a nuisance for well-run software projects + # that build with -Werror. So if the active version of Clang has + # this misfeature, we search for an option to squash it. + + AC_CACHE_CHECK([whether Clang needs flag to prevent "argument unused" warning when linking with -pthread], + [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG], + [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown + # Create an alternate version of $ac_link that compiles and + # links in two steps (.c -> .o, .o -> exe) instead of one + # (.c -> exe), because the warning occurs only in the second + # step + ax_pthread_save_ac_link="$ac_link" + ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g' + ax_pthread_link_step=`AS_ECHO(["$ac_link"]) | sed "$ax_pthread_sed"` + ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)" + ax_pthread_save_CFLAGS="$CFLAGS" + for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do + AS_IF([test "x$ax_pthread_try" = "xunknown"], [break]) + CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS" + ac_link="$ax_pthread_save_ac_link" + AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], + [ac_link="$ax_pthread_2step_ac_link" + AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], + [break]) + ]) + done + ac_link="$ax_pthread_save_ac_link" + CFLAGS="$ax_pthread_save_CFLAGS" + AS_IF([test "x$ax_pthread_try" = "x"], [ax_pthread_try=no]) + ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try" + ]) + + case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in + no | unknown) ;; + *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;; + esac + +fi # $ax_pthread_clang = yes + + + +# Various other checks: +if test "x$ax_pthread_ok" = "xyes"; then + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + + # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. + AC_CACHE_CHECK([for joinable pthread attribute], + [ax_cv_PTHREAD_JOINABLE_ATTR], + [ax_cv_PTHREAD_JOINABLE_ATTR=unknown + for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], + [int attr = $ax_pthread_attr; return attr /* ; */])], + [ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break], + []) + done + ]) + AS_IF([test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \ + test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \ + test "x$ax_pthread_joinable_attr_defined" != "xyes"], + [AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], + [$ax_cv_PTHREAD_JOINABLE_ATTR], + [Define to necessary symbol if this constant + uses a non-standard name on your system.]) + ax_pthread_joinable_attr_defined=yes + ]) + + AC_CACHE_CHECK([whether more special flags are required for pthreads], + [ax_cv_PTHREAD_SPECIAL_FLAGS], + [ax_cv_PTHREAD_SPECIAL_FLAGS=no + case $host_os in + solaris*) + ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS" + ;; + esac + ]) + AS_IF([test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \ + test "x$ax_pthread_special_flags_added" != "xyes"], + [PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS" + ax_pthread_special_flags_added=yes]) + + AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], + [ax_cv_PTHREAD_PRIO_INHERIT], + [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[int i = PTHREAD_PRIO_INHERIT; + return i;]])], + [ax_cv_PTHREAD_PRIO_INHERIT=yes], + [ax_cv_PTHREAD_PRIO_INHERIT=no]) + ]) + AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \ + test "x$ax_pthread_prio_inherit_defined" != "xyes"], + [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.]) + ax_pthread_prio_inherit_defined=yes + ]) + + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" + + # More AIX lossage: compile with *_r variant + if test "x$GCC" != "xyes"; then + case $host_os in + aix*) + AS_CASE(["x/$CC"], + [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], + [#handle absolute path differently from PATH based program lookup + AS_CASE(["x$CC"], + [x/*], + [ + AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"]) + AS_IF([test "x${CXX}" != "x"], [AS_IF([AS_EXECUTABLE_P([${CXX}_r])],[PTHREAD_CXX="${CXX}_r"])]) + ], + [ + AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC]) + AS_IF([test "x${CXX}" != "x"], [AC_CHECK_PROGS([PTHREAD_CXX],[${CXX}_r],[$CXX])]) + ] + ) + ]) + ;; + esac + fi +fi + +test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" +test -n "$PTHREAD_CXX" || PTHREAD_CXX="$CXX" + +AC_SUBST([PTHREAD_LIBS]) +AC_SUBST([PTHREAD_CFLAGS]) +AC_SUBST([PTHREAD_CC]) +AC_SUBST([PTHREAD_CXX]) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test "x$ax_pthread_ok" = "xyes"; then + ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) + : +else + ax_pthread_ok=no + $2 +fi +AC_LANG_POP +])dnl AX_PTHREAD diff --git a/configure.ac b/configure.ac index 5a03593..e182238 100644 --- a/configure.ac +++ b/configure.ac @@ -85,7 +85,7 @@ dnl - Check javascript backend dnl --------------------------------------------------------------------------- AC_ARG_WITH(duktape, AS_HELP_STRING([--with-duktape],[Use Duktape as javascript backend]),with_duktape=yes,with_duktape=no) AS_IF([test x${with_duktape} == xyes], [ - PKG_CHECK_MODULES(LIBJS, [duktape >= 2.0.0 ]) + PKG_CHECK_MODULES(LIBJS, [duktape >= 2.2.0 ]) AC_SUBST(LIBJS_CFLAGS) AC_SUBST(LIBJS_LIBS) ], [ @@ -111,6 +111,12 @@ AC_CHECK_LIB(expat,XML_ParserCreate,[EXPAT_LIBS="-lexpat"], [AC_MSG_ERROR([Can't find expat library. Please install expat.])]) AC_SUBST(EXPAT_LIBS) +AX_PTHREAD([], [AC_MSG_ERROR([Cannot find the way to enable pthread support.])]) +LIBS="$PTHREAD_LIBS $LIBS" +CFLAGS="$CFLAGS $PTHREAD_CFLAGS" +CC="$PTHREAD_CC" +AC_CHECK_FUNCS([pthread_condattr_setclock]) + AC_CHECK_FUNCS(clearenv fdatasync) if test "x$GCC" = "xyes"; then diff --git a/meson.build b/meson.build index 4e44723..ad9ab07 100644 --- a/meson.build +++ b/meson.build @@ -133,10 +133,15 @@ expat_dep = dependency('expat') assert(cc.has_header('expat.h', dependencies: expat_dep), 'Can\'t find expat.h. Please install expat.') assert(cc.has_function('XML_ParserCreate', dependencies: expat_dep), 'Can\'t find expat library. Please install expat.') +duktape_req_version = '>= 2.2.0' + js_engine = get_option('js_engine') if js_engine == 'duktape' - js_dep = dependency('duktape') + js_dep = dependency('duktape', version: duktape_req_version) libm_dep = cc.find_library('m') + thread_dep = dependency('threads') + func = 'pthread_condattr_setclock' + config_h.set('HAVE_' + func.to_upper(), cc.has_function(func, prefix : '#include ')) elif js_engine == 'mozjs' js_dep = dependency('mozjs-78') endif diff --git a/src/polkitbackend/Makefile.am b/src/polkitbackend/Makefile.am index 6a8b4ae..935fb98 100644 --- a/src/polkitbackend/Makefile.am +++ b/src/polkitbackend/Makefile.am @@ -17,6 +17,8 @@ AM_CPPFLAGS = \ -DPACKAGE_LIB_DIR=\""$(libdir)"\" \ -D_POSIX_PTHREAD_SEMANTICS \ -D_REENTRANT \ + -D_XOPEN_SOURCE=700 \ + -D_GNU_SOURCE=1 \ $(NULL) noinst_LTLIBRARIES=libpolkit-backend-1.la diff --git a/src/polkitbackend/meson.build b/src/polkitbackend/meson.build index 9ec01b2..266f280 100644 --- a/src/polkitbackend/meson.build +++ b/src/polkitbackend/meson.build @@ -29,11 +29,14 @@ c_flags = [ '-D_POLKIT_BACKEND_COMPILATION', '-DPACKAGE_DATA_DIR="@0@"'.format(pk_prefix / pk_datadir), '-DPACKAGE_SYSCONF_DIR="@0@"'.format(pk_prefix / pk_sysconfdir), + '-D_XOPEN_SOURCE=700', + '-D_GNU_SOURCE=1', ] if js_engine == 'duktape' sources += files('polkitbackendduktapeauthority.c') deps += libm_dep + deps += thread_dep elif js_engine == 'mozjs' sources += files('polkitbackendjsauthority.cpp') endif diff --git a/src/polkitbackend/polkitbackendcommon.h b/src/polkitbackend/polkitbackendcommon.h index 6d0d267..dd700fc 100644 --- a/src/polkitbackend/polkitbackendcommon.h +++ b/src/polkitbackend/polkitbackendcommon.h @@ -50,6 +50,8 @@ #include #endif /* HAVE_LIBSYSTEMD */ +#define RUNAWAY_KILLER_TIMEOUT (15) + #ifdef __cplusplus extern "C" { #endif diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c index a2b4420..c89dbcf 100644 --- a/src/polkitbackend/polkitbackendduktapeauthority.c +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -21,6 +21,8 @@ * Author: David Zeuthen */ +#include + #include "polkitbackendcommon.h" #include "duktape.h" @@ -47,8 +49,20 @@ struct _PolkitBackendJsAuthorityPrivate GFileMonitor **dir_monitors; /* NULL-terminated array of GFileMonitor instances */ duk_context *cx; + + pthread_t runaway_killer_thread; }; +enum +{ + RUNAWAY_KILLER_THREAD_EXIT_STATUS_UNSET, + RUNAWAY_KILLER_THREAD_EXIT_STATUS_SUCCESS, + RUNAWAY_KILLER_THREAD_EXIT_STATUS_FAILURE, +}; + +static gboolean execute_script_with_runaway_killer(PolkitBackendJsAuthority *authority, + const gchar *filename); + /* ---------------------------------------------------------------------------------------------------- */ G_DEFINE_TYPE (PolkitBackendJsAuthority, polkit_backend_js_authority, POLKIT_BACKEND_TYPE_INTERACTIVE_AUTHORITY); @@ -67,6 +81,15 @@ static const duk_function_list_entry js_polkit_functions[] = { NULL, NULL, 0 }, }; +static void report_error (void *udata, + const char *msg) +{ + PolkitBackendJsAuthority *authority = udata; + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "fatal Duktape JS backend error: %s", + (msg ? msg : "no message")); +} + static void polkit_backend_js_authority_init (PolkitBackendJsAuthority *authority) { @@ -78,7 +101,6 @@ polkit_backend_js_authority_init (PolkitBackendJsAuthority *authority) static void load_scripts (PolkitBackendJsAuthority *authority) { - duk_context *cx = authority->priv->cx; GList *files = NULL; GList *l; guint num_scripts = 0; @@ -123,36 +145,9 @@ load_scripts (PolkitBackendJsAuthority *authority) for (l = files; l != NULL; l = l->next) { const gchar *filename = (gchar *)l->data; -#if (DUK_VERSION >= 20000) - GFile *file = g_file_new_for_path (filename); - char *contents; - gsize len; - if (!g_file_load_contents (file, NULL, &contents, &len, NULL, NULL)) - { - polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), - "Error compiling script %s", - filename); - g_object_unref (file); - continue; - } - g_object_unref (file); - if (duk_peval_lstring_noresult(cx, contents,len) != 0) -#else - if (duk_peval_file_noresult (cx, filename) != 0) -#endif - { - polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), - "Error compiling script %s: %s", - filename, duk_safe_to_string (authority->priv->cx, -1)); -#if (DUK_VERSION >= 20000) - g_free (contents); -#endif + if (!execute_script_with_runaway_killer(authority, filename)) continue; - } -#if (DUK_VERSION >= 20000) - g_free (contents); -#endif num_scripts++; } @@ -232,7 +227,7 @@ polkit_backend_common_js_authority_constructed (GObject *object) PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); duk_context *cx; - cx = duk_create_heap (NULL, NULL, NULL, authority, NULL); + cx = duk_create_heap (NULL, NULL, NULL, authority, report_error); if (cx == NULL) goto fail; @@ -243,6 +238,9 @@ polkit_backend_common_js_authority_constructed (GObject *object) duk_put_function_list (cx, -1, js_polkit_functions); duk_put_prop_string (cx, -2, "polkit"); + /* load polkit objects/functions into JS context (e.g. addRule(), + * _deleteRules(), _runRules() et al) + */ duk_eval_string (cx, init_js); if (authority->priv->rules_dirs == NULL) @@ -510,6 +508,250 @@ push_action_and_details (duk_context *cx, /* ---------------------------------------------------------------------------------------------------- */ +typedef struct { + PolkitBackendJsAuthority *authority; + const gchar *filename; + pthread_cond_t cond; + pthread_mutex_t mutex; + gint ret; +} RunawayKillerCtx; + +static gpointer +runaway_killer_thread_execute_js (gpointer user_data) +{ + RunawayKillerCtx *ctx = user_data; + duk_context *cx = ctx->authority->priv->cx; + + int oldtype, pthread_err; + + if ((pthread_err = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype))) { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (ctx->authority), + "Error setting thread cancel type: %s", + strerror(pthread_err)); + goto err; + } + + GFile *file = g_file_new_for_path(ctx->filename); + char *contents; + gsize len; + + if (!g_file_load_contents(file, NULL, &contents, &len, NULL, NULL)) { + polkit_backend_authority_log(POLKIT_BACKEND_AUTHORITY(ctx->authority), + "Error loading script %s", ctx->filename); + g_object_unref(file); + goto err; + } + + g_object_unref(file); + + /* evaluate the script, trying to print context in any syntax errors + found */ + if (duk_peval_lstring(cx, contents, len) != 0) + { + polkit_backend_authority_log(POLKIT_BACKEND_AUTHORITY(ctx->authority), + "Error compiling script %s: %s", ctx->filename, + duk_safe_to_string(cx, -1)); + duk_pop(cx); + goto free_err; + } + g_free(contents); + + ctx->ret = RUNAWAY_KILLER_THREAD_EXIT_STATUS_SUCCESS; + goto end; + +free_err: + g_free(contents); +err: + ctx->ret = RUNAWAY_KILLER_THREAD_EXIT_STATUS_FAILURE; +end: + if ((pthread_err = pthread_cond_signal(&ctx->cond))) { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (ctx->authority), + "Error signaling on condition variable: %s", + strerror(pthread_err)); + ctx->ret = RUNAWAY_KILLER_THREAD_EXIT_STATUS_FAILURE; + } + return NULL; +} + +static gpointer +runaway_killer_thread_call_js (gpointer user_data) +{ + RunawayKillerCtx *ctx = user_data; + duk_context *cx = ctx->authority->priv->cx; + int oldtype, pthread_err; + + if ((pthread_err = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype))) { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (ctx->authority), + "Error setting thread cancel type: %s", + strerror(pthread_err)); + goto err; + } + + if (duk_pcall_prop (cx, 0, 2) != DUK_EXEC_SUCCESS) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (ctx->authority), + "Error evaluating admin rules: ", + duk_safe_to_string (cx, -1)); + goto err; + } + + ctx->ret = RUNAWAY_KILLER_THREAD_EXIT_STATUS_SUCCESS; + goto end; + +err: + ctx->ret = RUNAWAY_KILLER_THREAD_EXIT_STATUS_FAILURE; +end: + if ((pthread_err = pthread_cond_signal(&ctx->cond))) { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (ctx->authority), + "Error signaling on condition variable: %s", + strerror(pthread_err)); + ctx->ret = RUNAWAY_KILLER_THREAD_EXIT_STATUS_FAILURE; + } + return NULL; +} + +#if defined (HAVE_PTHREAD_CONDATTR_SETCLOCK) +# if defined(CLOCK_MONOTONIC) +# define PK_CLOCK CLOCK_MONOTONIC +# elif defined(CLOCK_BOOTTIME) +# define PK_CLOCK CLOCK_BOOTTIME +# else + /* No suitable clock */ +# undef HAVE_PTHREAD_CONDATTR_SETCLOCK +# define PK_CLOCK CLOCK_REALTIME +# endif +#else /* ! HAVE_PTHREAD_CONDATTR_SETCLOCK */ +# define PK_CLOCK CLOCK_REALTIME +#endif /* ! HAVE_PTHREAD_CONDATTR_SETCLOCK */ + +static gboolean +runaway_killer_common(PolkitBackendJsAuthority *authority, RunawayKillerCtx *ctx, void *js_context_cb (void *user_data)) +{ + int pthread_err; + gboolean cancel = FALSE; + pthread_condattr_t attr; + struct timespec abs_time; + +#ifdef HAVE_PTHREAD_CONDATTR_SETCLOCK + if ((pthread_err = pthread_condattr_init(&attr))) { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error initializing condition variable attributes: %s", + strerror(pthread_err)); + return FALSE; + } + if ((pthread_err = pthread_condattr_setclock(&attr, PK_CLOCK))) { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error setting condition variable attributes: %s", + strerror(pthread_err)); + goto err_clean_condattr; + } + /* Init again, with needed attr */ + if ((pthread_err = pthread_cond_init(&ctx->cond, &attr))) { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error initializing condition variable: %s", + strerror(pthread_err)); + goto err_clean_condattr; + } +#endif + + if ((pthread_err = pthread_mutex_lock(&ctx->mutex))) { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error locking mutex: %s", + strerror(pthread_err)); + goto err_clean_cond; + } + + if (clock_gettime(PK_CLOCK, &abs_time)) { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error getting system's monotonic time: %s", + strerror(errno)); + goto err_clean_cond; + } + abs_time.tv_sec += RUNAWAY_KILLER_TIMEOUT; + + if ((pthread_err = pthread_create(&authority->priv->runaway_killer_thread, NULL, + js_context_cb, ctx))) { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error creating runaway JS killer thread: %s", + strerror(pthread_err)); + goto err_clean_cond; + } + + while (ctx->ret == RUNAWAY_KILLER_THREAD_EXIT_STATUS_UNSET) /* loop to treat spurious wakeups */ + if (pthread_cond_timedwait(&ctx->cond, &ctx->mutex, &abs_time) == ETIMEDOUT) { + cancel = TRUE; + + /* Log that we are terminating the script */ + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Terminating runaway script after %d seconds", + RUNAWAY_KILLER_TIMEOUT); + + break; + } + + if ((pthread_err = pthread_mutex_unlock(&ctx->mutex))) { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error unlocking mutex: %s", + strerror(pthread_err)); + goto err_clean_cond; + } + + if (cancel) { + if ((pthread_err = pthread_cancel (authority->priv->runaway_killer_thread))) { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error cancelling runaway JS killer thread: %s", + strerror(pthread_err)); + goto err_clean_cond; + } + } + if ((pthread_err = pthread_join (authority->priv->runaway_killer_thread, NULL))) { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error joining runaway JS killer thread: %s", + strerror(pthread_err)); + goto err_clean_cond; + } + + return ctx->ret == RUNAWAY_KILLER_THREAD_EXIT_STATUS_SUCCESS; + + err_clean_cond: +#ifdef HAVE_PTHREAD_CONDATTR_SETCLOCK + pthread_cond_destroy(&ctx->cond); +#endif + err_clean_condattr: +#ifdef HAVE_PTHREAD_CONDATTR_SETCLOCK + pthread_condattr_destroy(&attr); +#endif + return FALSE; +} + +/* Blocking for at most RUNAWAY_KILLER_TIMEOUT */ +static gboolean +execute_script_with_runaway_killer(PolkitBackendJsAuthority *authority, + const gchar *filename) +{ + RunawayKillerCtx ctx = {.authority = authority, .filename = filename, + .ret = RUNAWAY_KILLER_THREAD_EXIT_STATUS_UNSET, + .mutex = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER}; + + return runaway_killer_common(authority, &ctx, &runaway_killer_thread_execute_js); +} + +/* Calls already stacked function and args. Blocking for at most + * RUNAWAY_KILLER_TIMEOUT. If timeout is the case, ctx.ret will be + * RUNAWAY_KILLER_THREAD_EXIT_STATUS_UNSET, thus returning FALSE. + */ +static gboolean +call_js_function_with_runaway_killer(PolkitBackendJsAuthority *authority) +{ + RunawayKillerCtx ctx = {.authority = authority, + .ret = RUNAWAY_KILLER_THREAD_EXIT_STATUS_UNSET, + .mutex = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER}; + + return runaway_killer_common(authority, &ctx, &runaway_killer_thread_call_js); +} + /* ---------------------------------------------------------------------------------------------------- */ GList * @@ -557,13 +799,8 @@ polkit_backend_common_js_authority_get_admin_auth_identities (PolkitBackendInter goto out; } - if (duk_pcall_prop (cx, 0, 2) != DUK_ERR_NONE) - { - polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), - "Error evaluating admin rules: ", - duk_safe_to_string (cx, -1)); - goto out; - } + if (!call_js_function_with_runaway_killer (authority)) + goto out; ret_str = duk_require_string (cx, -1); @@ -643,15 +880,15 @@ polkit_backend_common_js_authority_check_authorization_sync (PolkitBackendIntera goto out; } - if (duk_pcall_prop (cx, 0, 2) != DUK_ERR_NONE) - { - polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), - "Error evaluating authorization rules: ", - duk_safe_to_string (cx, -1)); - goto out; - } + // If any error is the js context happened (ctx.ret == + // RUNAWAY_KILLER_THREAD_EXIT_STATUS_FAILURE) or it never properly returned + // (runaway scripts or ctx.ret == RUNAWAY_KILLER_THREAD_EXIT_STATUS_UNSET), + // unauthorize + if (!call_js_function_with_runaway_killer (authority)) + goto out; if (duk_is_null(cx, -1)) { + /* this is fine, means there was no match, use implicit authorizations */ good = TRUE; goto out; } @@ -690,11 +927,7 @@ js_polkit_log (duk_context *cx) static duk_ret_t js_polkit_spawn (duk_context *cx) { -#if (DUK_VERSION >= 20000) duk_ret_t ret = DUK_RET_ERROR; -#else - duk_ret_t ret = DUK_RET_INTERNAL_ERROR; -#endif gchar *standard_output = NULL; gchar *standard_error = NULL; gint exit_status; diff --git a/src/polkitbackend/polkitbackendjsauthority.cpp b/src/polkitbackend/polkitbackendjsauthority.cpp index e28091d..11e91c0 100644 --- a/src/polkitbackend/polkitbackendjsauthority.cpp +++ b/src/polkitbackend/polkitbackendjsauthority.cpp @@ -829,11 +829,14 @@ runaway_killer_setup (PolkitBackendJsAuthority *authority) { g_assert (authority->priv->rkt_source == NULL); - /* set-up timer for runaway scripts, will be executed in runaway_killer_thread */ + /* set-up timer for runaway scripts, will be executed in + runaway_killer_thread, that is one, permanent thread running a glib + mainloop (rkt_loop) whose context (rkt_context) has a timeout source + (rkt_source) */ g_mutex_lock (&authority->priv->rkt_timeout_pending_mutex); authority->priv->rkt_timeout_pending = FALSE; g_mutex_unlock (&authority->priv->rkt_timeout_pending_mutex); - authority->priv->rkt_source = g_timeout_source_new_seconds (15); + authority->priv->rkt_source = g_timeout_source_new_seconds (RUNAWAY_KILLER_TIMEOUT); g_source_set_callback (authority->priv->rkt_source, rkt_on_timeout, authority, NULL); g_source_attach (authority->priv->rkt_source, authority->priv->rkt_context); @@ -893,6 +896,9 @@ execute_script_with_runaway_killer (PolkitBackendJsAuthority *authority, { bool ret; + // tries to JS_ExecuteScript(), may hang for > RUNAWAY_KILLER_TIMEOUT, + // runaway_killer_thread makes sure the call returns, due to exception + // injection runaway_killer_setup (authority); ret = JS_ExecuteScript (authority->priv->cx, script, diff --git a/test/data/etc/polkit-1/rules.d/10-testing.rules b/test/data/etc/polkit-1/rules.d/10-testing.rules index 98bf062..e346b5d 100644 --- a/test/data/etc/polkit-1/rules.d/10-testing.rules +++ b/test/data/etc/polkit-1/rules.d/10-testing.rules @@ -189,8 +189,10 @@ polkit.addRule(function(action, subject) { ; } catch (error) { if (error == "Terminating runaway script") - return polkit.Result.YES; - return polkit.Result.NO; + // Inverted logic to accomodate Duktape's model as well, which + // will always fail with negation, on timeouts + return polkit.Result.NO; + return polkit.Result.YES; } } }); diff --git a/test/polkitbackend/test-polkitbackendjsauthority.c b/test/polkitbackend/test-polkitbackendjsauthority.c index f97e0e0..2103b17 100644 --- a/test/polkitbackend/test-polkitbackendjsauthority.c +++ b/test/polkitbackend/test-polkitbackendjsauthority.c @@ -328,7 +328,7 @@ static const RulesTestCase rules_test_cases[] = { "net.company.run_away_script", "unix-user:root", NULL, - POLKIT_IMPLICIT_AUTHORIZATION_AUTHORIZED, + POLKIT_IMPLICIT_AUTHORIZATION_NOT_AUTHORIZED, }, { -- GitLab From 478bcc0b6b4fb44d4199c475d1581d78a8b6c30b Mon Sep 17 00:00:00 2001 From: Gustavo Lima Chaves Date: Thu, 28 Oct 2021 10:04:53 -0700 Subject: [PATCH 17/17] duktape: document runaway behavior Signed-off-by: Gustavo Lima Chaves --- docs/man/polkit.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/man/polkit.xml b/docs/man/polkit.xml index 99aa474..90715a5 100644 --- a/docs/man/polkit.xml +++ b/docs/man/polkit.xml @@ -639,7 +639,9 @@ polkit.Result = { If user-provided code takes a long time to execute, an exception will be thrown which normally results in the function being terminated (the current limit is 15 seconds). This is used to - catch runaway scripts. + catch runaway scripts. If the duktape JavaScript backend is + compiled in, instead of mozjs, no exception will be thrown—the + script will be killed right away (same timeout). -- GitLab