From d5643e84725e928b43e64e6f83e83290229628b3 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 14 Dec 2017 18:37:05 +0100 Subject: [PATCH] core: add permissions call Add a permissions call that can be used to update the permissions of global objects. This can be used to set specific permissions on certain objects, hide objects and define the permissions on new objects. The system is quite generic so that we can extend it later with more complicated match rules or so. --- .../module-protocol-native/protocol-native.c | 51 +++++- src/pipewire/client.c | 168 +++++++++++++++++- src/pipewire/client.h | 3 + src/pipewire/core.c | 7 + src/pipewire/global.c | 9 +- src/pipewire/interfaces.h | 53 +++++- src/pipewire/private.h | 3 + 7 files changed, 284 insertions(+), 10 deletions(-) diff --git a/src/modules/module-protocol-native/protocol-native.c b/src/modules/module-protocol-native/protocol-native.c index fe45e9fbb..fece93797 100644 --- a/src/modules/module-protocol-native/protocol-native.c +++ b/src/modules/module-protocol-native/protocol-native.c @@ -52,6 +52,28 @@ static void core_marshal_client_update(void *object, const struct spa_dict *prop pw_protocol_native_end_proxy(proxy, b); } +static void core_marshal_permissions(void *object, const struct spa_dict *props) +{ + struct pw_proxy *proxy = object; + struct spa_pod_builder *b; + int i, n_items; + + b = pw_protocol_native_begin_proxy(proxy, PW_CORE_PROXY_METHOD_PERMISSIONS); + + n_items = props ? props->n_items : 0; + + spa_pod_builder_add(b, "[ i", n_items, NULL); + + for (i = 0; i < n_items; i++) { + spa_pod_builder_add(b, + "s", props->items[i].key, + "s", props->items[i].value, NULL); + } + spa_pod_builder_add(b, "]", NULL); + + pw_protocol_native_end_proxy(proxy, b); +} + static void core_marshal_sync(void *object, uint32_t seq) { struct pw_proxy *proxy = object; @@ -397,6 +419,29 @@ static bool core_demarshal_client_update(void *object, void *data, size_t size) return true; } +static bool core_demarshal_permissions(void *object, void *data, size_t size) +{ + struct pw_resource *resource = object; + struct spa_dict props; + struct spa_pod_parser prs; + uint32_t i; + + spa_pod_parser_init(&prs, data, size, 0); + if (spa_pod_parser_get(&prs, "[ i", &props.n_items, NULL) < 0) + return false; + + props.items = alloca(props.n_items * sizeof(struct spa_dict_item)); + for (i = 0; i < props.n_items; i++) { + if (spa_pod_parser_get(&prs, + "s", &props.items[i].key, + "s", &props.items[i].value, + NULL) < 0) + return false; + } + pw_resource_do(resource, struct pw_core_proxy_methods, permissions, &props); + return true; +} + static bool core_demarshal_sync(void *object, void *data, size_t size) { struct pw_resource *resource = object; @@ -960,6 +1005,7 @@ static const struct pw_core_proxy_methods pw_protocol_native_core_method_marshal &core_marshal_sync, &core_marshal_get_registry, &core_marshal_client_update, + &core_marshal_permissions, &core_marshal_create_object, &core_marshal_create_link }; @@ -969,6 +1015,7 @@ static const struct pw_protocol_native_demarshal pw_protocol_native_core_method_ { &core_demarshal_sync, 0, }, { &core_demarshal_get_registry, 0, }, { &core_demarshal_client_update, 0, }, + { &core_demarshal_permissions, 0, }, { &core_demarshal_create_object, PW_PROTOCOL_NATIVE_REMAP, }, { &core_demarshal_create_link, PW_PROTOCOL_NATIVE_REMAP, } }; @@ -1003,11 +1050,11 @@ static const struct pw_protocol_marshal pw_protocol_native_core_marshal = { static const struct pw_registry_proxy_methods pw_protocol_native_registry_method_marshal = { PW_VERSION_REGISTRY_PROXY_METHODS, - ®istry_marshal_bind + ®istry_marshal_bind, }; static const struct pw_protocol_native_demarshal pw_protocol_native_registry_method_demarshal[] = { - { ®istry_demarshal_bind, PW_PROTOCOL_NATIVE_REMAP, } + { ®istry_demarshal_bind, PW_PROTOCOL_NATIVE_REMAP, }, }; static const struct pw_registry_proxy_events pw_protocol_native_registry_event_marshal = { diff --git a/src/pipewire/client.c b/src/pipewire/client.c index 3b758220a..2a46fcb6d 100644 --- a/src/pipewire/client.c +++ b/src/pipewire/client.c @@ -27,17 +27,56 @@ #include "pipewire/private.h" #include "pipewire/resource.h" +struct permission { + uint32_t id; + uint32_t permissions; +}; + /** \cond */ struct impl { struct pw_client this; + uint32_t permissions_default; + struct spa_hook core_listener; + struct pw_array permissions; }; struct resource_data { struct spa_hook resource_listener; }; +/** find a specific permission for a global or NULL when there is none */ +static struct permission * +find_permission(struct pw_client *client, struct pw_global *global) +{ + struct impl *impl = SPA_CONTAINER_OF(client, struct impl, this); + struct permission *p; + + if (!pw_array_check_index(&impl->permissions, global->id, struct permission)) + return NULL; + + p = pw_array_get_unchecked(&impl->permissions, global->id, struct permission); + if (p->permissions == -1) + return NULL; + else + return p; +} + /** \endcond */ +static uint32_t +client_permission_func(struct pw_global *global, + struct pw_client *client, void *data) +{ + struct impl *impl = data; + struct permission *p; + + p = find_permission(client, global); + if (p == NULL) + return impl->permissions_default; + else + return p->permissions; +} + static void client_unbind_func(void *data) { struct pw_resource *resource = data; @@ -49,6 +88,7 @@ static const struct pw_resource_events resource_events = { .destroy = client_unbind_func, }; + static int client_bind_func(struct pw_global *global, struct pw_client *client, uint32_t permissions, @@ -81,6 +121,24 @@ client_bind_func(struct pw_global *global, return -ENOMEM; } +static void +core_global_removed(void *data, struct pw_global *global) +{ + struct impl *impl = data; + struct pw_client *client = &impl->this; + struct permission *p; + + p = find_permission(client, global); + pw_log_debug("client %p: global %d removed, %p", client, global->id, p); + if (p != NULL) + p->permissions = -1; +} + +static const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, + .global_removed = core_global_removed, +}; + /** Make a new client object * * \param core a \ref pw_core object to register the client with @@ -120,7 +178,12 @@ struct pw_client *pw_client_new(struct pw_core *core, pw_properties_setf(properties, PW_CLIENT_PROP_UCRED_GID, "%d", ucred->gid); } + pw_array_init(&impl->permissions, 1024); + this->properties = properties; + this->permission_func = client_permission_func; + this->permission_data = impl; + impl->permissions_default = PW_PERM_RWX; if (user_data_size > 0) this->user_data = SPA_MEMBER(impl, sizeof(struct impl), void); @@ -131,6 +194,8 @@ struct pw_client *pw_client_new(struct pw_core *core, pw_map_init(&this->objects, 0, 32); pw_map_init(&this->types, 0, 32); + pw_core_add_listener(core, &impl->core_listener, &core_events, impl); + this->info.props = this->properties ? &this->properties->dict : NULL; return this; @@ -208,13 +273,15 @@ void pw_client_destroy(struct pw_client *client) pw_log_debug("client %p: destroy", client); spa_hook_list_call(&client->listener_list, struct pw_client_events, destroy); + spa_hook_remove(&impl->core_listener); + if (client->global) { spa_list_remove(&client->link); pw_global_destroy(client->global); } spa_list_for_each_safe(resource, tmp, &client->resource_list, link) - pw_resource_destroy(resource); + pw_resource_destroy(resource); pw_map_for_each(&client->objects, destroy_resource, client); @@ -223,6 +290,7 @@ void pw_client_destroy(struct pw_client *client) pw_map_clear(&client->objects); pw_map_clear(&client->types); + pw_array_clear(&impl->permissions); if (client->properties) pw_properties_free(client->properties); @@ -281,6 +349,104 @@ void pw_client_update_properties(struct pw_client *client, const struct spa_dict client->info.change_mask = 0; } +struct permissions_update { + struct pw_client *client; + uint32_t permissions; + bool only_new; +}; + +static int do_permissions(void *data, struct pw_global *global) +{ + struct permissions_update *update = data; + struct pw_client *client = update->client; + struct impl *impl = SPA_CONTAINER_OF(client, struct impl, this); + struct permission *p; + size_t len, i; + + len = pw_array_get_len(&impl->permissions, struct permission); + if (len <= global->id) { + size_t diff = global->id - len + 1; + + p = pw_array_add(&impl->permissions, diff * sizeof(struct permission)); + if (p == NULL) + return -ENOMEM; + + for (i = 0; i < diff; i++) + p[i].permissions = -1; + } + + p = pw_array_get_unchecked(&impl->permissions, global->id, struct permission); + if (p->permissions == -1) + p->permissions = impl->permissions_default; + else if (update->only_new) + return 0; + + p->permissions &= update->permissions; + pw_log_debug("client %p: set global %d permissions to %08x", client, global->id, p->permissions); + + return 0; +} + +static uint32_t parse_mask(const char *str) +{ + uint32_t mask = 0; + + while (*str != '\0') { + switch (*str++) { + case 'r': + mask |= PW_PERM_R; + break; + case 'w': + mask |= PW_PERM_W; + break; + case 'x': + mask |= PW_PERM_X; + break; + } + } + return mask; +} + +void pw_client_update_permissions(struct pw_client *client, const struct spa_dict *dict) +{ + struct impl *impl = SPA_CONTAINER_OF(client, struct impl, this); + int i; + const char *str; + size_t len; + struct permissions_update update = { client, 0 }; + + for (i = 0; i < dict->n_items; i++) { + str = dict->items[i].value; + + if (strcmp(dict->items[i].key, PW_CORE_PROXY_PERMISSIONS_DEFAULT) == 0) { + impl->permissions_default &= parse_mask(str); + pw_log_debug("client %p: set default permissions to %08x", + client, impl->permissions_default); + } + else if (strcmp(dict->items[i].key, PW_CORE_PROXY_PERMISSIONS_GLOBAL) == 0) { + struct pw_global *global; + + /* permissions.update=:[r][w][x] */ + len = strcspn(str, ":"); + if (len == 0) + continue; + + global = pw_core_find_global(client->core, atoi(str)); + if (global == NULL) + continue; + + update.permissions = parse_mask(str + len); + update.only_new = false; + do_permissions(&update, global); + } + else if (strcmp(dict->items[i].key, PW_CORE_PROXY_PERMISSIONS_EXISTING) == 0) { + update.permissions = parse_mask(str); + update.only_new = true; + pw_core_for_each_global(client->core, do_permissions, &update); + } + } +} + void pw_client_set_busy(struct pw_client *client, bool busy) { if (client->busy != busy) { diff --git a/src/pipewire/client.h b/src/pipewire/client.h index 0e4fb1b83..82b2a85cf 100644 --- a/src/pipewire/client.h +++ b/src/pipewire/client.h @@ -144,6 +144,9 @@ const struct pw_client_info *pw_client_get_info(struct pw_client *client); /** Update the client properties */ void pw_client_update_properties(struct pw_client *client, const struct spa_dict *dict); +/** Update the client permissions */ +void pw_client_update_permissions(struct pw_client *client, const struct spa_dict *dict); + /** Get the client properties */ const struct pw_properties *pw_client_get_properties(struct pw_client *client); diff --git a/src/pipewire/core.c b/src/pipewire/core.c index 68695464d..91158586c 100644 --- a/src/pipewire/core.c +++ b/src/pipewire/core.c @@ -104,6 +104,12 @@ static void core_client_update(void *object, const struct spa_dict *props) pw_client_update_properties(resource->client, props); } +static void core_permissions(void *object, const struct spa_dict *props) +{ + struct pw_resource *resource = object; + pw_client_update_permissions(resource->client, props); +} + static void core_sync(void *object, uint32_t seq) { struct pw_resource *resource = object; @@ -330,6 +336,7 @@ static const struct pw_core_proxy_methods core_methods = { .sync = core_sync, .get_registry = core_get_registry, .client_update = core_client_update, + .permissions = core_permissions, .create_object = core_create_object, .create_link = core_create_link }; diff --git a/src/pipewire/global.c b/src/pipewire/global.c index 56d681b06..42eb75cbe 100644 --- a/src/pipewire/global.c +++ b/src/pipewire/global.c @@ -38,11 +38,14 @@ struct global_impl { uint32_t pw_global_get_permissions(struct pw_global *global, struct pw_client *client) { struct pw_core *core = client->core; + uint32_t perms = PW_PERM_RWX; - if (core->permission_func == NULL) - return PW_PERM_RWX; + if (core->permission_func != NULL) + perms &= core->permission_func(global, client, core->permission_data); + if ((perms & PW_PERM_R) && client->permission_func != NULL) + perms &= client->permission_func(global, client, client->permission_data); - return core->permission_func(global, client, core->permission_data); + return perms; } /** Create and add a new global to the core diff --git a/src/pipewire/interfaces.h b/src/pipewire/interfaces.h index 1b7795f84..9816824bf 100644 --- a/src/pipewire/interfaces.h +++ b/src/pipewire/interfaces.h @@ -70,9 +70,30 @@ struct pw_link_proxy; #define PW_CORE_PROXY_METHOD_SYNC 1 #define PW_CORE_PROXY_METHOD_GET_REGISTRY 2 #define PW_CORE_PROXY_METHOD_CLIENT_UPDATE 3 -#define PW_CORE_PROXY_METHOD_CREATE_OBJECT 4 -#define PW_CORE_PROXY_METHOD_CREATE_LINK 5 -#define PW_CORE_PROXY_METHOD_NUM 6 +#define PW_CORE_PROXY_METHOD_PERMISSIONS 4 +#define PW_CORE_PROXY_METHOD_CREATE_OBJECT 5 +#define PW_CORE_PROXY_METHOD_CREATE_LINK 6 +#define PW_CORE_PROXY_METHOD_NUM 7 + +/** + * Key to update default permissions of globals without specific + * permissions. value is "[r][w][x]" */ +#define PW_CORE_PROXY_PERMISSIONS_DEFAULT "permissions.default" + +/** + * Key to update specific permissions of a global. If the global + * did not have specific permissions, it will first be assigned + * the default permissions before it is updated. + * Value is ":[r][w][x]"*/ +#define PW_CORE_PROXY_PERMISSIONS_GLOBAL "permissions.global" + +/** + * Key to update specific permissions of all existing globals. + * This is equivalent to using \ref PW_CORE_PROXY_PERMISSIONS_GLOBAL + * on each global id individually that did not have specific + * permissions. + * Value is "[r][w][x]" */ +#define PW_CORE_PROXY_PERMISSIONS_EXISTING "permissions.existing" /** * \struct pw_core_proxy_methods @@ -122,6 +143,19 @@ struct pw_core_proxy_methods { * \param props the new client properties */ void (*client_update) (void *object, const struct spa_dict *props); + /** + * Manage the permissions of the global objects + * + * Update the permissions of the global objects using the + * dictionary with properties. + * + * Globals can use the default permissions or can have specific + * permissions assigned to them. + * + * \param id the global id to change + * \param props dictionary with permission properties + */ + void (*permissions) (void *object, const struct spa_dict *props); /** * Create a new object on the PipeWire server from a factory. * Use a \a factory_name of "client-node" to create a @@ -186,6 +220,12 @@ pw_core_proxy_client_update(struct pw_core_proxy *core, const struct spa_dict *p pw_proxy_do((struct pw_proxy*)core, struct pw_core_proxy_methods, client_update, props); } +static inline void +pw_core_proxy_permissions(struct pw_core_proxy *core, const struct spa_dict *props) +{ + pw_proxy_do((struct pw_proxy*)core, struct pw_core_proxy_methods, permissions, props); +} + static inline void * pw_core_proxy_create_object(struct pw_core_proxy *core, const char *factory_name, @@ -328,8 +368,13 @@ pw_core_proxy_add_listener(struct pw_core_proxy *core, * request. This creates a client-side proxy that lets the object * emit events to the client and lets the client invoke methods on * the object. See \ref page_proxy + * + * Clients can also change the permissions of the global objects that + * it can see. This is interesting when you want to configure a + * pipewire session before handing it to another application. You + * can, for example, hide certain existing or new objects or limit + * the access permissions on an object. */ - #define PW_REGISTRY_PROXY_METHOD_BIND 0 #define PW_REGISTRY_PROXY_METHOD_NUM 1 diff --git a/src/pipewire/private.h b/src/pipewire/private.h index 1a2c9dc4c..94adae224 100644 --- a/src/pipewire/private.h +++ b/src/pipewire/private.h @@ -73,6 +73,9 @@ struct pw_client { struct spa_list link; /**< link in core object client list */ struct pw_global *global; /**< global object created for this client */ + pw_permission_func_t permission_func; /**< get permissions of an object */ + void *permission_data; /**< data passed to permission function */ + struct pw_properties *properties; /**< Client properties */ struct pw_client_info info; /**< client info */