From 46754adc0e09e7ea8a9c84183425924333c0d251 Mon Sep 17 00:00:00 2001
From: Matthias Clasen <mclasen@redhat.com>
Date: Fri, 15 Jul 2016 09:55:44 -0400
Subject: [PATCH 15/18] Add flatpak access control
Add a module that talks to xdg-desktop-portal for access control
in sandboxed applications. Currently, it asks the portal for
access to microphone/speakers when a record or playback stream
is opened or samples are played.
For non-sandboxed applications, the module imposes no restrictions.
---
src/Makefile.am | 9 +-
src/modules/module-access.c | 4 +-
src/modules/module-flatpak.c | 738 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 749 insertions(+), 2 deletions(-)
create mode 100644 src/modules/module-flatpak.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 70bc848..0e0a480 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1175,7 +1175,8 @@ libavahi_wrap_la_LIBADD = $(AM_LIBADD) $(AVAHI_CFLAGS) libpulsecore-@PA_MAJORMIN
if HAVE_DBUS
# Serveral module (e.g. libalsa-util.la)
modlibexec_LTLIBRARIES += \
- module-console-kit.la
+ module-console-kit.la \
+ module-flatpak.la
endif
modlibexec_LTLIBRARIES += \
@@ -1473,6 +1474,7 @@ endif
# These are generated by an M4 script
SYMDEF_FILES = \
module-access-symdef.h \
+ module-flatpak-symdef.h \
module-cli-symdef.h \
module-cli-protocol-tcp-symdef.h \
module-cli-protocol-unix-symdef.h \
@@ -1600,6 +1602,11 @@ module_access_la_SOURCES = modules/module-access.c
module_access_la_LDFLAGS = $(MODULE_LDFLAGS)
module_access_la_LIBADD = $(MODULE_LIBADD)
+module_flatpak_la_SOURCES = modules/module-flatpak.c
+module_flatpak_la_CFLAGS = $(AM_CDFLAGS) $(DBUS_CFLAGS)
+module_flatpak_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_flatpak_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS)
+
# CLI protocol
module_cli_la_SOURCES = modules/module-cli.c
diff --git a/src/modules/module-access.c b/src/modules/module-access.c
index 39e2f0e..dec1dbf 100644
--- a/src/modules/module-access.c
+++ b/src/modules/module-access.c
@@ -498,9 +498,9 @@ int pa__init(pa_module*m) {
ap->rule[PA_ACCESS_HOOK_VIEW_CARD] = rule_allow;
ap->rule[PA_ACCESS_HOOK_STAT] = rule_allow;
ap->rule[PA_ACCESS_HOOK_VIEW_SAMPLE] = rule_allow;
+
ap->rule[PA_ACCESS_HOOK_PLAY_SAMPLE] = rule_allow;
ap->rule[PA_ACCESS_HOOK_CONNECT_PLAYBACK] = rule_allow;
-
ap->rule[PA_ACCESS_HOOK_CONNECT_RECORD] = rule_check_async;
ap->rule[PA_ACCESS_HOOK_VIEW_CLIENT] = rule_check_owner;
@@ -508,11 +508,13 @@ int pa__init(pa_module*m) {
ap->rule[PA_ACCESS_HOOK_CREATE_SINK_INPUT] = rule_allow;
ap->rule[PA_ACCESS_HOOK_VIEW_SINK_INPUT] = rule_check_owner;
+ ap->rule[PA_ACCESS_HOOK_MOVE_SINK_INPUT] = rule_check_owner;
ap->rule[PA_ACCESS_HOOK_SET_SINK_INPUT_VOLUME] = rule_check_owner;
ap->rule[PA_ACCESS_HOOK_KILL_SINK_INPUT] = rule_check_owner;
ap->rule[PA_ACCESS_HOOK_CREATE_SOURCE_OUTPUT] = rule_allow;
ap->rule[PA_ACCESS_HOOK_VIEW_SOURCE_OUTPUT] = rule_check_owner;
+ ap->rule[PA_ACCESS_HOOK_MOVE_SOURCE_OUTPUT] = rule_check_owner;
ap->rule[PA_ACCESS_HOOK_SET_SOURCE_OUTPUT_VOLUME] = rule_check_owner;
ap->rule[PA_ACCESS_HOOK_KILL_SOURCE_OUTPUT] = rule_check_owner;
diff --git a/src/modules/module-flatpak.c b/src/modules/module-flatpak.c
new file mode 100644
index 0000000..64bbe86
--- /dev/null
+++ b/src/modules/module-flatpak.c
@@ -0,0 +1,738 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2016 Red Hat, Inc.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/rtclock.h>
+#include <pulse/timeval.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/core-struct.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/sink-input.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-shared.h>
+
+#include "module-access-symdef.h"
+
+PA_MODULE_AUTHOR("Matthias Clasen");
+PA_MODULE_DESCRIPTION("Controls access to server resources for flatpak apps");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(true);
+PA_MODULE_USAGE("");
+
+static const char* const valid_modargs[] = {
+ NULL,
+};
+
+typedef struct access_policy access_policy;
+typedef struct event_item event_item;
+typedef struct client_data client_data;
+typedef struct userdata userdata;
+
+typedef pa_hook_result_t (*access_rule_t)(pa_core *c, pa_access_data *d, struct userdata *u);
+
+struct access_policy {
+ uint32_t index;
+ struct userdata *userdata;
+
+ access_rule_t rule[PA_ACCESS_HOOK_MAX];
+};
+
+struct event_item {
+ PA_LLIST_FIELDS(event_item);
+
+ int facility;
+ uint32_t object_index;
+};
+
+struct async_cache {
+ bool checked;
+ bool granted;
+};
+
+struct userdata {
+ pa_core *core;
+
+ pa_hook_slot *hook[PA_ACCESS_HOOK_MAX];
+
+ pa_idxset *policies;
+ uint32_t default_policy;
+ uint32_t portal_policy;
+
+ pa_dbus_connection *connection;
+ pa_hashmap *clients;
+ pa_hook_slot *client_put_slot;
+ pa_hook_slot *client_auth_slot;
+ pa_hook_slot *client_proplist_changed_slot;
+ pa_hook_slot *client_unlink_slot;
+};
+
+struct client_data {
+ struct userdata *u;
+
+ uint32_t index;
+ uint32_t policy;
+ pid_t pid;
+
+ struct async_cache cached[PA_ACCESS_HOOK_MAX];
+ pa_access_data *access_data;
+
+ PA_LLIST_HEAD(event_item, events);
+};
+
+
+static void add_event(struct client_data *cd, int facility, uint32_t oidx) {
+ event_item *i;
+
+ i = pa_xnew0(event_item, 1);
+ PA_LLIST_INIT(event_item, i);
+ i->facility = facility;
+ i->object_index = oidx;
+
+ PA_LLIST_PREPEND(event_item, cd->events, i);
+}
+
+static event_item *find_event(struct client_data *cd, int facility, uint32_t oidx) {
+ event_item *i;
+
+ PA_LLIST_FOREACH(i, cd->events) {
+ if (i->facility == facility && i->object_index == oidx)
+ return i;
+ }
+ return NULL;
+}
+
+static bool remove_event(struct client_data *cd, int facility, uint32_t oidx) {
+ event_item *i = find_event(cd, facility, oidx);
+ if (i) {
+ PA_LLIST_REMOVE(event_item, cd->events, i);
+ pa_xfree(i);
+ return true;
+ }
+ return false;
+}
+
+static client_data * client_data_new(struct userdata *u, uint32_t index, uint32_t policy, pid_t pid) {
+ client_data *cd;
+
+ cd = pa_xnew0(client_data, 1);
+ cd->u = u;
+ cd->index = index;
+ cd->policy = policy;
+ cd->pid = pid;
+ pa_hashmap_put(u->clients, PA_UINT32_TO_PTR(index), cd);
+ pa_log("new client %d with pid %d, policy %d", index, pid, policy);
+
+ return cd;
+}
+
+static void client_data_free(client_data *cd) {
+ event_item *e;
+
+ while ((e = cd->events)) {
+ PA_LLIST_REMOVE(event_item, cd->events, e);
+ pa_xfree(e);
+ }
+ pa_log("removed client %d", cd->index);
+ pa_xfree(cd);
+}
+
+static client_data * client_data_get(struct userdata *u, uint32_t index) {
+ return pa_hashmap_get(u->clients, PA_UINT32_TO_PTR(index));
+}
+
+static void client_data_remove(struct userdata *u, uint32_t index) {
+ pa_hashmap_remove_and_free(u->clients, PA_UINT32_TO_PTR(index));
+}
+
+/* rule checks if the operation on the object is performed by the owner of the object */
+static pa_hook_result_t rule_check_owner (pa_core *c, pa_access_data *d, struct userdata *u) {
+ pa_hook_result_t result = PA_HOOK_STOP;
+ uint32_t idx = PA_INVALID_INDEX;
+
+ switch (d->hook) {
+ case PA_ACCESS_HOOK_VIEW_CLIENT:
+ case PA_ACCESS_HOOK_KILL_CLIENT: {
+ idx = d->object_index;
+ break;
+ }
+
+ case PA_ACCESS_HOOK_VIEW_SINK_INPUT:
+ case PA_ACCESS_HOOK_SET_SINK_INPUT_VOLUME:
+ case PA_ACCESS_HOOK_KILL_SINK_INPUT: {
+ const pa_sink_input *si = pa_idxset_get_by_index(c->sink_inputs, d->object_index);
+ idx = (si && si->client) ? si->client->index : PA_INVALID_INDEX;
+ break;
+ }
+
+ case PA_ACCESS_HOOK_VIEW_SOURCE_OUTPUT:
+ case PA_ACCESS_HOOK_SET_SOURCE_OUTPUT_VOLUME:
+ case PA_ACCESS_HOOK_KILL_SOURCE_OUTPUT: {
+ const pa_source_output *so = pa_idxset_get_by_index(c->source_outputs, d->object_index);
+ idx = (so && so->client) ? so->client->index : PA_INVALID_INDEX;
+ break;
+ }
+ default:
+ break;
+ }
+ if (idx == d->client_index) {
+ pa_log("allow operation %d/%d of same client %d", d->hook, d->object_index, idx);
+ result = PA_HOOK_OK;
+ } else
+ pa_log("blocked operation %d/%d of client %d to client %d", d->hook, d->object_index, idx, d->client_index);
+
+ return result;
+}
+
+/* rule allows the operation */
+static pa_hook_result_t rule_allow (pa_core *c, pa_access_data *d, struct userdata *u) {
+ pa_log("allow operation %d/%d for client %d", d->hook, d->object_index, d->client_index);
+ return PA_HOOK_OK;
+}
+
+/* rule blocks the operation */
+static pa_hook_result_t rule_block (pa_core *c, pa_access_data *d, struct userdata *u) {
+ pa_log("blocked operation %d/%d for client %d", d->hook, d->object_index, d->client_index);
+ return PA_HOOK_STOP;
+}
+
+static DBusHandlerResult portal_response(DBusConnection *connection, DBusMessage *msg, void *user_data)
+{
+ client_data *cd = user_data;
+ pa_access_data *d = cd->access_data;
+
+ if (dbus_message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) {
+ uint32_t response = 2;
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ dbus_connection_remove_filter (connection, portal_response, cd);
+
+ if (!dbus_message_get_args(msg, &error, DBUS_TYPE_UINT32, &response, DBUS_TYPE_INVALID)) {
+ pa_log("failed to parse Response: %s\n", error.message);
+ dbus_error_free(&error);
+ }
+
+ cd->cached[d->hook].checked = true;
+ cd->cached[d->hook].granted = response == 0 ? true : false;
+
+ pa_log("portal check result: %d\n", cd->cached[d->hook].granted);
+
+ d->complete_cb (d, cd->cached[d->hook].granted);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static pa_hook_result_t rule_check_portal (pa_core *c, pa_access_data *d, struct userdata *u) {
+ client_data *cd = client_data_get(u, d->client_index);
+ DBusMessage *m = NULL, *r = NULL;
+ DBusError error;
+ pid_t pid;
+ DBusMessageIter msg_iter;
+ DBusMessageIter dict_iter;
+ const char *handle;
+ const char *device;
+
+ if (cd->cached[d->hook].checked) {
+ pa_log("returned cached answer for portal check: %d\n", cd->cached[d->hook].granted);
+ return cd->cached[d->hook].granted ? PA_HOOK_OK : PA_HOOK_STOP;
+ }
+
+ pa_log("ask portal for operation %d/%d for client %d", d->hook, d->object_index, d->client_index);
+
+ cd->access_data = d;
+
+ dbus_error_init(&error);
+
+ if (!(m = dbus_message_new_method_call("org.freedesktop.portal.Desktop",
+ "/org/freedesktop/portal/desktop",
+ "org.freedesktop.portal.Device",
+ "AccessDevice"))) {
+ return PA_HOOK_STOP;
+ }
+
+ if (d->hook == PA_ACCESS_HOOK_CONNECT_RECORD)
+ device = "microphone";
+ else if (d->hook == PA_ACCESS_HOOK_CONNECT_PLAYBACK ||
+ d->hook == PA_ACCESS_HOOK_PLAY_SAMPLE)
+ device = "speakers";
+ else
+ pa_assert_not_reached ();
+
+ pid = cd->pid;
+ if (!dbus_message_append_args(m,
+ DBUS_TYPE_UINT32, &pid,
+ DBUS_TYPE_INVALID)) {
+ dbus_message_unref(m);
+ return PA_HOOK_STOP;
+ }
+
+ dbus_message_iter_init_append(m, &msg_iter);
+ dbus_message_iter_open_container (&msg_iter, DBUS_TYPE_ARRAY, "s", &dict_iter);
+ dbus_message_iter_append_basic (&dict_iter, DBUS_TYPE_STRING, &device);
+ dbus_message_iter_close_container (&msg_iter, &dict_iter);
+
+ dbus_message_iter_open_container (&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter);
+ dbus_message_iter_close_container (&msg_iter, &dict_iter);
+
+ if (!(r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->connection), m, -1, &error))) {
+ pa_log("Failed to call portal: %s\n", error.message);
+ dbus_error_free(&error);
+ dbus_message_unref(m);
+ return PA_HOOK_STOP;
+ }
+
+ dbus_message_unref(m);
+
+ if (!dbus_message_get_args(r, &error, DBUS_TYPE_OBJECT_PATH, &handle, DBUS_TYPE_INVALID)) {
+ pa_log("Failed to parse AccessDevice result: %s\n", error.message);
+ dbus_error_free(&error);
+ dbus_message_unref(r);
+ return PA_HOOK_STOP;
+ }
+
+ dbus_message_unref(r);
+
+ dbus_bus_add_match(pa_dbus_connection_get(u->connection),
+ "type='signal',interface='org.freedesktop.portal.Request'",
+ &error);
+ dbus_connection_flush(pa_dbus_connection_get(u->connection));
+ if (dbus_error_is_set(&error)) {
+ pa_log("Failed to subscribe to Request signal: %s\n", error.message);
+ dbus_error_free(&error);
+ return PA_HOOK_STOP;
+ }
+
+ dbus_connection_add_filter(pa_dbus_connection_get(u->connection), portal_response, cd, NULL);
+
+ return PA_HOOK_CANCEL;
+}
+
+static access_policy *access_policy_new(struct userdata *u, bool allow_all) {
+ access_policy *ap;
+ int i;
+
+ ap = pa_xnew0(access_policy, 1);
+ ap->userdata = u;
+ for (i = 0; i < PA_ACCESS_HOOK_MAX; i++)
+ ap->rule[i] = allow_all ? rule_allow : rule_block;
+
+ pa_idxset_put(u->policies, ap, &ap->index);
+
+ return ap;
+}
+
+static void access_policy_free(access_policy *ap) {
+ pa_idxset_remove_by_index(ap->userdata->policies, ap->index);
+ pa_xfree(ap);
+}
+
+static pa_hook_result_t check_access (pa_core *c, pa_access_data *d, struct userdata *u) {
+ access_policy *ap;
+ access_rule_t rule;
+ client_data *cd = client_data_get(u, d->client_index);
+
+ /* unknown client */
+ if (cd == NULL)
+ return PA_HOOK_STOP;
+
+ ap = pa_idxset_get_by_index(u->policies, cd->policy);
+
+ rule = ap->rule[d->hook];
+ if (rule)
+ return rule(c, d, u);
+
+ return PA_HOOK_STOP;
+}
+
+static const pa_access_hook_t event_hook[PA_SUBSCRIPTION_EVENT_FACILITY_MASK+1] = {
+ [PA_SUBSCRIPTION_EVENT_SINK] = PA_ACCESS_HOOK_VIEW_SINK,
+ [PA_SUBSCRIPTION_EVENT_SOURCE] = PA_ACCESS_HOOK_VIEW_SOURCE,
+ [PA_SUBSCRIPTION_EVENT_SINK_INPUT] = PA_ACCESS_HOOK_VIEW_SINK_INPUT,
+ [PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT] = PA_ACCESS_HOOK_VIEW_SOURCE_OUTPUT,
+ [PA_SUBSCRIPTION_EVENT_MODULE] = PA_ACCESS_HOOK_VIEW_MODULE,
+ [PA_SUBSCRIPTION_EVENT_CLIENT] = PA_ACCESS_HOOK_VIEW_CLIENT,
+ [PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE] = PA_ACCESS_HOOK_VIEW_SAMPLE,
+ [PA_SUBSCRIPTION_EVENT_SERVER] = PA_ACCESS_HOOK_VIEW_SERVER,
+ [PA_SUBSCRIPTION_EVENT_CARD] = PA_ACCESS_HOOK_VIEW_CARD
+};
+
+static pa_hook_result_t filter_event (pa_core *c, pa_access_data *d, struct userdata *u) {
+ int facility;
+ client_data *cd;
+
+ facility = d->event & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
+
+ cd = client_data_get (u, d->client_index);
+ /* unknown client destination, block event */
+ if (cd == NULL)
+ goto block;
+
+ switch (d->event & PA_SUBSCRIPTION_EVENT_TYPE_MASK) {
+ case PA_SUBSCRIPTION_EVENT_REMOVE:
+ /* if the client saw this object before, let the event go through */
+ if (remove_event(cd, facility, d->object_index)) {
+ pa_log("pass event %02x/%d to client %d", d->event, d->object_index, d->client_index);
+ return PA_HOOK_OK;
+ }
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_CHANGE:
+ /* if the client saw this object before, let it go through */
+ if (find_event(cd, facility, d->object_index)) {
+ pa_log("pass event %02x/%d to client %d", d->event, d->object_index, d->client_index);
+ return PA_HOOK_OK;
+ }
+
+ /* fallthrough to do hook check and register event */
+ case PA_SUBSCRIPTION_EVENT_NEW: {
+ pa_access_data data = *d;
+
+ /* new object, check if the client is allowed to inspect it */
+ data.hook = event_hook[facility];
+ if (data.hook && pa_hook_fire(&c->access[data.hook], &data) == PA_HOOK_OK) {
+ /* client can inspect the object, remember for later */
+ add_event(cd, facility, d->object_index);
+ pa_log("pass event %02x/%d to client %d", d->event, d->object_index, d->client_index);
+ return PA_HOOK_OK;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+block:
+ pa_log("blocked event %02x/%d for client %d", d->event, d->object_index, d->client_index);
+ return PA_HOOK_STOP;
+}
+
+static bool
+client_is_sandboxed (pa_client *cl)
+{
+ char *path;
+ char data[2048];
+ int n;
+ const char *state = NULL;
+ const char *current;
+ bool result;
+ int fd;
+ pid_t pid;
+
+ if (cl->creds_valid) {
+ pa_log ("client has trusted pid %d", cl->creds.pid);
+ }
+ else {
+ pa_log ("no trusted pid found, assuming not sandboxed\n");
+ return false;
+ }
+
+ pid = cl->creds.pid;
+
+ path = pa_sprintf_malloc("/proc/%u/cgroup", pid);
+ fd = pa_open_cloexec(path, O_RDONLY, 0);
+ free (path);
+
+ if (fd == -1)
+ return false;
+
+ pa_loop_read(fd, &data, sizeof(data), NULL);
+ close(fd);
+
+ result = false;
+ while ((current = pa_split_in_place(data, "\n", &n, &state)) != NULL) {
+ if (strncmp(current, "1:name=systemd:", strlen("1:name=systemd:")) == 0) {
+ const char *p = strstr(current, "flatpak-");
+ if (p && p - current < n) {
+ pa_log("found a flatpak cgroup, assuming sandboxed\n");
+ result = true;
+ break;
+ }
+ }
+ }
+
+ return result;
+}
+
+static uint32_t find_policy_for_client (struct userdata *u, pa_client *cl) {
+ char *s;
+
+ s = pa_proplist_to_string(cl->proplist);
+ pa_log ("client proplist %s", s);
+ pa_xfree(s);
+
+ if (client_is_sandboxed (cl)) {
+ pa_log("client is sandboxed, choosing portal policy\n");
+ return u->portal_policy;
+ }
+ else {
+ pa_log("client not sandboxed, choosing default policy\n");
+ return u->default_policy;
+ }
+}
+
+static pa_hook_result_t client_put_cb(pa_core *c, pa_object *o, struct userdata *u) {
+ pa_client *cl;
+ uint32_t policy;
+
+pa_log("client put\n");
+ pa_assert(c);
+ pa_object_assert_ref(o);
+
+ cl = (pa_client *) o;
+ pa_assert(cl);
+
+ /* when we get here, the client just connected and is not yet authenticated
+ * we should probably install a policy that denies all access */
+ policy = find_policy_for_client(u, cl);
+
+ client_data_new(u, cl->index, policy, cl->creds.pid);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t client_auth_cb(pa_core *c, pa_object *o, struct userdata *u) {
+ pa_client *cl;
+ client_data *cd;
+ uint32_t policy;
+
+ pa_assert(c);
+ pa_object_assert_ref(o);
+
+ cl = (pa_client *) o;
+ pa_assert(cl);
+
+ cd = client_data_get (u, cl->index);
+ if (cd == NULL)
+ return PA_HOOK_OK;
+
+ policy = find_policy_for_client(u, cl);
+ cd->policy = policy;
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t client_proplist_changed_cb(pa_core *c, pa_object *o, struct userdata *u) {
+ pa_client *cl;
+ client_data *cd;
+ uint32_t policy;
+
+ pa_assert(c);
+ pa_object_assert_ref(o);
+
+ cl = (pa_client *) o;
+ pa_assert(cl);
+
+ cd = client_data_get (u, cl->index);
+ if (cd == NULL)
+ return PA_HOOK_OK;
+
+ policy = find_policy_for_client(u, cl);
+ cd->policy = policy;
+ cd->pid = cl->creds.pid;
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t client_unlink_cb(pa_core *c, pa_object *o, struct userdata *u) {
+ pa_client *cl;
+
+ pa_assert(c);
+ pa_object_assert_ref(o);
+
+ cl = (pa_client *) o;
+ pa_assert(cl);
+
+ client_data_remove(u, cl->index);
+
+ return PA_HOOK_OK;
+}
+
+
+int pa__init(pa_module*m) {
+ pa_modargs *ma = NULL;
+ struct userdata *u;
+ int i;
+ access_policy *ap;
+ DBusError error;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+ goto fail;
+ }
+
+ u = pa_xnew0(struct userdata, 1);
+ u->core = m->core;
+ m->userdata = u;
+
+ dbus_error_init(&error);
+
+ if (!(u->connection = pa_dbus_bus_get (u->core, DBUS_BUS_SESSION, &error))) {
+ pa_log("Failed to connect to session bus: %s\n", error.message);
+ dbus_error_free(&error);
+ }
+
+ u->policies = pa_idxset_new (NULL, NULL);
+ u->clients = pa_hashmap_new_full(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func, NULL,
+ (pa_free_cb_t) client_data_free);
+
+ u->client_put_slot = pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_CLIENT_PUT], PA_HOOK_EARLY, (pa_hook_cb_t) client_put_cb, u);
+ u->client_auth_slot = pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_CLIENT_AUTH], PA_HOOK_EARLY, (pa_hook_cb_t) client_auth_cb, u);
+ u->client_proplist_changed_slot = pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_CLIENT_PROPLIST_CHANGED], PA_HOOK_EARLY, (pa_hook_cb_t) client_proplist_changed_cb, u);
+ u->client_unlink_slot = pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_CLIENT_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) client_unlink_cb, u);
+
+ for (i = 0; i < PA_ACCESS_HOOK_MAX; i++) {
+ pa_hook_cb_t cb;
+
+ if (i == PA_ACCESS_HOOK_FILTER_SUBSCRIBE_EVENT)
+ cb = (pa_hook_cb_t) filter_event;
+ else
+ cb = (pa_hook_cb_t) check_access;
+
+ u->hook[i] = pa_hook_connect(&u->core->access[i], PA_HOOK_EARLY - 1, cb, u);
+ }
+
+ ap = access_policy_new(u, false);
+
+ ap->rule[PA_ACCESS_HOOK_VIEW_SINK] = rule_allow;
+ ap->rule[PA_ACCESS_HOOK_VIEW_SOURCE] = rule_allow;
+ ap->rule[PA_ACCESS_HOOK_VIEW_SERVER] = rule_allow;
+ ap->rule[PA_ACCESS_HOOK_VIEW_MODULE] = rule_allow;
+ ap->rule[PA_ACCESS_HOOK_VIEW_CARD] = rule_allow;
+ ap->rule[PA_ACCESS_HOOK_STAT] = rule_allow;
+ ap->rule[PA_ACCESS_HOOK_VIEW_SAMPLE] = rule_allow;
+
+ ap->rule[PA_ACCESS_HOOK_PLAY_SAMPLE] = rule_allow;
+ ap->rule[PA_ACCESS_HOOK_CONNECT_PLAYBACK] = rule_allow;
+ ap->rule[PA_ACCESS_HOOK_CONNECT_RECORD] = rule_allow;
+
+ ap->rule[PA_ACCESS_HOOK_VIEW_CLIENT] = rule_check_owner;
+ ap->rule[PA_ACCESS_HOOK_KILL_CLIENT] = rule_check_owner;
+
+ ap->rule[PA_ACCESS_HOOK_CREATE_SINK_INPUT] = rule_allow;
+ ap->rule[PA_ACCESS_HOOK_VIEW_SINK_INPUT] = rule_check_owner;
+ ap->rule[PA_ACCESS_HOOK_MOVE_SINK_INPUT] = rule_check_owner;
+ ap->rule[PA_ACCESS_HOOK_SET_SINK_INPUT_VOLUME] = rule_check_owner;
+ ap->rule[PA_ACCESS_HOOK_KILL_SINK_INPUT] = rule_check_owner;
+
+ ap->rule[PA_ACCESS_HOOK_CREATE_SOURCE_OUTPUT] = rule_allow;
+ ap->rule[PA_ACCESS_HOOK_VIEW_SOURCE_OUTPUT] = rule_check_owner;
+ ap->rule[PA_ACCESS_HOOK_MOVE_SOURCE_OUTPUT] = rule_check_owner;
+ ap->rule[PA_ACCESS_HOOK_SET_SOURCE_OUTPUT_VOLUME] = rule_check_owner;
+ ap->rule[PA_ACCESS_HOOK_KILL_SOURCE_OUTPUT] = rule_check_owner;
+
+ u->default_policy = ap->index;
+
+ ap = access_policy_new(u, false);
+
+ ap->rule[PA_ACCESS_HOOK_VIEW_SINK] = rule_allow;
+ ap->rule[PA_ACCESS_HOOK_VIEW_SOURCE] = rule_allow;
+ ap->rule[PA_ACCESS_HOOK_VIEW_SERVER] = rule_allow;
+ ap->rule[PA_ACCESS_HOOK_VIEW_MODULE] = rule_allow;
+ ap->rule[PA_ACCESS_HOOK_VIEW_CARD] = rule_allow;
+ ap->rule[PA_ACCESS_HOOK_STAT] = rule_allow;
+ ap->rule[PA_ACCESS_HOOK_VIEW_SAMPLE] = rule_allow;
+
+ ap->rule[PA_ACCESS_HOOK_PLAY_SAMPLE] = rule_check_portal;
+ ap->rule[PA_ACCESS_HOOK_CONNECT_PLAYBACK] = rule_check_portal;
+ ap->rule[PA_ACCESS_HOOK_CONNECT_RECORD] = rule_check_portal;
+
+ ap->rule[PA_ACCESS_HOOK_VIEW_CLIENT] = rule_check_owner;
+ ap->rule[PA_ACCESS_HOOK_KILL_CLIENT] = rule_check_owner;
+
+ ap->rule[PA_ACCESS_HOOK_CREATE_SINK_INPUT] = rule_allow;
+ ap->rule[PA_ACCESS_HOOK_VIEW_SINK_INPUT] = rule_check_owner;
+ ap->rule[PA_ACCESS_HOOK_MOVE_SINK_INPUT] = rule_check_owner;
+ ap->rule[PA_ACCESS_HOOK_SET_SINK_INPUT_VOLUME] = rule_check_owner;
+ ap->rule[PA_ACCESS_HOOK_KILL_SINK_INPUT] = rule_check_owner;
+
+ ap->rule[PA_ACCESS_HOOK_CREATE_SOURCE_OUTPUT] = rule_allow;
+ ap->rule[PA_ACCESS_HOOK_VIEW_SOURCE_OUTPUT] = rule_check_owner;
+ ap->rule[PA_ACCESS_HOOK_MOVE_SOURCE_OUTPUT] = rule_check_owner;
+ ap->rule[PA_ACCESS_HOOK_SET_SOURCE_OUTPUT_VOLUME] = rule_check_owner;
+ ap->rule[PA_ACCESS_HOOK_KILL_SOURCE_OUTPUT] = rule_check_owner;
+
+ u->portal_policy = ap->index;
+
+ pa_modargs_free(ma);
+ return 0;
+
+fail:
+ pa__done(m);
+
+ if (ma)
+ pa_modargs_free(ma);
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata* u;
+ int i;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ for (i = 0; i < PA_ACCESS_HOOK_MAX; i++) {
+ if (u->hook[i])
+ pa_hook_slot_free(u->hook[i]);
+ }
+
+ if (u->policies)
+ pa_idxset_free(u->policies, (pa_free_cb_t) access_policy_free);
+
+ if (u->client_put_slot)
+ pa_hook_slot_free(u->client_put_slot);
+ if (u->client_auth_slot)
+ pa_hook_slot_free(u->client_auth_slot);
+ if (u->client_proplist_changed_slot)
+ pa_hook_slot_free(u->client_proplist_changed_slot);
+ if (u->client_unlink_slot)
+ pa_hook_slot_free(u->client_unlink_slot);
+
+ if (u->clients)
+ pa_hashmap_free(u->clients);
+
+ if (u->connection)
+ pa_dbus_connection_unref (u->connection);
+
+ pa_xfree(u);
+}
--
2.9.3