mirror of
https://gitlab.freedesktop.org/pipewire/wireplumber.git
synced 2025-12-20 05:20:05 +01:00
tools: split wireplumber script execution mode to a separate wpexec tool
This is mainly for security reasons. This way, we can limit the permissions of wpexec without limiting wireplumber, for instance with pipewire's default module-access, which limits apps based on their executable name
This commit is contained in:
parent
e654f7709f
commit
1ba99739c2
5 changed files with 268 additions and 48 deletions
49
src/main.c
49
src/main.c
|
|
@ -22,36 +22,11 @@ enum WpExitCode
|
|||
};
|
||||
|
||||
static gchar * config_file = NULL;
|
||||
static gchar * exec_script = NULL;
|
||||
static GVariantBuilder exec_args_b =
|
||||
G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
|
||||
|
||||
static gboolean
|
||||
parse_exec_script_arg (const gchar *option_name, const gchar *value,
|
||||
gpointer data, GError **error)
|
||||
{
|
||||
g_auto(GStrv) tokens = g_strsplit (value, "=", 2);
|
||||
if (!tokens[0] || *g_strstrip (tokens[0]) == '\0') {
|
||||
g_set_error (error, WP_DOMAIN_DAEMON, WP_CODE_INVALID_ARGUMENT,
|
||||
"invalid script argument '%s'; must be in key=value format", value);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_variant_builder_add (&exec_args_b, "{sv}", tokens[0], tokens[1] ?
|
||||
g_variant_new_string (g_strstrip (tokens[1])) :
|
||||
g_variant_new_boolean (TRUE));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GOptionEntry entries[] =
|
||||
{
|
||||
{ "config-file", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &config_file,
|
||||
"The configuration file to load components from", NULL },
|
||||
{ "execute", 'e', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &exec_script,
|
||||
"Runs WirePlumber in interactive script execution mode, "
|
||||
"executing the given script", NULL },
|
||||
{ G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK,
|
||||
parse_exec_script_arg, NULL, NULL },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
|
|
@ -67,7 +42,6 @@ struct _WpInitTransition
|
|||
enum {
|
||||
STEP_LOAD_MODULE = WP_TRANSITION_STEP_CUSTOM_START,
|
||||
STEP_LOAD_CONFIG,
|
||||
STEP_LOAD_SCRIPT,
|
||||
STEP_CONNECT,
|
||||
STEP_ACTIVATE_PLUGINS,
|
||||
STEP_ACTIVATE_SCRIPTS,
|
||||
|
|
@ -87,16 +61,11 @@ wp_init_transition_get_next_step (WpTransition * transition, guint step)
|
|||
{
|
||||
switch (step) {
|
||||
case WP_TRANSITION_STEP_NONE: return STEP_LOAD_MODULE;
|
||||
case STEP_LOAD_MODULE: return STEP_LOAD_CONFIG;
|
||||
case STEP_LOAD_CONFIG: return STEP_CONNECT;
|
||||
case STEP_CONNECT: return STEP_ACTIVATE_PLUGINS;
|
||||
case STEP_ACTIVATE_SCRIPTS: return WP_TRANSITION_STEP_NONE;
|
||||
|
||||
case STEP_LOAD_MODULE:
|
||||
return exec_script ? STEP_LOAD_SCRIPT : STEP_LOAD_CONFIG;
|
||||
|
||||
case STEP_LOAD_CONFIG:
|
||||
case STEP_LOAD_SCRIPT:
|
||||
return STEP_CONNECT;
|
||||
|
||||
case STEP_ACTIVATE_PLUGINS: {
|
||||
WpInitTransition *self = WP_INIT_TRANSITION (transition);
|
||||
if (self->pending_plugins == 0)
|
||||
|
|
@ -160,16 +129,6 @@ wp_init_transition_execute_step (WpTransition * transition, guint step)
|
|||
break;
|
||||
}
|
||||
|
||||
case STEP_LOAD_SCRIPT: {
|
||||
GVariant *args = g_variant_builder_end (&exec_args_b);
|
||||
if (!wp_core_load_component (core, exec_script, "script/lua", args, &error)) {
|
||||
wp_transition_return_error (transition, error);
|
||||
return;
|
||||
}
|
||||
wp_transition_advance (transition);
|
||||
break;
|
||||
}
|
||||
|
||||
case STEP_CONNECT:
|
||||
g_signal_connect_object (core, "connected",
|
||||
G_CALLBACK (wp_transition_advance), transition, G_CONNECT_SWAPPED);
|
||||
|
|
@ -200,7 +159,7 @@ wp_init_transition_execute_step (WpTransition * transition, guint step)
|
|||
|
||||
wp_info_object (self, "Executing scripts...");
|
||||
|
||||
WpPlugin *p = wp_plugin_find (core, "lua-scripting");
|
||||
g_autoptr (WpPlugin) p = wp_plugin_find (core, "lua-scripting");
|
||||
wp_object_activate (WP_OBJECT (p), WP_PLUGIN_FEATURE_ENABLED, NULL,
|
||||
(GAsyncReadyCallback) on_plugin_activated, self);
|
||||
break;
|
||||
|
|
@ -324,7 +283,7 @@ main (gint argc, gchar **argv)
|
|||
d.loop = g_main_loop_new (NULL, FALSE);
|
||||
d.core = wp_core_new (NULL, wp_properties_new (
|
||||
PW_KEY_APP_NAME, "WirePlumber",
|
||||
"wireplumber.interactive", exec_script ? "true" : "false",
|
||||
"wireplumber.interactive", "false",
|
||||
NULL));
|
||||
g_signal_connect (d.core, "disconnected", G_CALLBACK (on_disconnected), &d);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,3 +8,14 @@ executable('wpctl',
|
|||
install: true,
|
||||
dependencies : [gobject_dep, gio_dep, wp_dep, pipewire_dep],
|
||||
)
|
||||
|
||||
executable('wpexec',
|
||||
'wpexec.c',
|
||||
c_args : [
|
||||
'-D_GNU_SOURCE',
|
||||
'-DG_LOG_USE_STRUCTURED',
|
||||
'-DG_LOG_DOMAIN="wpexec"',
|
||||
],
|
||||
install: true,
|
||||
dependencies : [gobject_dep, gio_dep, wp_dep, pipewire_dep],
|
||||
)
|
||||
|
|
|
|||
250
src/tools/wpexec.c
Normal file
250
src/tools/wpexec.c
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2019-2021 Collabora Ltd.
|
||||
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <wp/wp.h>
|
||||
#include <glib-unix.h>
|
||||
#include <pipewire/keys.h>
|
||||
|
||||
#define WP_DOMAIN_DAEMON (wp_domain_daemon_quark ())
|
||||
static G_DEFINE_QUARK (wireplumber-daemon, wp_domain_daemon);
|
||||
|
||||
enum WpExitCode
|
||||
{
|
||||
WP_CODE_OK = 0,
|
||||
WP_CODE_OPERATION_FAILED,
|
||||
WP_CODE_INVALID_ARGUMENT,
|
||||
};
|
||||
|
||||
static gchar * exec_script = NULL;
|
||||
static GVariantBuilder exec_args_b =
|
||||
G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
|
||||
|
||||
static gboolean
|
||||
parse_exec_script_arg (const gchar *option_name, const gchar *value,
|
||||
gpointer data, GError **error)
|
||||
{
|
||||
/* the first argument is the script */
|
||||
if (!exec_script) {
|
||||
exec_script = g_strdup (value);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
g_auto(GStrv) tokens = g_strsplit (value, "=", 2);
|
||||
if (!tokens[0] || *g_strstrip (tokens[0]) == '\0') {
|
||||
g_set_error (error, WP_DOMAIN_DAEMON, WP_CODE_INVALID_ARGUMENT,
|
||||
"invalid script argument '%s'; must be in key=value format", value);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_variant_builder_add (&exec_args_b, "{sv}", tokens[0], tokens[1] ?
|
||||
g_variant_new_string (g_strstrip (tokens[1])) :
|
||||
g_variant_new_boolean (TRUE));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GOptionEntry entries[] =
|
||||
{
|
||||
{ G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK,
|
||||
parse_exec_script_arg, NULL, NULL },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
/*** WpInitTransition ***/
|
||||
|
||||
struct _WpInitTransition
|
||||
{
|
||||
WpTransition parent;
|
||||
};
|
||||
|
||||
enum {
|
||||
STEP_LOAD_MODULE = WP_TRANSITION_STEP_CUSTOM_START,
|
||||
STEP_LOAD_SCRIPT,
|
||||
STEP_CONNECT,
|
||||
STEP_ACTIVATE_SCRIPT,
|
||||
};
|
||||
|
||||
G_DECLARE_FINAL_TYPE (WpInitTransition, wp_init_transition,
|
||||
WP, INIT_TRANSITION, WpTransition)
|
||||
G_DEFINE_TYPE (WpInitTransition, wp_init_transition, WP_TYPE_TRANSITION)
|
||||
|
||||
static void
|
||||
wp_init_transition_init (WpInitTransition * self)
|
||||
{
|
||||
}
|
||||
|
||||
static guint
|
||||
wp_init_transition_get_next_step (WpTransition * transition, guint step)
|
||||
{
|
||||
switch (step) {
|
||||
case WP_TRANSITION_STEP_NONE: return STEP_LOAD_MODULE;
|
||||
case STEP_LOAD_MODULE: return STEP_LOAD_SCRIPT;
|
||||
case STEP_LOAD_SCRIPT: return STEP_CONNECT;
|
||||
case STEP_CONNECT: return STEP_ACTIVATE_SCRIPT;
|
||||
case STEP_ACTIVATE_SCRIPT: return WP_TRANSITION_STEP_NONE;
|
||||
default:
|
||||
g_return_val_if_reached (WP_TRANSITION_STEP_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
on_plugin_activated (WpObject * p, GAsyncResult * res, WpInitTransition *self)
|
||||
{
|
||||
GError *error = NULL;
|
||||
if (!wp_object_activate_finish (p, res, &error)) {
|
||||
wp_transition_return_error (WP_TRANSITION (self), error);
|
||||
return;
|
||||
}
|
||||
wp_transition_advance (WP_TRANSITION (self));
|
||||
}
|
||||
|
||||
static void
|
||||
wp_init_transition_execute_step (WpTransition * transition, guint step)
|
||||
{
|
||||
WpInitTransition *self = WP_INIT_TRANSITION (transition);
|
||||
WpCore *core = wp_transition_get_source_object (transition);
|
||||
GError *error = NULL;
|
||||
|
||||
switch (step) {
|
||||
case STEP_LOAD_MODULE:
|
||||
if (!wp_core_load_component (core, "libwireplumber-module-lua-scripting",
|
||||
"module", NULL, &error)) {
|
||||
wp_transition_return_error (transition, error);
|
||||
return;
|
||||
}
|
||||
wp_transition_advance (transition);
|
||||
break;
|
||||
|
||||
case STEP_LOAD_SCRIPT: {
|
||||
GVariant *args = g_variant_builder_end (&exec_args_b);
|
||||
if (!wp_core_load_component (core, exec_script, "script/lua", args, &error)) {
|
||||
wp_transition_return_error (transition, error);
|
||||
return;
|
||||
}
|
||||
wp_transition_advance (transition);
|
||||
break;
|
||||
}
|
||||
|
||||
case STEP_CONNECT:
|
||||
g_signal_connect_object (core, "connected",
|
||||
G_CALLBACK (wp_transition_advance), transition, G_CONNECT_SWAPPED);
|
||||
|
||||
if (!wp_core_connect (core)) {
|
||||
wp_transition_return_error (transition, g_error_new (WP_DOMAIN_DAEMON,
|
||||
WP_CODE_OPERATION_FAILED, "Failed to connect to PipeWire"));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case STEP_ACTIVATE_SCRIPT: {
|
||||
g_autoptr (WpPlugin) p = wp_plugin_find (core, "lua-scripting");
|
||||
wp_object_activate (WP_OBJECT (p), WP_PLUGIN_FEATURE_ENABLED, NULL,
|
||||
(GAsyncReadyCallback) on_plugin_activated, self);
|
||||
break;
|
||||
}
|
||||
|
||||
case WP_TRANSITION_STEP_ERROR:
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
wp_init_transition_class_init (WpInitTransitionClass * klass)
|
||||
{
|
||||
WpTransitionClass * transition_class = (WpTransitionClass *) klass;
|
||||
|
||||
transition_class->get_next_step = wp_init_transition_get_next_step;
|
||||
transition_class->execute_step = wp_init_transition_execute_step;
|
||||
}
|
||||
|
||||
/*** WpExec ***/
|
||||
|
||||
typedef struct
|
||||
{
|
||||
WpCore *core;
|
||||
GMainLoop *loop;
|
||||
gint exit_code;
|
||||
} WpExec;
|
||||
|
||||
static void
|
||||
wpexec_clear (WpExec * self)
|
||||
{
|
||||
g_clear_pointer (&self->loop, g_main_loop_unref);
|
||||
g_clear_object (&self->core);
|
||||
}
|
||||
|
||||
G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (WpExec, wpexec_clear)
|
||||
|
||||
static gboolean
|
||||
signal_handler (gpointer data)
|
||||
{
|
||||
WpExec *d = data;
|
||||
g_main_loop_quit (d->loop);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
init_start (WpTransition * transition)
|
||||
{
|
||||
wp_transition_advance (transition);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
init_done (WpCore * core, GAsyncResult * res, WpExec * d)
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
if (!wp_transition_finish (res, &error)) {
|
||||
wp_message ("%s", error->message);
|
||||
d->exit_code = WP_CODE_OPERATION_FAILED;
|
||||
g_main_loop_quit (d->loop);
|
||||
}
|
||||
}
|
||||
|
||||
gint
|
||||
main (gint argc, gchar **argv)
|
||||
{
|
||||
g_auto (WpExec) d = {0};
|
||||
g_autoptr (GOptionContext) context = NULL;
|
||||
g_autoptr (GError) error = NULL;
|
||||
|
||||
wp_init (WP_INIT_ALL);
|
||||
|
||||
context = g_option_context_new ("- WirePlumber script interpreter");
|
||||
g_option_context_add_main_entries (context, entries, NULL);
|
||||
if (!g_option_context_parse (context, &argc, &argv, &error)) {
|
||||
wp_message ("%s", error->message);
|
||||
return WP_CODE_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
/* init wireplumber core */
|
||||
d.loop = g_main_loop_new (NULL, FALSE);
|
||||
d.core = wp_core_new (NULL, wp_properties_new (
|
||||
PW_KEY_APP_NAME, "wpexec",
|
||||
"wireplumber.interactive", "true",
|
||||
NULL));
|
||||
g_signal_connect_swapped (d.core, "disconnected",
|
||||
G_CALLBACK (g_main_loop_quit), d.loop);
|
||||
|
||||
/* watch for exit signals */
|
||||
g_unix_signal_add (SIGINT, signal_handler, &d);
|
||||
g_unix_signal_add (SIGTERM, signal_handler, &d);
|
||||
g_unix_signal_add (SIGHUP, signal_handler, &d);
|
||||
|
||||
/* initialization transition */
|
||||
g_idle_add ((GSourceFunc) init_start,
|
||||
wp_transition_new (wp_init_transition_get_type (), d.core,
|
||||
NULL, (GAsyncReadyCallback) init_done, &d));
|
||||
|
||||
/* run */
|
||||
g_main_loop_run (d.loop);
|
||||
wp_core_disconnect (d.core);
|
||||
return d.exit_code;
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/wireplumber -e
|
||||
#!/usr/bin/wpexec
|
||||
--
|
||||
-- WirePlumber
|
||||
--
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/wireplumber -e
|
||||
#!/usr/bin/wpexec
|
||||
--
|
||||
-- WirePlumber
|
||||
--
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
-- This is an example of an interactive script
|
||||
--
|
||||
-- Execute with:
|
||||
-- wireplumber -e ./interactive.lua option1=value1 option2=value2 ...
|
||||
-- wpexec ./interactive.lua option1=value1 option2=value2 ...
|
||||
-- or:
|
||||
-- ./interactive.lua option1=value1 option2=value2
|
||||
-----------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue