diff options
Diffstat (limited to 'src/polkitbackend/polkitbackendduktapeauthority.c')
-rw-r--r-- | src/polkitbackend/polkitbackendduktapeauthority.c | 1051 |
1 files changed, 1051 insertions, 0 deletions
diff --git a/src/polkitbackend/polkitbackendduktapeauthority.c b/src/polkitbackend/polkitbackendduktapeauthority.c new file mode 100644 index 0000000..c89dbcf --- /dev/null +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -0,0 +1,1051 @@ +/* + * Copyright (C) 2008-2012 Red Hat, Inc. + * Copyright (C) 2015 Tangent Space <jstpierre@mecheye.net> + * Copyright (C) 2019 Wu Xiaotian <yetist@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen <davidz@redhat.com> + */ + +#include <pthread.h> + +#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: ", + 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); + + if (innetgr (netgroup, + NULL, /* host */ + user, + NULL)) /* domain */ + { + is_in_netgroup = TRUE; + } + + duk_push_boolean (cx, is_in_netgroup); + return 1; +} + +/* ---------------------------------------------------------------------------------------------------- */ |