diff --git a/doc/index.dox b/doc/index.dox index 813ed3ad7..5169dfe39 100644 --- a/doc/index.dox +++ b/doc/index.dox @@ -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 diff --git a/media-session.d/access-default.conf b/media-session.d/access-default.conf new file mode 100644 index 000000000..540ff39a5 --- /dev/null +++ b/media-session.d/access-default.conf @@ -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" + } + } +] diff --git a/media-session.d/media-session.conf b/media-session.d/media-session.conf index 23d3b797d..56c232263 100644 --- a/media-session.d/media-session.conf +++ b/media-session.d/media-session.conf @@ -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 diff --git a/media-session.d/meson.build b/media-session.d/meson.build index 5760ec029..16c61eb0c 100644 --- a/media-session.d/meson.build +++ b/media-session.d/meson.build @@ -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' ], diff --git a/src/access-default.c b/src/access-default.c index 434e8cd85..0ccc18abb 100644 --- a/src/access-default.c +++ b/src/access-default.c @@ -22,40 +22,71 @@ * DEALINGS IN THE SOFTWARE. */ +#include "config.h" + +#include #include #include #include -#include +#include #include -#include "config.h" - +#include +#include #include -#include "pipewire/pipewire.h" +#include #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; } diff --git a/src/media-session.c b/src/media-session.c index 1f65d61a5..9a73e8bc9 100644 --- a/src/media-session.c +++ b/src/media-session.c @@ -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 },