diff --git a/buildutil/ax_pthread.m4 b/buildutil/ax_pthread.m4 new file mode 100644 index 0000000000000000000000000000000000000000..9f35d139149f8d9bda17cddb730cd13bcf775465 --- /dev/null +++ b/buildutil/ax_pthread.m4 @@ -0,0 +1,522 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_pthread.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) +# +# DESCRIPTION +# +# This macro figures out how to build C programs using POSIX threads. It +# sets the PTHREAD_LIBS output variable to the threads library and linker +# flags, and the PTHREAD_CFLAGS output variable to any special C compiler +# flags that are needed. (The user can also force certain compiler +# flags/libs to be tested by setting these environment variables.) +# +# Also sets PTHREAD_CC and PTHREAD_CXX to any special C compiler that is +# needed for multi-threaded programs (defaults to the value of CC +# respectively CXX otherwise). (This is necessary on e.g. AIX to use the +# special cc_r/CC_r compiler alias.) +# +# NOTE: You are assumed to not only compile your program with these flags, +# but also to link with them as well. For example, you might link with +# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS +# $PTHREAD_CXX $CXXFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS +# +# If you are only building threaded programs, you may wish to use these +# variables in your default LIBS, CFLAGS, and CC: +# +# LIBS="$PTHREAD_LIBS $LIBS" +# CFLAGS="$CFLAGS $PTHREAD_CFLAGS" +# CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS" +# CC="$PTHREAD_CC" +# CXX="$PTHREAD_CXX" +# +# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant +# has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to +# that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX). +# +# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the +# PTHREAD_PRIO_INHERIT symbol is defined when compiling with +# PTHREAD_CFLAGS. +# +# ACTION-IF-FOUND is a list of shell commands to run if a threads library +# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it +# is not found. If ACTION-IF-FOUND is not specified, the default action +# will define HAVE_PTHREAD. +# +# Please let the authors know if this macro fails on any platform, or if +# you have any other suggestions or comments. This macro was based on work +# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help +# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by +# Alejandro Forero Cuervo to the autoconf macro repository. We are also +# grateful for the helpful feedback of numerous users. +# +# Updated for Autoconf 2.68 by Daniel Richard G. +# +# LICENSE +# +# Copyright (c) 2008 Steven G. Johnson +# Copyright (c) 2011 Daniel Richard G. +# Copyright (c) 2019 Marc Stevens +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 31 + +AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) +AC_DEFUN([AX_PTHREAD], [ +AC_REQUIRE([AC_CANONICAL_HOST]) +AC_REQUIRE([AC_PROG_CC]) +AC_REQUIRE([AC_PROG_SED]) +AC_LANG_PUSH([C]) +ax_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on Tru64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then + ax_pthread_save_CC="$CC" + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"]) + AS_IF([test "x$PTHREAD_CXX" != "x"], [CXX="$PTHREAD_CXX"]) + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS]) + AC_LINK_IFELSE([AC_LANG_CALL([], [pthread_join])], [ax_pthread_ok=yes]) + AC_MSG_RESULT([$ax_pthread_ok]) + if test "x$ax_pthread_ok" = "xno"; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + CC="$ax_pthread_save_CC" + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items with a "," contain both +# C compiler flags (before ",") and linker flags (after ","). Other items +# starting with a "-" are C compiler flags, and remaining items are +# library names, except for "none" which indicates that we try without +# any flags at all, and "pthread-config" which is a program returning +# the flags for the Pth emulation library. + +ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64 +# (Note: HP C rejects this with "bad form for `-t' option") +# -pthreads: Solaris/gcc (Note: HP C also rejects) +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads and +# -D_REENTRANT too), HP C (must be checked before -lpthread, which +# is present but should not be used directly; and before -mthreads, +# because the compiler interprets this as "-mt" + "-hreads") +# -mthreads: Mingw32/gcc, Lynx/gcc +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case $host_os in + + freebsd*) + + # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) + # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) + + ax_pthread_flags="-kthread lthread $ax_pthread_flags" + ;; + + hpux*) + + # From the cc(1) man page: "[-mt] Sets various -D flags to enable + # multi-threading and also sets -lpthread." + + ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags" + ;; + + openedition*) + + # IBM z/OS requires a feature-test macro to be defined in order to + # enable POSIX threads at all, so give the user a hint if this is + # not set. (We don't define these ourselves, as they can affect + # other portions of the system API in unpredictable ways.) + + AC_EGREP_CPP([AX_PTHREAD_ZOS_MISSING], + [ +# if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS) + AX_PTHREAD_ZOS_MISSING +# endif + ], + [AC_MSG_WARN([IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support.])]) + ;; + + solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (N.B.: The stubs are missing + # pthread_cleanup_push, or rather a function called by this macro, + # so we could check for that, but who knows whether they'll stub + # that too in a future libc.) So we'll check first for the + # standard Solaris way of linking pthreads (-mt -lpthread). + + ax_pthread_flags="-mt,-lpthread pthread $ax_pthread_flags" + ;; +esac + +# Are we compiling with Clang? + +AC_CACHE_CHECK([whether $CC is Clang], + [ax_cv_PTHREAD_CLANG], + [ax_cv_PTHREAD_CLANG=no + # Note that Autoconf sets GCC=yes for Clang as well as GCC + if test "x$GCC" = "xyes"; then + AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG], + [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */ +# if defined(__clang__) && defined(__llvm__) + AX_PTHREAD_CC_IS_CLANG +# endif + ], + [ax_cv_PTHREAD_CLANG=yes]) + fi + ]) +ax_pthread_clang="$ax_cv_PTHREAD_CLANG" + + +# GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC) + +# Note that for GCC and Clang -pthread generally implies -lpthread, +# except when -nostdlib is passed. +# This is problematic using libtool to build C++ shared libraries with pthread: +# [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25460 +# [2] https://bugzilla.redhat.com/show_bug.cgi?id=661333 +# [3] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=468555 +# To solve this, first try -pthread together with -lpthread for GCC + +AS_IF([test "x$GCC" = "xyes"], + [ax_pthread_flags="-pthread,-lpthread -pthread -pthreads $ax_pthread_flags"]) + +# Clang takes -pthread (never supported any other flag), but we'll try with -lpthread first + +AS_IF([test "x$ax_pthread_clang" = "xyes"], + [ax_pthread_flags="-pthread,-lpthread -pthread"]) + + +# The presence of a feature test macro requesting re-entrant function +# definitions is, on some systems, a strong hint that pthreads support is +# correctly enabled + +case $host_os in + darwin* | hpux* | linux* | osf* | solaris*) + ax_pthread_check_macro="_REENTRANT" + ;; + + aix*) + ax_pthread_check_macro="_THREAD_SAFE" + ;; + + *) + ax_pthread_check_macro="--" + ;; +esac +AS_IF([test "x$ax_pthread_check_macro" = "x--"], + [ax_pthread_check_cond=0], + [ax_pthread_check_cond="!defined($ax_pthread_check_macro)"]) + + +if test "x$ax_pthread_ok" = "xno"; then +for ax_pthread_try_flag in $ax_pthread_flags; do + + case $ax_pthread_try_flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + *,*) + PTHREAD_CFLAGS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\1/"` + PTHREAD_LIBS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\2/"` + AC_MSG_CHECKING([whether pthreads work with "$PTHREAD_CFLAGS" and "$PTHREAD_LIBS"]) + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag]) + PTHREAD_CFLAGS="$ax_pthread_try_flag" + ;; + + pthread-config) + AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) + AS_IF([test "x$ax_pthread_config" = "xno"], [continue]) + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag]) + PTHREAD_LIBS="-l$ax_pthread_try_flag" + ;; + esac + + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include +# if $ax_pthread_check_cond +# error "$ax_pthread_check_macro must be defined" +# endif + static void *some_global = NULL; + static void routine(void *a) + { + /* To avoid any unused-parameter or + unused-but-set-parameter warning. */ + some_global = a; + } + static void *start_routine(void *a) { return a; }], + [pthread_t th; pthread_attr_t attr; + pthread_create(&th, 0, start_routine, 0); + pthread_join(th, 0); + pthread_attr_init(&attr); + pthread_cleanup_push(routine, 0); + pthread_cleanup_pop(0) /* ; */])], + [ax_pthread_ok=yes], + []) + + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" + + AC_MSG_RESULT([$ax_pthread_ok]) + AS_IF([test "x$ax_pthread_ok" = "xyes"], [break]) + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + + +# Clang needs special handling, because older versions handle the -pthread +# option in a rather... idiosyncratic way + +if test "x$ax_pthread_clang" = "xyes"; then + + # Clang takes -pthread; it has never supported any other flag + + # (Note 1: This will need to be revisited if a system that Clang + # supports has POSIX threads in a separate library. This tends not + # to be the way of modern systems, but it's conceivable.) + + # (Note 2: On some systems, notably Darwin, -pthread is not needed + # to get POSIX threads support; the API is always present and + # active. We could reasonably leave PTHREAD_CFLAGS empty. But + # -pthread does define _REENTRANT, and while the Darwin headers + # ignore this macro, third-party headers might not.) + + # However, older versions of Clang make a point of warning the user + # that, in an invocation where only linking and no compilation is + # taking place, the -pthread option has no effect ("argument unused + # during compilation"). They expect -pthread to be passed in only + # when source code is being compiled. + # + # Problem is, this is at odds with the way Automake and most other + # C build frameworks function, which is that the same flags used in + # compilation (CFLAGS) are also used in linking. Many systems + # supported by AX_PTHREAD require exactly this for POSIX threads + # support, and in fact it is often not straightforward to specify a + # flag that is used only in the compilation phase and not in + # linking. Such a scenario is extremely rare in practice. + # + # Even though use of the -pthread flag in linking would only print + # a warning, this can be a nuisance for well-run software projects + # that build with -Werror. So if the active version of Clang has + # this misfeature, we search for an option to squash it. + + AC_CACHE_CHECK([whether Clang needs flag to prevent "argument unused" warning when linking with -pthread], + [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG], + [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown + # Create an alternate version of $ac_link that compiles and + # links in two steps (.c -> .o, .o -> exe) instead of one + # (.c -> exe), because the warning occurs only in the second + # step + ax_pthread_save_ac_link="$ac_link" + ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g' + ax_pthread_link_step=`AS_ECHO(["$ac_link"]) | sed "$ax_pthread_sed"` + ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)" + ax_pthread_save_CFLAGS="$CFLAGS" + for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do + AS_IF([test "x$ax_pthread_try" = "xunknown"], [break]) + CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS" + ac_link="$ax_pthread_save_ac_link" + AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], + [ac_link="$ax_pthread_2step_ac_link" + AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], + [break]) + ]) + done + ac_link="$ax_pthread_save_ac_link" + CFLAGS="$ax_pthread_save_CFLAGS" + AS_IF([test "x$ax_pthread_try" = "x"], [ax_pthread_try=no]) + ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try" + ]) + + case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in + no | unknown) ;; + *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;; + esac + +fi # $ax_pthread_clang = yes + + + +# Various other checks: +if test "x$ax_pthread_ok" = "xyes"; then + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + + # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. + AC_CACHE_CHECK([for joinable pthread attribute], + [ax_cv_PTHREAD_JOINABLE_ATTR], + [ax_cv_PTHREAD_JOINABLE_ATTR=unknown + for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], + [int attr = $ax_pthread_attr; return attr /* ; */])], + [ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break], + []) + done + ]) + AS_IF([test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \ + test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \ + test "x$ax_pthread_joinable_attr_defined" != "xyes"], + [AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], + [$ax_cv_PTHREAD_JOINABLE_ATTR], + [Define to necessary symbol if this constant + uses a non-standard name on your system.]) + ax_pthread_joinable_attr_defined=yes + ]) + + AC_CACHE_CHECK([whether more special flags are required for pthreads], + [ax_cv_PTHREAD_SPECIAL_FLAGS], + [ax_cv_PTHREAD_SPECIAL_FLAGS=no + case $host_os in + solaris*) + ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS" + ;; + esac + ]) + AS_IF([test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \ + test "x$ax_pthread_special_flags_added" != "xyes"], + [PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS" + ax_pthread_special_flags_added=yes]) + + AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], + [ax_cv_PTHREAD_PRIO_INHERIT], + [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[int i = PTHREAD_PRIO_INHERIT; + return i;]])], + [ax_cv_PTHREAD_PRIO_INHERIT=yes], + [ax_cv_PTHREAD_PRIO_INHERIT=no]) + ]) + AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \ + test "x$ax_pthread_prio_inherit_defined" != "xyes"], + [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.]) + ax_pthread_prio_inherit_defined=yes + ]) + + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" + + # More AIX lossage: compile with *_r variant + if test "x$GCC" != "xyes"; then + case $host_os in + aix*) + AS_CASE(["x/$CC"], + [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], + [#handle absolute path differently from PATH based program lookup + AS_CASE(["x$CC"], + [x/*], + [ + AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"]) + AS_IF([test "x${CXX}" != "x"], [AS_IF([AS_EXECUTABLE_P([${CXX}_r])],[PTHREAD_CXX="${CXX}_r"])]) + ], + [ + AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC]) + AS_IF([test "x${CXX}" != "x"], [AC_CHECK_PROGS([PTHREAD_CXX],[${CXX}_r],[$CXX])]) + ] + ) + ]) + ;; + esac + fi +fi + +test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" +test -n "$PTHREAD_CXX" || PTHREAD_CXX="$CXX" + +AC_SUBST([PTHREAD_LIBS]) +AC_SUBST([PTHREAD_CFLAGS]) +AC_SUBST([PTHREAD_CC]) +AC_SUBST([PTHREAD_CXX]) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test "x$ax_pthread_ok" = "xyes"; then + ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) + : +else + ax_pthread_ok=no + $2 +fi +AC_LANG_POP +])dnl AX_PTHREAD diff --git a/configure.ac b/configure.ac index e434ca2e5de913fa1d13a67afe8e9efc00f68472..e1822380d5f8097c2c2de6106f4fce8e562d1858 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= 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 99aa474fd82b08e2dfa2d90dca11cd2e53cfab4d..90715a53203ec746146131fc24eae015673b19d2 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). diff --git a/meson.build b/meson.build index 858078dd0ffa90076c4c4acea46d55d22ce4709b..ad9ab079b8dab98514135909c6d8c883650e0744 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 ')) +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 25e3e7793ae1671fc65e172386b9392abf4e9352..76aa311c99620129b3594b0d95d675df91dc483b 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 7e3c0809984987d8e77ff6c7717248f132e990f3..935fb986d8db60a31d5f3705b681b9b03ff1caef 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 64f0e4acfd068cc47926fbe38956abd73532559d..266f2808b6eda77db373b71dee4ed146484a0963 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 0000000000000000000000000000000000000000..6783dff145f8ccff7f4021f9bff7bb84c85b8df2 --- /dev/null +++ b/src/polkitbackend/polkitbackendcommon.c @@ -0,0 +1,530 @@ +/* + * Copyright (C) 2008 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "polkitbackendcommon.h" + +static void +utils_child_watch_from_release_cb (GPid pid, + gint status, + gpointer user_data) +{ +} + +static void +utils_spawn_data_free (UtilsSpawnData *data) +{ + if (data->timeout_source != NULL) + { + g_source_destroy (data->timeout_source); + data->timeout_source = NULL; + } + + /* Nuke the child, if necessary */ + if (data->child_watch_source != NULL) + { + g_source_destroy (data->child_watch_source); + data->child_watch_source = NULL; + } + + if (data->child_pid != 0) + { + GSource *source; + kill (data->child_pid, SIGTERM); + /* OK, we need to reap for the child ourselves - we don't want + * to use waitpid() because that might block the calling + * thread (the child might handle SIGTERM and use several + * seconds for cleanup/rollback). + * + * So we use GChildWatch instead. + * + * Avoid taking a references to ourselves. but note that we need + * to pass the GSource so we can nuke it once handled. + */ + source = g_child_watch_source_new (data->child_pid); + g_source_set_callback (source, + (GSourceFunc) utils_child_watch_from_release_cb, + source, + (GDestroyNotify) g_source_destroy); + g_source_attach (source, data->main_context); + g_source_unref (source); + data->child_pid = 0; + } + + if (data->child_stdout != NULL) + { + g_string_free (data->child_stdout, TRUE); + data->child_stdout = NULL; + } + + if (data->child_stderr != NULL) + { + g_string_free (data->child_stderr, TRUE); + data->child_stderr = NULL; + } + + if (data->child_stdout_channel != NULL) + { + g_io_channel_unref (data->child_stdout_channel); + data->child_stdout_channel = NULL; + } + if (data->child_stderr_channel != NULL) + { + g_io_channel_unref (data->child_stderr_channel); + data->child_stderr_channel = NULL; + } + + if (data->child_stdout_source != NULL) + { + g_source_destroy (data->child_stdout_source); + data->child_stdout_source = NULL; + } + if (data->child_stderr_source != NULL) + { + g_source_destroy (data->child_stderr_source); + data->child_stderr_source = NULL; + } + + if (data->child_stdout_fd != -1) + { + g_warn_if_fail (close (data->child_stdout_fd) == 0); + data->child_stdout_fd = -1; + } + if (data->child_stderr_fd != -1) + { + g_warn_if_fail (close (data->child_stderr_fd) == 0); + data->child_stderr_fd = -1; + } + + if (data->cancellable_handler_id > 0) + { + g_cancellable_disconnect (data->cancellable, data->cancellable_handler_id); + data->cancellable_handler_id = 0; + } + + if (data->main_context != NULL) + g_main_context_unref (data->main_context); + + if (data->cancellable != NULL) + g_object_unref (data->cancellable); + + g_slice_free (UtilsSpawnData, data); +} + +/* called in the thread where @cancellable was cancelled */ +static void +utils_on_cancelled (GCancellable *cancellable, + gpointer user_data) +{ + UtilsSpawnData *data = (UtilsSpawnData *)user_data; + GError *error; + + error = NULL; + g_warn_if_fail (g_cancellable_set_error_if_cancelled (cancellable, &error)); + g_simple_async_result_take_error (data->simple, error); + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); +} + +static gboolean +utils_timeout_cb (gpointer user_data) +{ + UtilsSpawnData *data = (UtilsSpawnData *)user_data; + + data->timed_out = TRUE; + + /* ok, timeout is history, make sure we don't free it in spawn_data_free() */ + data->timeout_source = NULL; + + /* we're done */ + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); + + return FALSE; /* remove source */ +} + +static void +utils_child_watch_cb (GPid pid, + gint status, + gpointer user_data) +{ + UtilsSpawnData *data = (UtilsSpawnData *)user_data; + gchar *buf; + gsize buf_size; + + if (g_io_channel_read_to_end (data->child_stdout_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) + { + g_string_append_len (data->child_stdout, buf, buf_size); + g_free (buf); + } + if (g_io_channel_read_to_end (data->child_stderr_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) + { + g_string_append_len (data->child_stderr, buf, buf_size); + g_free (buf); + } + + data->exit_status = status; + + /* ok, child watch is history, make sure we don't free it in spawn_data_free() */ + data->child_pid = 0; + data->child_watch_source = NULL; + + /* we're done */ + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); +} + +static gboolean +utils_read_child_stderr (GIOChannel *channel, + GIOCondition condition, + gpointer user_data) +{ + UtilsSpawnData *data = (UtilsSpawnData *)user_data; + gchar buf[1024]; + gsize bytes_read; + + g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); + g_string_append_len (data->child_stderr, buf, bytes_read); + return TRUE; +} + +static gboolean +utils_read_child_stdout (GIOChannel *channel, + GIOCondition condition, + gpointer user_data) +{ + UtilsSpawnData *data = (UtilsSpawnData *)user_data; + gchar buf[1024]; + gsize bytes_read; + + g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); + g_string_append_len (data->child_stdout, buf, bytes_read); + return TRUE; +} + +void +polkit_backend_common_spawn (const gchar *const *argv, + guint timeout_seconds, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + UtilsSpawnData *data; + GError *error; + + data = g_slice_new0 (UtilsSpawnData); + data->timeout_seconds = timeout_seconds; + data->simple = g_simple_async_result_new (NULL, + callback, + user_data, + (gpointer*)polkit_backend_common_spawn); + data->main_context = g_main_context_get_thread_default (); + if (data->main_context != NULL) + g_main_context_ref (data->main_context); + + data->cancellable = cancellable != NULL ? (GCancellable*)g_object_ref (cancellable) : NULL; + + data->child_stdout = g_string_new (NULL); + data->child_stderr = g_string_new (NULL); + data->child_stdout_fd = -1; + data->child_stderr_fd = -1; + + /* the life-cycle of UtilsSpawnData is tied to its GSimpleAsyncResult */ + g_simple_async_result_set_op_res_gpointer (data->simple, data, (GDestroyNotify) utils_spawn_data_free); + + error = NULL; + if (data->cancellable != NULL) + { + /* could already be cancelled */ + error = NULL; + if (g_cancellable_set_error_if_cancelled (data->cancellable, &error)) + { + g_simple_async_result_take_error (data->simple, error); + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); + goto out; + } + + data->cancellable_handler_id = g_cancellable_connect (data->cancellable, + G_CALLBACK (utils_on_cancelled), + data, + NULL); + } + + error = NULL; + if (!g_spawn_async_with_pipes (NULL, /* working directory */ + (gchar **) argv, + NULL, /* envp */ + G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, + NULL, /* child_setup */ + NULL, /* child_setup's user_data */ + &(data->child_pid), + NULL, /* gint *stdin_fd */ + &(data->child_stdout_fd), + &(data->child_stderr_fd), + &error)) + { + g_prefix_error (&error, "Error spawning: "); + g_simple_async_result_take_error (data->simple, error); + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); + goto out; + } + + if (timeout_seconds > 0) + { + data->timeout_source = g_timeout_source_new_seconds (timeout_seconds); + g_source_set_priority (data->timeout_source, G_PRIORITY_DEFAULT); + g_source_set_callback (data->timeout_source, utils_timeout_cb, data, NULL); + g_source_attach (data->timeout_source, data->main_context); + g_source_unref (data->timeout_source); + } + + data->child_watch_source = g_child_watch_source_new (data->child_pid); + g_source_set_callback (data->child_watch_source, (GSourceFunc) utils_child_watch_cb, data, NULL); + g_source_attach (data->child_watch_source, data->main_context); + g_source_unref (data->child_watch_source); + + data->child_stdout_channel = g_io_channel_unix_new (data->child_stdout_fd); + g_io_channel_set_flags (data->child_stdout_channel, G_IO_FLAG_NONBLOCK, NULL); + data->child_stdout_source = g_io_create_watch (data->child_stdout_channel, G_IO_IN); + g_source_set_callback (data->child_stdout_source, (GSourceFunc) utils_read_child_stdout, data, NULL); + g_source_attach (data->child_stdout_source, data->main_context); + g_source_unref (data->child_stdout_source); + + data->child_stderr_channel = g_io_channel_unix_new (data->child_stderr_fd); + g_io_channel_set_flags (data->child_stderr_channel, G_IO_FLAG_NONBLOCK, NULL); + data->child_stderr_source = g_io_create_watch (data->child_stderr_channel, G_IO_IN); + g_source_set_callback (data->child_stderr_source, (GSourceFunc) utils_read_child_stderr, data, NULL); + g_source_attach (data->child_stderr_source, data->main_context); + g_source_unref (data->child_stderr_source); + + out: + ; +} + +void +polkit_backend_common_on_dir_monitor_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (user_data); + + /* TODO: maybe rate-limit so storms of events are collapsed into one with a 500ms resolution? + * Because when editing a file with emacs we get 4-8 events.. + */ + + if (file != NULL) + { + gchar *name; + + name = g_file_get_basename (file); + + /* g_print ("event_type=%d file=%p name=%s\n", event_type, file, name); */ + if (!g_str_has_prefix (name, ".") && + !g_str_has_prefix (name, "#") && + g_str_has_suffix (name, ".rules") && + (event_type == G_FILE_MONITOR_EVENT_CREATED || + event_type == G_FILE_MONITOR_EVENT_DELETED || + event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Reloading rules"); + polkit_backend_common_reload_scripts (authority); + } + g_free (name); + } +} + +gboolean +polkit_backend_common_spawn_finish (GAsyncResult *res, + gint *out_exit_status, + gchar **out_standard_output, + gchar **out_standard_error, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + UtilsSpawnData *data; + gboolean ret = FALSE; + + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == polkit_backend_common_spawn); + + if (g_simple_async_result_propagate_error (simple, error)) + goto out; + + data = (UtilsSpawnData*)g_simple_async_result_get_op_res_gpointer (simple); + + if (data->timed_out) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_TIMED_OUT, + "Timed out after %d seconds", + data->timeout_seconds); + goto out; + } + + if (out_exit_status != NULL) + *out_exit_status = data->exit_status; + + if (out_standard_output != NULL) + *out_standard_output = g_strdup (data->child_stdout->str); + + if (out_standard_error != NULL) + *out_standard_error = g_strdup (data->child_stderr->str); + + ret = TRUE; + + out: + return ret; +} + +static const gchar * +polkit_backend_js_authority_get_name (PolkitBackendAuthority *authority) +{ + return "js"; +} + +static const gchar * +polkit_backend_js_authority_get_version (PolkitBackendAuthority *authority) +{ + return PACKAGE_VERSION; +} + +static PolkitAuthorityFeatures +polkit_backend_js_authority_get_features (PolkitBackendAuthority *authority) +{ + return POLKIT_AUTHORITY_FEATURES_TEMPORARY_AUTHORIZATION; +} + +void +polkit_backend_common_js_authority_class_init_common (PolkitBackendJsAuthorityClass *klass) +{ + GObjectClass *gobject_class; + PolkitBackendAuthorityClass *authority_class; + PolkitBackendInteractiveAuthorityClass *interactive_authority_class; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = polkit_backend_common_js_authority_finalize; + gobject_class->set_property = polkit_backend_common_js_authority_set_property; + gobject_class->constructed = polkit_backend_common_js_authority_constructed; + + authority_class = POLKIT_BACKEND_AUTHORITY_CLASS (klass); + authority_class->get_name = polkit_backend_js_authority_get_name; + authority_class->get_version = polkit_backend_js_authority_get_version; + authority_class->get_features = polkit_backend_js_authority_get_features; + + interactive_authority_class = POLKIT_BACKEND_INTERACTIVE_AUTHORITY_CLASS (klass); + interactive_authority_class->get_admin_identities = polkit_backend_common_js_authority_get_admin_auth_identities; + interactive_authority_class->check_authorization_sync = polkit_backend_common_js_authority_check_authorization_sync; + + g_object_class_install_property (gobject_class, + PROP_RULES_DIRS, + g_param_spec_boxed ("rules-dirs", + NULL, + NULL, + G_TYPE_STRV, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); +} + +gint +polkit_backend_common_rules_file_name_cmp (const gchar *a, + const gchar *b) +{ + gint ret; + const gchar *a_base; + const gchar *b_base; + + a_base = strrchr (a, '/'); + b_base = strrchr (b, '/'); + + g_assert (a_base != NULL); + g_assert (b_base != NULL); + a_base += 1; + b_base += 1; + + ret = g_strcmp0 (a_base, b_base); + if (ret == 0) + { + /* /etc wins over /usr */ + ret = g_strcmp0 (a, b); + g_assert (ret != 0); + } + + return ret; +} + +const gchar * +polkit_backend_common_get_signal_name (gint signal_number) +{ + switch (signal_number) + { +#define _HANDLE_SIG(sig) case sig: return #sig; + _HANDLE_SIG (SIGHUP); + _HANDLE_SIG (SIGINT); + _HANDLE_SIG (SIGQUIT); + _HANDLE_SIG (SIGILL); + _HANDLE_SIG (SIGABRT); + _HANDLE_SIG (SIGFPE); + _HANDLE_SIG (SIGKILL); + _HANDLE_SIG (SIGSEGV); + _HANDLE_SIG (SIGPIPE); + _HANDLE_SIG (SIGALRM); + _HANDLE_SIG (SIGTERM); + _HANDLE_SIG (SIGUSR1); + _HANDLE_SIG (SIGUSR2); + _HANDLE_SIG (SIGCHLD); + _HANDLE_SIG (SIGCONT); + _HANDLE_SIG (SIGSTOP); + _HANDLE_SIG (SIGTSTP); + _HANDLE_SIG (SIGTTIN); + _HANDLE_SIG (SIGTTOU); + _HANDLE_SIG (SIGBUS); +#ifdef SIGPOLL + _HANDLE_SIG (SIGPOLL); +#endif + _HANDLE_SIG (SIGPROF); + _HANDLE_SIG (SIGSYS); + _HANDLE_SIG (SIGTRAP); + _HANDLE_SIG (SIGURG); + _HANDLE_SIG (SIGVTALRM); + _HANDLE_SIG (SIGXCPU); + _HANDLE_SIG (SIGXFSZ); +#undef _HANDLE_SIG + default: + break; + } + return "UNKNOWN_SIGNAL"; +} + +void +polkit_backend_common_spawn_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SpawnData *data = (SpawnData *)user_data; + data->res = (GAsyncResult*)g_object_ref (res); + g_main_loop_quit (data->loop); +} diff --git a/src/polkitbackend/polkitbackendcommon.h b/src/polkitbackend/polkitbackendcommon.h new file mode 100644 index 0000000000000000000000000000000000000000..dd700fc06011d0e3cb7040d7aee921e4c9d96de4 --- /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 + */ + +#if !defined (_POLKIT_BACKEND_COMPILATION) && !defined(_POLKIT_BACKEND_INSIDE_POLKIT_BACKEND_H) +#error "Only can be included directly, this file may disappear or change contents." +#endif + +#ifndef __POLKIT_BACKEND_COMMON_H +#define __POLKIT_BACKEND_COMMON_H + +#include "config.h" +#include +#include +#include +#include +#ifdef HAVE_NETGROUP_H +#include +#else +#include +#endif +#include +#include +#include +#include //here, all things glib via glib.h (including -> gspawn.h) + +#include +#include "polkitbackendjsauthority.h" + +#include + +#ifdef HAVE_LIBSYSTEMD +#include +#endif /* HAVE_LIBSYSTEMD */ + +#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 0000000000000000000000000000000000000000..c89dbcf5a6ccb2cde49afb803585ec407c184f1b --- /dev/null +++ b/src/polkitbackend/polkitbackendduktapeauthority.c @@ -0,0 +1,1051 @@ +/* + * Copyright (C) 2008-2012 Red Hat, Inc. + * Copyright (C) 2015 Tangent Space + * Copyright (C) 2019 Wu Xiaotian + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include + +#include "polkitbackendcommon.h" + +#include "duktape.h" + +/* Built source and not too big to worry about deduplication */ +#include "initjs.h" /* init.js */ + +/** + * SECTION:polkitbackendjsauthority + * @title: PolkitBackendJsAuthority + * @short_description: JS Authority + * @stability: Unstable + * + * An (Duktape-based) implementation of #PolkitBackendAuthority that reads and + * evaluates Javascript files and supports interaction with authentication + * agents (virtue of being based on #PolkitBackendInteractiveAuthority). + */ + +/* ---------------------------------------------------------------------------------------------------- */ + +struct _PolkitBackendJsAuthorityPrivate +{ + gchar **rules_dirs; + GFileMonitor **dir_monitors; /* NULL-terminated array of GFileMonitor instances */ + + duk_context *cx; + + pthread_t runaway_killer_thread; +}; + +enum +{ + RUNAWAY_KILLER_THREAD_EXIT_STATUS_UNSET, + RUNAWAY_KILLER_THREAD_EXIT_STATUS_SUCCESS, + RUNAWAY_KILLER_THREAD_EXIT_STATUS_FAILURE, +}; + +static gboolean execute_script_with_runaway_killer(PolkitBackendJsAuthority *authority, + const gchar *filename); + +/* ---------------------------------------------------------------------------------------------------- */ + +G_DEFINE_TYPE (PolkitBackendJsAuthority, polkit_backend_js_authority, POLKIT_BACKEND_TYPE_INTERACTIVE_AUTHORITY); + +/* ---------------------------------------------------------------------------------------------------- */ + +static duk_ret_t js_polkit_log (duk_context *cx); +static duk_ret_t js_polkit_spawn (duk_context *cx); +static duk_ret_t js_polkit_user_is_in_netgroup (duk_context *cx); + +static const duk_function_list_entry js_polkit_functions[] = +{ + { "log", js_polkit_log, 1 }, + { "spawn", js_polkit_spawn, 1 }, + { "_userIsInNetGroup", js_polkit_user_is_in_netgroup, 2 }, + { NULL, NULL, 0 }, +}; + +static void report_error (void *udata, + const char *msg) +{ + PolkitBackendJsAuthority *authority = udata; + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "fatal Duktape JS backend error: %s", + (msg ? msg : "no message")); +} + +static void +polkit_backend_js_authority_init (PolkitBackendJsAuthority *authority) +{ + authority->priv = G_TYPE_INSTANCE_GET_PRIVATE (authority, + POLKIT_BACKEND_TYPE_JS_AUTHORITY, + PolkitBackendJsAuthorityPrivate); +} + +static void +load_scripts (PolkitBackendJsAuthority *authority) +{ + GList *files = NULL; + GList *l; + guint num_scripts = 0; + GError *error = NULL; + guint n; + + files = NULL; + + for (n = 0; authority->priv->rules_dirs != NULL && authority->priv->rules_dirs[n] != NULL; n++) + { + const gchar *dir_name = authority->priv->rules_dirs[n]; + GDir *dir = NULL; + + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Loading rules from directory %s", + dir_name); + + dir = g_dir_open (dir_name, + 0, + &error); + if (dir == NULL) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error opening rules directory: %s (%s, %d)", + error->message, g_quark_to_string (error->domain), error->code); + g_clear_error (&error); + } + else + { + const gchar *name; + while ((name = g_dir_read_name (dir)) != NULL) + { + if (g_str_has_suffix (name, ".rules")) + files = g_list_prepend (files, g_strdup_printf ("%s/%s", dir_name, name)); + } + g_dir_close (dir); + } + } + + files = g_list_sort (files, (GCompareFunc) polkit_backend_common_rules_file_name_cmp); + + for (l = files; l != NULL; l = l->next) + { + const gchar *filename = (gchar *)l->data; + + if (!execute_script_with_runaway_killer(authority, filename)) + continue; + num_scripts++; + } + + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Finished loading, compiling and executing %d rules", + num_scripts); + g_list_free_full (files, g_free); +} + +void +polkit_backend_common_reload_scripts (PolkitBackendJsAuthority *authority) +{ + duk_context *cx = authority->priv->cx; + + duk_set_top (cx, 0); + if (!duk_get_global_string (cx, "polkit")) { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Error deleting old rules, not loading new ones"); + return; + } + duk_push_string (cx, "_deleteRules"); + + duk_call_prop (cx, 0, 0); + + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), + "Collecting garbage unconditionally..."); + + load_scripts (authority); + + /* Let applications know we have new rules... */ + g_signal_emit_by_name (authority, "changed"); +} + +static void +setup_file_monitors (PolkitBackendJsAuthority *authority) +{ + guint n; + GPtrArray *p; + + p = g_ptr_array_new (); + for (n = 0; authority->priv->rules_dirs != NULL && authority->priv->rules_dirs[n] != NULL; n++) + { + GFile *file; + GError *error; + GFileMonitor *monitor; + + file = g_file_new_for_path (authority->priv->rules_dirs[n]); + error = NULL; + monitor = g_file_monitor_directory (file, + G_FILE_MONITOR_NONE, + NULL, + &error); + g_object_unref (file); + if (monitor == NULL) + { + g_warning ("Error monitoring directory %s: %s", + authority->priv->rules_dirs[n], + error->message); + g_clear_error (&error); + } + else + { + g_signal_connect (monitor, + "changed", + G_CALLBACK (polkit_backend_common_on_dir_monitor_changed), + authority); + g_ptr_array_add (p, monitor); + } + } + g_ptr_array_add (p, NULL); + authority->priv->dir_monitors = (GFileMonitor**) g_ptr_array_free (p, FALSE); +} + +void +polkit_backend_common_js_authority_constructed (GObject *object) +{ + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); + duk_context *cx; + + cx = duk_create_heap (NULL, NULL, NULL, authority, report_error); + if (cx == NULL) + goto fail; + + authority->priv->cx = cx; + + duk_push_global_object (cx); + duk_push_object (cx); + duk_put_function_list (cx, -1, js_polkit_functions); + duk_put_prop_string (cx, -2, "polkit"); + + /* load polkit objects/functions into JS context (e.g. addRule(), + * _deleteRules(), _runRules() et al) + */ + duk_eval_string (cx, init_js); + + if (authority->priv->rules_dirs == NULL) + { + authority->priv->rules_dirs = g_new0 (gchar *, 3); + authority->priv->rules_dirs[0] = g_strdup (PACKAGE_SYSCONF_DIR "/polkit-1/rules.d"); + authority->priv->rules_dirs[1] = g_strdup (PACKAGE_DATA_DIR "/polkit-1/rules.d"); + } + + setup_file_monitors (authority); + load_scripts (authority); + + G_OBJECT_CLASS (polkit_backend_js_authority_parent_class)->constructed (object); + return; + + fail: + g_critical ("Error initializing JavaScript environment"); + g_assert_not_reached (); +} + +void +polkit_backend_common_js_authority_finalize (GObject *object) +{ + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); + guint n; + + for (n = 0; authority->priv->dir_monitors != NULL && authority->priv->dir_monitors[n] != NULL; n++) + { + GFileMonitor *monitor = authority->priv->dir_monitors[n]; + g_signal_handlers_disconnect_by_func (monitor, + G_CALLBACK (polkit_backend_common_on_dir_monitor_changed), + authority); + g_object_unref (monitor); + } + g_free (authority->priv->dir_monitors); + g_strfreev (authority->priv->rules_dirs); + + duk_destroy_heap (authority->priv->cx); + + G_OBJECT_CLASS (polkit_backend_js_authority_parent_class)->finalize (object); +} + +void +polkit_backend_common_js_authority_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); + + switch (property_id) + { + case PROP_RULES_DIRS: + g_assert (authority->priv->rules_dirs == NULL); + authority->priv->rules_dirs = (gchar **) g_value_dup_boxed (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +polkit_backend_js_authority_class_init (PolkitBackendJsAuthorityClass *klass) +{ + polkit_backend_common_js_authority_class_init_common (klass); + g_type_class_add_private (klass, sizeof (PolkitBackendJsAuthorityPrivate)); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +set_property_str (duk_context *cx, + const gchar *name, + const gchar *value) +{ + duk_push_string (cx, value); + duk_put_prop_string (cx, -2, name); +} + +static void +set_property_strv (duk_context *cx, + const gchar *name, + GPtrArray *value) +{ + guint n; + duk_push_array (cx); + for (n = 0; n < value->len; n++) + { + duk_push_string (cx, g_ptr_array_index (value, n)); + duk_put_prop_index (cx, -2, n); + } + duk_put_prop_string (cx, -2, name); +} + +static void +set_property_int32 (duk_context *cx, + const gchar *name, + gint32 value) +{ + duk_push_int (cx, value); + duk_put_prop_string (cx, -2, name); +} + +static void +set_property_bool (duk_context *cx, + const char *name, + gboolean value) +{ + duk_push_boolean (cx, value); + duk_put_prop_string (cx, -2, name); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +push_subject (duk_context *cx, + PolkitSubject *subject, + PolkitIdentity *user_for_subject, + gboolean subject_is_local, + gboolean subject_is_active, + GError **error) +{ + gboolean ret = FALSE; + pid_t pid; + uid_t uid; + gchar *user_name = NULL; + GPtrArray *groups = NULL; + struct passwd *passwd; + char *seat_str = NULL; + char *session_str = NULL; + + if (!duk_get_global_string (cx, "Subject")) { + return FALSE; + } + + duk_new (cx, 0); + + if (POLKIT_IS_UNIX_PROCESS (subject)) + { + pid = polkit_unix_process_get_pid (POLKIT_UNIX_PROCESS (subject)); + } + else if (POLKIT_IS_SYSTEM_BUS_NAME (subject)) + { + PolkitSubject *process; + process = polkit_system_bus_name_get_process_sync (POLKIT_SYSTEM_BUS_NAME (subject), NULL, error); + if (process == NULL) + goto out; + pid = polkit_unix_process_get_pid (POLKIT_UNIX_PROCESS (process)); + g_object_unref (process); + } + else + { + g_assert_not_reached (); + } + +#ifdef HAVE_LIBSYSTEMD + if (sd_pid_get_session (pid, &session_str) == 0) + { + if (sd_session_get_seat (session_str, &seat_str) == 0) + { + /* do nothing */ + } + } +#endif /* HAVE_LIBSYSTEMD */ + + g_assert (POLKIT_IS_UNIX_USER (user_for_subject)); + uid = polkit_unix_user_get_uid (POLKIT_UNIX_USER (user_for_subject)); + + groups = g_ptr_array_new_with_free_func (g_free); + + passwd = getpwuid (uid); + if (passwd == NULL) + { + user_name = g_strdup_printf ("%d", (gint) uid); + g_warning ("Error looking up info for uid %d: %m", (gint) uid); + } + else + { + gid_t gids[512]; + int num_gids = 512; + + user_name = g_strdup (passwd->pw_name); + + if (getgrouplist (passwd->pw_name, + passwd->pw_gid, + gids, + &num_gids) < 0) + { + g_warning ("Error looking up groups for uid %d: %m", (gint) uid); + } + else + { + gint n; + for (n = 0; n < num_gids; n++) + { + struct group *group; + group = getgrgid (gids[n]); + if (group == NULL) + { + g_ptr_array_add (groups, g_strdup_printf ("%d", (gint) gids[n])); + } + else + { + g_ptr_array_add (groups, g_strdup (group->gr_name)); + } + } + } + } + + set_property_int32 (cx, "pid", pid); + set_property_str (cx, "user", user_name); + set_property_strv (cx, "groups", groups); + set_property_str (cx, "seat", seat_str); + set_property_str (cx, "session", session_str); + set_property_bool (cx, "local", subject_is_local); + set_property_bool (cx, "active", subject_is_active); + + ret = TRUE; + + out: + free (session_str); + free (seat_str); + g_free (user_name); + if (groups != NULL) + g_ptr_array_unref (groups); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +push_action_and_details (duk_context *cx, + const gchar *action_id, + PolkitDetails *details, + GError **error) +{ + gchar **keys; + guint n; + + if (!duk_get_global_string (cx, "Action")) { + return FALSE; + } + + duk_new (cx, 0); + + set_property_str (cx, "id", action_id); + + keys = polkit_details_get_keys (details); + for (n = 0; keys != NULL && keys[n] != NULL; n++) + { + gchar *key; + const gchar *value; + key = g_strdup_printf ("_detail_%s", keys[n]); + value = polkit_details_lookup (details, keys[n]); + set_property_str (cx, key, value); + g_free (key); + } + g_strfreev (keys); + + return TRUE; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct { + PolkitBackendJsAuthority *authority; + const gchar *filename; + pthread_cond_t cond; + pthread_mutex_t mutex; + gint ret; +} RunawayKillerCtx; + +static gpointer +runaway_killer_thread_execute_js (gpointer user_data) +{ + RunawayKillerCtx *ctx = user_data; + duk_context *cx = ctx->authority->priv->cx; + + int oldtype, pthread_err; + + if ((pthread_err = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype))) { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (ctx->authority), + "Error setting thread cancel type: %s", + strerror(pthread_err)); + goto err; + } + + GFile *file = g_file_new_for_path(ctx->filename); + char *contents; + gsize len; + + if (!g_file_load_contents(file, NULL, &contents, &len, NULL, NULL)) { + polkit_backend_authority_log(POLKIT_BACKEND_AUTHORITY(ctx->authority), + "Error loading script %s", ctx->filename); + g_object_unref(file); + goto err; + } + + g_object_unref(file); + + /* evaluate the script, trying to print context in any syntax errors + found */ + if (duk_peval_lstring(cx, contents, len) != 0) + { + polkit_backend_authority_log(POLKIT_BACKEND_AUTHORITY(ctx->authority), + "Error compiling script %s: %s", ctx->filename, + duk_safe_to_string(cx, -1)); + duk_pop(cx); + goto free_err; + } + g_free(contents); + + ctx->ret = RUNAWAY_KILLER_THREAD_EXIT_STATUS_SUCCESS; + goto end; + +free_err: + g_free(contents); +err: + ctx->ret = RUNAWAY_KILLER_THREAD_EXIT_STATUS_FAILURE; +end: + if ((pthread_err = pthread_cond_signal(&ctx->cond))) { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (ctx->authority), + "Error signaling on condition variable: %s", + strerror(pthread_err)); + ctx->ret = RUNAWAY_KILLER_THREAD_EXIT_STATUS_FAILURE; + } + return NULL; +} + +static gpointer +runaway_killer_thread_call_js (gpointer user_data) +{ + RunawayKillerCtx *ctx = user_data; + duk_context *cx = ctx->authority->priv->cx; + int oldtype, pthread_err; + + if ((pthread_err = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype))) { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (ctx->authority), + "Error setting thread cancel type: %s", + strerror(pthread_err)); + goto err; + } + + if (duk_pcall_prop (cx, 0, 2) != DUK_EXEC_SUCCESS) + { + polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (ctx->authority), + "Error evaluating admin rules: ", + 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 ca171083d835ede41809e7528f49f4428d54dc43..11e91c045da1e1eee607b3f37421a658c08efa11 100644 --- a/src/polkitbackend/polkitbackendjsauthority.cpp +++ b/src/polkitbackend/polkitbackendjsauthority.cpp @@ -19,29 +19,7 @@ * Author: David Zeuthen */ -#include "config.h" -#include -#include -#include -#include -#ifdef HAVE_NETGROUP_H -#include -#else -#include -#endif -#include -#include -#include -#include - -#include -#include "polkitbackendjsauthority.h" - -#include - -#ifdef HAVE_LIBSYSTEMD -#include -#endif /* HAVE_LIBSYSTEMD */ +#include "polkitbackendcommon.h" #include #include @@ -52,6 +30,7 @@ #include #include +/* Built source and not too big to worry about deduplication */ #include "initjs.h" /* init.js */ #ifdef JSGC_USE_EXACT_ROOTING @@ -67,10 +46,9 @@ * @short_description: JS Authority * @stability: Unstable * - * An implementation of #PolkitBackendAuthority that reads and - * evalates Javascript files and supports interaction with - * authentication agents (virtue of being based on - * #PolkitBackendInteractiveAuthority). + * An (SpiderMonkey-based) implementation of #PolkitBackendAuthority that reads + * and evaluates Javascript files and supports interaction with authentication + * agents (virtue of being based on #PolkitBackendInteractiveAuthority). */ /* ---------------------------------------------------------------------------------------------------- */ @@ -100,57 +78,11 @@ static bool execute_script_with_runaway_killer (PolkitBackendJsAuthority *author JS::HandleScript script, JS::MutableHandleValue rval); -static void utils_spawn (const gchar *const *argv, - guint timeout_seconds, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); - -gboolean utils_spawn_finish (GAsyncResult *res, - gint *out_exit_status, - gchar **out_standard_output, - gchar **out_standard_error, - GError **error); - -static void on_dir_monitor_changed (GFileMonitor *monitor, - GFile *file, - GFile *other_file, - GFileMonitorEvent event_type, - gpointer user_data); - -/* ---------------------------------------------------------------------------------------------------- */ - -enum -{ - PROP_0, - PROP_RULES_DIRS, -}; - /* ---------------------------------------------------------------------------------------------------- */ static gpointer runaway_killer_thread_func (gpointer user_data); static void runaway_killer_terminate (PolkitBackendJsAuthority *authority); -static GList *polkit_backend_js_authority_get_admin_auth_identities (PolkitBackendInteractiveAuthority *authority, - PolkitSubject *caller, - PolkitSubject *subject, - PolkitIdentity *user_for_subject, - gboolean subject_is_local, - gboolean subject_is_active, - const gchar *action_id, - PolkitDetails *details); - -static PolkitImplicitAuthorization polkit_backend_js_authority_check_authorization_sync ( - PolkitBackendInteractiveAuthority *authority, - PolkitSubject *caller, - PolkitSubject *subject, - PolkitIdentity *user_for_subject, - gboolean subject_is_local, - gboolean subject_is_active, - const gchar *action_id, - PolkitDetails *details, - PolkitImplicitAuthorization implicit); - G_DEFINE_TYPE (PolkitBackendJsAuthority, polkit_backend_js_authority, POLKIT_BACKEND_TYPE_INTERACTIVE_AUTHORITY); /* ---------------------------------------------------------------------------------------------------- */ @@ -229,33 +161,6 @@ polkit_backend_js_authority_init (PolkitBackendJsAuthority *authority) PolkitBackendJsAuthorityPrivate); } -static gint -rules_file_name_cmp (const gchar *a, - const gchar *b) -{ - gint ret; - const gchar *a_base; - const gchar *b_base; - - a_base = strrchr (a, '/'); - b_base = strrchr (b, '/'); - - g_assert (a_base != NULL); - g_assert (b_base != NULL); - a_base += 1; - b_base += 1; - - ret = g_strcmp0 (a_base, b_base); - if (ret == 0) - { - /* /etc wins over /usr */ - ret = g_strcmp0 (a, b); - g_assert (ret != 0); - } - - return ret; -} - /* authority->priv->cx must be within a request */ static void load_scripts (PolkitBackendJsAuthority *authority) @@ -299,7 +204,7 @@ load_scripts (PolkitBackendJsAuthority *authority) } } - files = g_list_sort (files, (GCompareFunc) rules_file_name_cmp); + files = g_list_sort (files, (GCompareFunc) polkit_backend_common_rules_file_name_cmp); for (l = files; l != NULL; l = l->next) { @@ -365,8 +270,8 @@ load_scripts (PolkitBackendJsAuthority *authority) g_list_free_full (files, g_free); } -static void -reload_scripts (PolkitBackendJsAuthority *authority) +void +polkit_backend_common_reload_scripts (PolkitBackendJsAuthority *authority) { JS::RootedValueArray<1> args(authority->priv->cx); JS::RootedValue rval(authority->priv->cx); @@ -395,42 +300,6 @@ reload_scripts (PolkitBackendJsAuthority *authority) g_signal_emit_by_name (authority, "changed"); } -static void -on_dir_monitor_changed (GFileMonitor *monitor, - GFile *file, - GFile *other_file, - GFileMonitorEvent event_type, - gpointer user_data) -{ - PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (user_data); - - /* TODO: maybe rate-limit so storms of events are collapsed into one with a 500ms resolution? - * Because when editing a file with emacs we get 4-8 events.. - */ - - if (file != NULL) - { - gchar *name; - - name = g_file_get_basename (file); - - /* g_print ("event_type=%d file=%p name=%s\n", event_type, file, name); */ - if (!g_str_has_prefix (name, ".") && - !g_str_has_prefix (name, "#") && - g_str_has_suffix (name, ".rules") && - (event_type == G_FILE_MONITOR_EVENT_CREATED || - event_type == G_FILE_MONITOR_EVENT_DELETED || - event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)) - { - polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), - "Reloading rules"); - reload_scripts (authority); - } - g_free (name); - } -} - - static void setup_file_monitors (PolkitBackendJsAuthority *authority) { @@ -462,7 +331,7 @@ setup_file_monitors (PolkitBackendJsAuthority *authority) { g_signal_connect (monitor, "changed", - G_CALLBACK (on_dir_monitor_changed), + G_CALLBACK (polkit_backend_common_on_dir_monitor_changed), authority); g_ptr_array_add (p, monitor); } @@ -471,8 +340,8 @@ setup_file_monitors (PolkitBackendJsAuthority *authority) authority->priv->dir_monitors = (GFileMonitor**) g_ptr_array_free (p, FALSE); } -static void -polkit_backend_js_authority_constructed (GObject *object) +void +polkit_backend_common_js_authority_constructed (GObject *object) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); @@ -561,8 +430,8 @@ polkit_backend_js_authority_constructed (GObject *object) g_assert_not_reached (); } -static void -polkit_backend_js_authority_finalize (GObject *object) +void +polkit_backend_common_js_authority_finalize (GObject *object) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); guint n; @@ -577,7 +446,7 @@ polkit_backend_js_authority_finalize (GObject *object) { GFileMonitor *monitor = authority->priv->dir_monitors[n]; g_signal_handlers_disconnect_by_func (monitor, - (gpointer*)G_CALLBACK (on_dir_monitor_changed), + (gpointer*)G_CALLBACK (polkit_backend_common_on_dir_monitor_changed), authority); g_object_unref (monitor); } @@ -594,11 +463,11 @@ polkit_backend_js_authority_finalize (GObject *object) G_OBJECT_CLASS (polkit_backend_js_authority_parent_class)->finalize (object); } -static void -polkit_backend_js_authority_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec) +void +polkit_backend_common_js_authority_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (object); @@ -615,57 +484,12 @@ polkit_backend_js_authority_set_property (GObject *object, } } -static const gchar * -polkit_backend_js_authority_get_name (PolkitBackendAuthority *authority) -{ - return "js"; -} - -static const gchar * -polkit_backend_js_authority_get_version (PolkitBackendAuthority *authority) -{ - return PACKAGE_VERSION; -} - -static PolkitAuthorityFeatures -polkit_backend_js_authority_get_features (PolkitBackendAuthority *authority) -{ - return POLKIT_AUTHORITY_FEATURES_TEMPORARY_AUTHORIZATION; -} - static void polkit_backend_js_authority_class_init (PolkitBackendJsAuthorityClass *klass) { - GObjectClass *gobject_class; - PolkitBackendAuthorityClass *authority_class; - PolkitBackendInteractiveAuthorityClass *interactive_authority_class; - - - gobject_class = G_OBJECT_CLASS (klass); - gobject_class->finalize = polkit_backend_js_authority_finalize; - gobject_class->set_property = polkit_backend_js_authority_set_property; - gobject_class->constructed = polkit_backend_js_authority_constructed; - - authority_class = POLKIT_BACKEND_AUTHORITY_CLASS (klass); - authority_class->get_name = polkit_backend_js_authority_get_name; - authority_class->get_version = polkit_backend_js_authority_get_version; - authority_class->get_features = polkit_backend_js_authority_get_features; - - interactive_authority_class = POLKIT_BACKEND_INTERACTIVE_AUTHORITY_CLASS (klass); - interactive_authority_class->get_admin_identities = polkit_backend_js_authority_get_admin_auth_identities; - interactive_authority_class->check_authorization_sync = polkit_backend_js_authority_check_authorization_sync; - - g_object_class_install_property (gobject_class, - PROP_RULES_DIRS, - g_param_spec_boxed ("rules-dirs", - NULL, - NULL, - G_TYPE_STRV, - GParamFlags(G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE))); - + polkit_backend_common_js_authority_class_init_common (klass); g_type_class_add_private (klass, sizeof (PolkitBackendJsAuthorityPrivate)); - JS_Init (); } @@ -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 98bf062a08cb11fddb7df95d0bcdec1b1ac3587d..e346b5dd3f08886cd93ceca3c9c81f7802aa4f18 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 f97e0e0f24ad776aaece0acf581e9538e704adbd..2103b174d58dfcab6bb97825d666dfb482e3da8b 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, }, {