/* * 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 #include "polkitbackendcommon.h" #include "duktape.h" /* Built source and not too big to worry about deduplication */ #include "initjs.h" /* init.js */ /** * SECTION:polkitbackendjsauthority * @title: PolkitBackendJsAuthority * @short_description: JS Authority * @stability: Unstable * * An (Duktape-based) implementation of #PolkitBackendAuthority that reads and * evaluates 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; 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); /* ---------------------------------------------------------------------------------------------------- */ 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 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) { authority->priv = G_TYPE_INSTANCE_GET_PRIVATE (authority, POLKIT_BACKEND_TYPE_JS_AUTHORITY, PolkitBackendJsAuthorityPrivate); } static void load_scripts (PolkitBackendJsAuthority *authority) { GList *files = NULL; GList *l; guint num_scripts = 0; GError *error = NULL; guint n; files = NULL; for (n = 0; authority->priv->rules_dirs != NULL && authority->priv->rules_dirs[n] != NULL; n++) { const gchar *dir_name = authority->priv->rules_dirs[n]; GDir *dir = NULL; polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), "Loading rules from directory %s", dir_name); dir = g_dir_open (dir_name, 0, &error); if (dir == NULL) { polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), "Error opening rules directory: %s (%s, %d)", error->message, g_quark_to_string (error->domain), error->code); g_clear_error (&error); } else { const gchar *name; while ((name = g_dir_read_name (dir)) != NULL) { if (g_str_has_suffix (name, ".rules")) files = g_list_prepend (files, g_strdup_printf ("%s/%s", dir_name, name)); } g_dir_close (dir); } } files = g_list_sort (files, (GCompareFunc) polkit_backend_common_rules_file_name_cmp); for (l = files; l != NULL; l = l->next) { const gchar *filename = (gchar *)l->data; if (!execute_script_with_runaway_killer(authority, filename)) continue; 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); } void polkit_backend_common_reload_scripts (PolkitBackendJsAuthority *authority) { duk_context *cx = authority->priv->cx; duk_set_top (cx, 0); 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); 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 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 (polkit_backend_common_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); } void 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, report_error); 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"); /* 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) { 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 (); } void polkit_backend_common_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 (polkit_backend_common_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); } 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); 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 void polkit_backend_js_authority_class_init (PolkitBackendJsAuthorityClass *klass) { polkit_backend_common_js_authority_class_init_common (klass); 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; if (!duk_get_global_string (cx, "Subject")) { return FALSE; } 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; if (!duk_get_global_string (cx, "Action")) { return FALSE; } 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; } /* ---------------------------------------------------------------------------------------------------- */ 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: %s", 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 * 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; 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); 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)) { 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 (!call_js_function_with_runaway_killer (authority)) 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; } /* ---------------------------------------------------------------------------------------------------- */ 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; GError *error = NULL; gchar *ret_str = NULL; gboolean good = FALSE; duk_context *cx = authority->priv->cx; duk_set_top (cx, 0); if (!duk_get_global_string (cx, "polkit")) { goto out; } 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 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; } 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; if (ret_str != NULL) 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 duk_ret_t js_polkit_spawn (duk_context *cx) { duk_ret_t ret = DUK_RET_ERROR; 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; 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 (!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); 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)", polkit_backend_common_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); #ifdef HAVE_SETNETGRENT if (innetgr (netgroup, NULL, /* host */ user, NULL)) /* domain */ { is_in_netgroup = TRUE; } #endif duk_push_boolean (cx, is_in_netgroup); return 1; } /* ---------------------------------------------------------------------------------------------------- */