Blob Blame History Raw
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