modules: access-default: handle arbitrary clients

Extend the access-default module to handle arbitrary clients
based on its configuration file. Add a default configuration
file which gives all permissions to clients having

  pipewire.access = "flatpak" and media.category = "Manager"

and gives "rx" permissions to clients having

  pipewire.access = "flatpak" or pipewire.access = "restricted"

See pipewire#1960, pipewire#1863, etc. and
wireplumber@703c35cfd44265e81595db29eed081c8785cda87.
This commit is contained in:
Barnabás Pőcze 2022-01-01 20:38:47 +01:00
parent 9d074e3ce7
commit a02208c04f
6 changed files with 218 additions and 94 deletions

View file

@ -19,19 +19,8 @@ specify an alternative config directory.
## Access management
The \ref page_media_session_module_access_default module handles clients
that have \ref PW_KEY_ACCESS set to "flatpak". Other clients are
ignored.
The module sets the permissions of all objects to `RX`. This limits the
flatpaks from doing modifications to other objects.
Because this will also set the core object permission `R`, the client will
resume with the new permissions.
`pipewire-media-session` implements \ref PW_KEY_MEDIA_CATEGORY type
"Manager" applications by simply setting the client permissions to ALL. No
additional checks are performed yet.
The \ref page_media_session_module_access_default module monitors
clients and sets the initial permissions based on its configuration file.
## Modules

View file

@ -0,0 +1,25 @@
# client access config file for PipeWire version @VERSION@ #
#
# Copy and edit this file in @MEDIA_SESSION_CONFIG_DIR@/media-session.d/
# for system-wide changes or in
# ~/.config/pipewire/media-session.d/ for local changes.
rules = [
{
matches = [
{ pipewire.access = "flatpak", media.category = "Manager" }
]
actions = {
default-permissions = "all"
}
}
{
matches = [
{ pipewire.access = "flatpak" }
{ pipewire.access = "restricted" }
]
actions = {
default-permissions = "rx"
}
}
]

View file

