From c7fc4e1b61f0fd82fc697c19c604af7e9fb291a2 Mon Sep 17 00:00:00 2001
From: Gustavo Lima Chaves <limachaves@gmail.com>
Date: Tue, 25 Jan 2022 09:43:21 +0000
Subject: [PATCH] Added support for duktape as JS engine

Original author: Wu Xiaotian (@yetist)
Resurrection author, runaway-killer author: Gustavo Lima Chaves (@limachaves)
---
 buildutil/ax_pthread.m4                            |  522 ++++++++++
 configure.ac                                       |   34 +-
 docs/man/polkit.xml                                |    4 +-
 meson.build                                        |   16 +-
 meson_options.txt                                  |    1 +
 src/polkitbackend/Makefile.am                      |   17 +-
 src/polkitbackend/meson.build                      |   14 +-
 src/polkitbackend/polkitbackendcommon.c            |  530 ++++++++++
 src/polkitbackend/polkitbackendcommon.h            |  158 +++
 src/polkitbackend/polkitbackendduktapeauthority.c  | 1051 ++++++++++++++++++++
 src/polkitbackend/polkitbackendjsauthority.cpp     |  721 ++------------
 test/data/etc/polkit-1/rules.d/10-testing.rules    |    6 +-
 test/polkitbackend/test-polkitbackendjsauthority.c |    2 +-
 13 files changed, 2398 insertions(+), 678 deletions(-)
 create mode 100644 buildutil/ax_pthread.m4
 create mode 100644 src/polkitbackend/polkitbackendcommon.c
 create mode 100644 src/polkitbackend/polkitbackendcommon.h
 create mode 100644 src/polkitbackend/polkitbackendduktapeauthority.c

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 <stevenj@alum.mit.edu>
+#   Copyright (c) 2011 Daniel Richard G. <skunk@iSKUNK.ORG>
+#   Copyright (c) 2019 Marc Stevens <marc.stevens@cwi.nl>
+#
+#   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 <https://www.gnu.org/licenses/>.
+#
+#   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 <pthread.h>
+#                       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 <pthread.h>],
+                                                 [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 <pthread.h>]],
+                                             [[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 e434ca2..e182238 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.2.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=<dir>      Use expat from here],
@@ -100,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
@@ -585,6 +602,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/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).
       </para>
 
       <para>
diff --git a/meson.build b/meson.build
index 858078d..ad9ab07 100644
--- a/meson.build
+++ b/meson.build
@@ -133,7 +133,18 @@ 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')
+duktape_req_version = '>= 2.2.0'
+
+js_engine = get_option('js_engine')
+if js_engine == '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 <pthread.h>'))
+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 +372,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/Makefile.am b/src/polkitbackend/Makefile.am
index 7e3c080..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
@@ -31,9 +33,10 @@ 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		polkitbackendjsauthority.cpp		\
+	polkitbackendjsauthority.h				\
 	polkitbackendactionpool.h		polkitbackendactionpool.c		\
 	polkitbackendactionlookup.h		polkitbackendactionlookup.c		\
         $(NULL)
@@ -51,19 +54,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/meson.build b/src/polkitbackend/meson.build
index 64f0e4a..266f280 100644
--- a/src/polkitbackend/meson.build
+++ b/src/polkitbackend/meson.build
@@ -4,8 +4,8 @@ sources = files(
   'polkitbackendactionlookup.c',
   'polkitbackendactionpool.c',
   'polkitbackendauthority.c',
+  'polkitbackendcommon.c',
   'polkitbackendinteractiveauthority.c',
-  'polkitbackendjsauthority.cpp',
 )
 
 output = 'initjs.h'
@@ -21,7 +21,7 @@ sources += custom_target(
 deps = [
   expat_dep,
   libpolkit_gobject_dep,
-  mozjs_dep,
+  js_dep,
 ]
 
 c_flags = [
@@ -29,8 +29,18 @@ 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
+
 if enable_logind
   sources += files('polkitbackendsessionmonitor-systemd.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 <davidz@redhat.com>
+ */
+
+#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..dd700fc
--- /dev/null
+++ b/src/polkitbackend/polkitbackendcommon.h
@@ -0,0 +1,158 @@
+/*
+ * 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 <davidz@redhat.com>
+ */
+
+#if !defined (_POLKIT_BACKEND_COMPILATION) && !defined(_POLKIT_BACKEND_INSIDE_POLKIT_BACKEND_H)
+#error "Only <polkitbackend/polkitbackend.h> 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 <sys/wait.h>
+#include <errno.h>
+#include <pwd.h>
+#include <grp.h>
+#ifdef HAVE_NETGROUP_H
+#include <netgroup.h>
+#else
+#include <netdb.h>
+#endif
+#include <string.h>
+#include <glib/gstdio.h>
+#include <locale.h>
+#include <glib/gi18n-lib.h> //here, all things glib via glib.h (including -> gspawn.h)
+
+#include <polkit/polkit.h>
+#include "polkitbackendjsauthority.h"
+
+#include <polkit/polkitprivate.h>
+
+#ifdef HAVE_LIBSYSTEMD
+#include <systemd/sd-login.h>
+#endif /* HAVE_LIBSYSTEMD */
+
+#define RUNAWAY_KILLER_TIMEOUT (15)
+
+#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
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;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/src/polkitbackend/polkitbackendjsauthority.cpp b/src/polkitbackend/polkitbackendjsauthority.cpp
index ca17108..11e91c0 100644
--- a/src/polkitbackend/polkitbackendjsauthority.cpp
+++ b/src/polkitbackend/polkitbackendjsauthority.cpp
@@ -19,29 +19,7 @@
  * Author: David Zeuthen <davidz@redhat.com>
  */
 
-#include "config.h"
-#include <sys/wait.h>
-#include <errno.h>
-#include <pwd.h>
-#include <grp.h>
-#ifdef HAVE_NETGROUP_H
-#include <netgroup.h>
-#else
-#include <netdb.h>
-#endif
-#include <string.h>
-#include <glib/gstdio.h>
-#include <locale.h>
-#include <glib/gi18n-lib.h>
-
-#include <polkit/polkit.h>
-#include "polkitbackendjsauthority.h"
-
-#include <polkit/polkitprivate.h>
-
-#ifdef HAVE_LIBSYSTEMD
-#include <systemd/sd-login.h>
-#endif /* HAVE_LIBSYSTEMD */
+#include "polkitbackendcommon.h"
 
 #include <js/CompilationAndEvaluation.h>
 #include <js/ContextOptions.h>
@@ -52,6 +30,7 @@
 #include <js/Array.h>
 #include <jsapi.h>
 
+/* 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);
@@ -396,42 +301,6 @@ reload_scripts (PolkitBackendJsAuthority *authority)
 }
 
 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;
@@ -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 ();
 }
 
@@ -1005,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);
 
@@ -1069,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,
@@ -1099,15 +929,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 +1032,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 +1154,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 +1211,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 +1248,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 +1313,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;
-}
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,
   },
 
   {
-- 
1.8.3.1

