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 */