@ -73,7 +73,7 @@ session.modules = {
# the default bundle is always enabled.
default = [
access-default # manages flatpak access
access-default # manage default client permissions
portal # manage portal permissions
v4l2 # video for linux udev detection
#libcamera # libcamera udev detection

View file

@ -3,6 +3,7 @@ conf_config.set('VERSION', '@0@'.format(media_session_version))
conf_config.set('MEDIA_SESSION_CONFIG_DIR', media_session_configdir)
conf_files = [
[ 'access-default.conf', 'access-default.conf' ],
[ 'bluez-monitor.conf', 'bluez-monitor.conf' ],
[ 'v4l2-monitor.conf', 'v4l2-monitor.conf' ],
[ 'media-session.conf', 'media-session.conf' ],

View file

@ -22,40 +22,71 @@
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <math.h>
#include <strings.h>
#include <time.h>
#include "config.h"
#include <spa/utils/defs.h>
#include <spa/utils/json.h>
#include <spa/utils/string.h>
#include "pipewire/pipewire.h"
#include <pipewire/pipewire.h>
#include "media-session.h"
/** \page page_media_session_module_access_default Media Session Module: Access Default
*
* The Access Flatpak module manages permissions for flatpak clients. It
* monitors clients with a \ref PW_KEY_ACCESS value of `"flatpak"` and
* restricts those clients to the \ref PW_PERM_R and \ref PW_PERM_X
* permissions.
* The Access Default module manages permissions for clients. It monitors clients and
* applies permissions based on the rules specified in the module's configuration file.
* The permissions are only set once and for all objects.
*
* Clients of \ref PW_KEY_MEDIA_CATEGORY type "Manager" are permitted full
* access (\ref PW_PERM_ALL).
* ## Configuration
*
* The value "flatpak" is typically assigned by the \ref page_module_access.
* This module loads the `alsa-monitor.conf` configuration file. The main
* component in that file is the `rules = []` array that consists of multiple
* dictionaries that `matches` a client and specifying `actions` to take.
*
* ## Module Properties
* The following `actions` are supported:
* - `default-permissions`: set the permissions of the matched client
*
* This module requires the following properties on the client object:
* - \ref PW_KEY_ACCESS
* - \ref PW_KEY_MEDIA_CATEGORY (optional, only matters for "Manager" objects)
* The value of `default-permissions` may either be the string "all"
* for granting all permissions or a combination of `r` (read), `w` (write),
* and `x` (execute) characters.
*
* Only the first rule that matches will be applied.
*
* For example:
* ```
* rules = [
* {
* matches = [
* { pipewire.access = "flatpak", media.category = "Manager" }
* ]
* actions = {
* default-permissions = "all"
* }
* }
* {
* matches = [
* { pipewire.access = "flatpak" }
* { pipewire.access = "restricted" }
* ]
* actions = {
* default-permissions = "rx"
* }
* }
* ]
* ```
*/
#define NAME "access-default"
#define SESSION_KEY "access-default"
#define SESSION_KEY NAME
#define SESSION_CONF NAME ".conf"
PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME);
#define PW_LOG_TOPIC_DEFAULT mod_topic
@ -65,59 +96,105 @@ struct impl {
struct spa_hook listener;
struct spa_list client_list;
char *rules;
size_t rules_length;
};
struct client {
struct sm_client *obj;
uint32_t id;
struct impl *impl;
struct spa_list link; /**< link in impl client_list */
struct sm_client *sm_client;
struct spa_hook listener;
unsigned int active:1;
struct impl *impl;
unsigned int handled:1;
};
static uint32_t parse_permissions(const char *perms)
{
if (strcasecmp(perms, "all") == 0)
return PW_PERM_ALL;
uint32_t p = 0;
for (; *perms; perms++) {
switch (tolower(*perms)) {
case 'r': p |= PW_PERM_R; break;
case 'w': p |= PW_PERM_W; break;
case 'x': p |= PW_PERM_X; break;
case 'm': p |= PW_PERM_M; break;
case '-': /* do nothing */ break;
default: return PW_PERM_INVALID;
}
}
return p;
}
static int handle_default_permission(void *data, const char *location, const char *action, const char *value, size_t length)
{
if (spa_streq(action, "default-permissions")) {
uint32_t *perms = data;
if (*perms != PW_PERM_INVALID)
return 0;
if (!spa_json_is_string(value, length))
return 0;
char p[16];
if (spa_json_parse_stringn(value, length, p, sizeof(p)) <= 0)
return 0;
*perms = parse_permissions(p);
}
return 0;
}
static uint32_t get_permissions(struct impl *impl, const struct spa_dict *client_props)
{
uint32_t perm = PW_PERM_INVALID;
pw_conf_match_rules(impl->rules, impl->rules_length, NAME, client_props,
handle_default_permission, &perm);
return perm;
}
static void object_update(void *data)
{
struct client *client = data;
struct sm_client *sm_client = client->sm_client;
struct impl *impl = client->impl;
const char *str;
pw_log_debug("%p: client %p %08x", impl, client, client->obj->obj.changed);
pw_log_debug("%p: client %p: changed:0x%08x avail:0x%08x handled:%d",
impl, client,
sm_client->obj.changed, sm_client->obj.avail,
client->handled);
if (client->obj->obj.avail & SM_CLIENT_CHANGE_MASK_INFO &&
!client->active) {
struct pw_permission permissions[1];
uint32_t perms;
if (client->handled || !SPA_FLAG_IS_SET(sm_client->obj.avail, SM_CLIENT_CHANGE_MASK_INFO))
return;
if (client->obj->info == NULL || client->obj->info->props == NULL ||
(str = spa_dict_lookup(client->obj->info->props, PW_KEY_ACCESS)) == NULL)
return;
struct pw_client_info *info = sm_client->info;
if (info == NULL || info->props == NULL)
return;
if (spa_streq(str, "flatpak")) {
if ((str = spa_dict_lookup(client->obj->info->props, PW_KEY_MEDIA_CATEGORY)) != NULL &&
(spa_streq(str, "Manager"))) {
/* FIXME, use permission store to check if this app is allowed to
* be a manager app */
perms = PW_PERM_ALL;
} else {
/* limited access for everything else */
perms = PW_PERM_R | PW_PERM_X;
}
} else if (spa_streq(str, "restricted")) {
perms = PW_PERM_R | PW_PERM_X;
} else
return;
client->handled = true;
pw_log_info("%p: flatpak client %d granted 0x%08x permissions"
, impl, client->id, perms);
permissions[0] = PW_PERMISSION_INIT(PW_ID_ANY, perms);
pw_client_update_permissions(client->obj->obj.proxy,
1, permissions);
client->active = true;
}
uint32_t perms = get_permissions(impl, info->props);
if (perms == PW_PERM_INVALID)
return;
pw_log_info("%p: client %p: id:%u granting:" PW_PERMISSION_FORMAT,
impl, sm_client, sm_client->obj.id, PW_PERMISSION_ARGS(perms));
const struct pw_permission permissions[] = {
PW_PERMISSION_INIT(PW_ID_ANY, perms),
};
pw_client_update_permissions(client->sm_client->obj.proxy,
SPA_N_ELEMENTS(permissions), permissions);
}
static const struct sm_object_events object_events = {
@ -125,58 +202,46 @@ static const struct sm_object_events object_events = {
.update = object_update
};
static int
handle_client(struct impl *impl, struct sm_object *object)
static void handle_client(struct impl *impl, struct sm_client *sm_client)
{
struct client *client;
pw_log_debug("%p: new client '%u'", impl, object->id);
pw_log_debug("%p: new client %p: id:%u", impl, sm_client, sm_client->obj.id);
client = sm_object_add_data(&sm_client->obj, SESSION_KEY, sizeof(*client));
client = sm_object_add_data(object, SESSION_KEY, sizeof(struct client));
client->obj = (struct sm_client*)object;
client->id = object->id;
client->impl = impl;
spa_list_append(&impl->client_list, &client->link);
client->obj->obj.mask |= SM_CLIENT_CHANGE_MASK_INFO;
sm_object_add_listener(&client->obj->obj, &client->listener, &object_events, client);
return 1;
client->sm_client = sm_client;
client->sm_client->obj.mask |= SM_CLIENT_CHANGE_MASK_INFO;
sm_object_add_listener(&client->sm_client->obj, &client->listener, &object_events, client);
}
static void destroy_client(struct impl *impl, struct client *client)
static void destroy_client(struct client *client)
{
spa_list_remove(&client->link);
spa_hook_remove(&client->listener);
sm_object_remove_data((struct sm_object*)client->obj, SESSION_KEY);
sm_object_remove_data(&client->sm_client->obj, SESSION_KEY);
}
static void session_create(void *data, struct sm_object *object)
{
struct impl *impl = data;
int res;
pw_log_debug("%p: create global '%d'", impl, object->id);
pw_log_debug("%p: create global %u", data, object->id);
if (spa_streq(object->type, PW_TYPE_INTERFACE_Client))
res = handle_client(impl, object);
else
res = 0;
if (res < 0)
pw_log_warn("%p: can't handle global %d", impl, object->id);
handle_client(data, (struct sm_client *) object);
}
static void session_remove(void *data, struct sm_object *object)
{
struct impl *impl = data;
pw_log_debug("%p: remove global '%d'", impl, object->id);
pw_log_debug("%p: remove global %u", data, object->id);
if (spa_streq(object->type, PW_TYPE_INTERFACE_Client)) {
struct client *client;
if ((client = sm_object_get_data(object, SESSION_KEY)) != NULL)
destroy_client(impl, client);
destroy_client(client);
}
}
@ -186,7 +251,9 @@ static void session_destroy(void *data)
struct client *client;
spa_list_consume(client, &impl->client_list, link)
destroy_client(impl, client);
destroy_client(client);
free(impl->rules);
spa_hook_remove(&impl->listener);
free(impl);
@ -199,22 +266,64 @@ static const struct sm_media_session_events session_events = {
.destroy = session_destroy,
};
static int load_conf(struct impl *impl)
{
struct pw_properties *conf = pw_properties_new(NULL, NULL);
if (conf == NULL)
return -errno;
int res = sm_media_session_load_conf(impl->session, SESSION_CONF, conf);
if (res < 0)
goto out;
const char *rules = pw_properties_get(conf, "rules");
if (rules == NULL) {
pw_log_warn("no 'rules' section in " SESSION_CONF);
res = -ENOENT;
goto out;
}
impl->rules = strdup(rules);
if (impl->rules == NULL) {
res = -errno;
goto out;
}
impl->rules_length = strlen(impl->rules);
out:
pw_properties_free(conf);
return res;
}
int sm_access_default_start(struct sm_media_session *session)
{
struct impl *impl;
int res;
PW_LOG_TOPIC_INIT(mod_topic);
impl = calloc(1, sizeof(struct impl));
impl = calloc(1, sizeof(*impl));
if (impl == NULL)
return -errno;
impl->session = session;
res = load_conf(impl);
if (res < 0)
goto error;
spa_list_init(&impl->client_list);
sm_media_session_add_listener(impl->session,
&impl->listener,
&session_events, impl);
return 0;
error:
free(impl);
return res;
}

View file

@ -2440,7 +2440,7 @@ static const struct {
const char *props;
} modules[] = {
{ "access-default", "manage flatpak access", sm_access_default_start, NULL },
{ "access-default", "manage default client permissions", sm_access_default_start, NULL },
{ "portal", "manage portal permissions", sm_access_portal_start, NULL },
{ "metadata", "export metadata API", sm_metadata_start, NULL },
{ "default-nodes", "restore default nodes", sm_default_nodes_start, NULL },