Skip to content

Commit

Permalink
Use QubesDB for knowing if mic is allowed or not
Browse files Browse the repository at this point in the history
  • Loading branch information
fepitre committed Jul 5, 2024
1 parent 660ac1b commit 2cae4a0
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 122 deletions.
4 changes: 1 addition & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ shmoverride/shmoverride.so:

shmoverride/X-wrapper-qubes:
(cd shmoverride; $(MAKE) X-wrapper-qubes)

pulse/pacat-simple-vchan:
$(MAKE) -C pulse pacat-simple-vchan

Expand All @@ -65,8 +65,6 @@ install:
install -D gui-daemon/qubes-guid $(DESTDIR)/usr/bin/qubes-guid
install -m 0644 -D gui-daemon/qubes-guid.1 $(DESTDIR)$(MANDIR)/man1/qubes-guid.1
install -D pulse/pacat-simple-vchan $(DESTDIR)/usr/bin/pacat-simple-vchan
install -D pulse/qubes.AudioInputEnable $(DESTDIR)/etc/qubes-rpc/qubes.AudioInputEnable
install -D pulse/qubes.AudioInputDisable $(DESTDIR)/etc/qubes-rpc/qubes.AudioInputDisable
install -D shmoverride/X-wrapper-qubes $(DESTDIR)/usr/bin/X-wrapper-qubes
install -D shmoverride/shmoverride.so $(DESTDIR)$(LIBDIR)/qubes-gui-daemon/shmoverride.so
install -D -m 0644 gui-daemon/guid.conf $(DESTDIR)/etc/qubes/guid.conf
Expand Down
2 changes: 0 additions & 2 deletions debian/qubes-audio-daemon.install
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
usr/bin/pacat-simple-vchan
etc/qubes-rpc/qubes.AudioInputEnable
etc/qubes-rpc/qubes.AudioInputDisable
144 changes: 57 additions & 87 deletions pulse/pacat-simple-vchan.c
Original file line number Diff line number Diff line change
Expand Up @@ -809,102 +809,91 @@ static void check_vchan_eof_timer(pa_mainloop_api*a, pa_time_event* e,
a->time_restart(e, &restart_tv);
}

int is_rec_allowed_from_qdb(struct userdata *u) {
int new_rec_allowed;
char *qdb_entry = qdb_read(u->qdb, u->qdb_path, NULL);

if (qdb_entry != NULL) {
if (strcmp(qdb_entry, "0") == 0) {
new_rec_allowed = 0;
} else if (strcmp(qdb_entry, "1") == 0) {
new_rec_allowed = 1;
} else {
pacat_log("invalid value from Qubes DB");
new_rec_allowed = -1;
}
} else {
new_rec_allowed = -errno;
if (new_rec_allowed == -ENOENT)
pacat_log("no %s entry in QubesDB", u->qdb_path);
else
pacat_log("unable to obtain %s entry from QubesDB", u->qdb_path);
}

free(qdb_entry);

return new_rec_allowed;
}

static void control_socket_callback(pa_mainloop_api *UNUSED(a),
pa_io_event *UNUSED(e), int fd, pa_io_event_flags_t f,
pa_io_event *UNUSED(e), int UNUSED(fd), pa_io_event_flags_t f,
void *userdata) {

struct userdata *u = userdata;
int client_fd;
char command_buffer[32];
size_t command_len = 0;
int ret;
int new_rec_allowed = -1;

if (!(f & PA_IO_EVENT_INPUT))
return;
pacat_log("callback %d", new_rec_allowed);

client_fd = accept(fd, NULL, NULL);
if (client_fd < 0) {
pacat_log("Accept control connection failed: %s", strerror(errno));
if (!(f & PA_IO_EVENT_INPUT))
return;
}

/* read until either:
* - end of command (\n) is found
* - EOF
*/
do {
ret = read(client_fd, command_buffer+command_len, sizeof(command_buffer)-command_len);
if (ret < 0) {
pacat_log("Control client read failed: %s", strerror(errno));
return;
}
command_len += ret;
if (ret == 0)
break;
} while (!memchr(command_buffer + (command_len-ret), '\n', ret));

if (strncmp(command_buffer, "audio-input 0\n", command_len) == 0) {
new_rec_allowed = 0;
} else if (strncmp(command_buffer, "audio-input 1\n", command_len) == 0) {
new_rec_allowed = 1;
} else {
pacat_log("Invalid command buffer");
return;
}
new_rec_allowed = is_rec_allowed_from_qdb(u);
if (new_rec_allowed != -1) {
g_mutex_lock(&u->prop_mutex);
u->rec_allowed = new_rec_allowed;
pacat_log("Setting audio-input to %s", u->rec_allowed ? "enabled" : "disabled");
if (u->rec_allowed && u->rec_requested) {
pacat_log("Recording start");
pa_stream_cork(u->rec_stream, 0, NULL, NULL);
} else if (!u->rec_allowed && u->rec_stream &&
(u->rec_requested || !pa_stream_is_corked(u->rec_stream))) {
pacat_log("Recording stop");
pa_stream_cork(u->rec_stream, 1, NULL, NULL);
pacat_log("something %d", new_rec_allowed);
if (new_rec_allowed != u->rec_allowed) {
pacat_log("something else %d", new_rec_allowed);
u->rec_allowed = new_rec_allowed;
pacat_log("Setting audio-input to %s", u->rec_allowed ? "enabled" : "disabled");
if (u->rec_allowed && u->rec_requested) {
pacat_log("Recording start");
pa_stream_cork(u->rec_stream, 0, NULL, NULL);
} else if (!u->rec_allowed && u->rec_stream &&
(u->rec_requested || !pa_stream_is_corked(u->rec_stream))) {
pacat_log("Recording stop");
pa_stream_cork(u->rec_stream, 1, NULL, NULL);
}
if (!qdb_write(u->qdb, u->qdb_path, new_rec_allowed ? "1" : "0", 1)) {
pacat_log("Failed to write QubesDB %s: %s", u->qdb_path, strerror(errno));
}
}
g_mutex_unlock(&u->prop_mutex);
if (!qdb_write(u->qdb, u->qdb_path, new_rec_allowed ? "1" : "0", 1)) {
pacat_log("Failed to write QubesDB %s: %s", u->qdb_path, strerror(errno));
}
}
/* accept only one command per connection */
close(client_fd);
}

static int setup_control(struct userdata *u) {
int socket_fd = -1;
/* better safe than sorry - zero initialize the buffer */
struct sockaddr_un addr = { 0 };

socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (socket_fd == -1) {
pacat_log("socket failed: %s", strerror(errno));
u->qdb = qdb_open(NULL);
if (!u->qdb) {
pacat_log("qdb_open failed: %s", strerror(errno));
goto fail;
}

if ((size_t)snprintf(addr.sun_path, sizeof(addr.sun_path),
"/var/run/qubes/audio-control.%s", u->name)
>= sizeof(addr.sun_path)) {
pacat_log("VM name too long");
if (asprintf(&u->qdb_path, "/audio-input/%s", u->name) < 0) {
pacat_log("QubesDB path setup failed: %s", strerror(errno));
u->qdb_path = NULL;
goto fail;
}
/* without this line, the bind() fails in many linux versions
with Invalid Argument, and mic cannot attach */
addr.sun_family = AF_UNIX;

/* ignore result */
unlink(addr.sun_path);

if (bind(socket_fd, &addr, sizeof(addr)) == -1) {
pacat_log("bind to %s failed: %s", addr.sun_path, strerror(errno));
if (!qdb_watch(u->qdb, u->qdb_path)) {
pacat_log("failed to setup watch on %s: %m\n", u->qdb_path);
goto fail;
}

if (listen(socket_fd, 5) == -1) {
pacat_log("listen on %s failed: %s", addr.sun_path, strerror(errno));
socket_fd = qdb_watch_fd(u->qdb);
if (socket_fd < 0)
goto fail;
}

u->control_socket_event = u->mainloop_api->io_new(u->mainloop_api,
socket_fd, PA_IO_EVENT_INPUT, control_socket_callback, u);
Expand All @@ -913,23 +902,6 @@ static int setup_control(struct userdata *u) {
goto fail;
}

u->qdb = qdb_open(NULL);
if (!u->qdb) {
pacat_log("qdb_open failed: %s", strerror(errno));
goto fail;
}

if (asprintf(&u->qdb_path, "/audio-input/%s", u->name) < 0) {
pacat_log("QubesDB path setup failed: %s", strerror(errno));
u->qdb_path = NULL;
goto fail;
}

if (!qdb_write(u->qdb, u->qdb_path, "0", 1)) {
pacat_log("qdb_write failed: %s", strerror(errno));
goto fail;
}

u->control_socket_fd = socket_fd;

return 0;
Expand All @@ -956,8 +928,6 @@ static void control_cleanup(struct userdata *u) {
u->mainloop_api->io_free(u->control_socket_event);
if (u->control_socket_fd > 0)
close(u->control_socket_fd);
if (u->qdb && u->qdb_path)
qdb_rm(u->qdb, u->qdb_path);
if (u->qdb_path)
free(u->qdb_path);
if (u->qdb)
Expand Down
4 changes: 0 additions & 4 deletions pulse/qubes.AudioInputDisable

This file was deleted.

4 changes: 0 additions & 4 deletions pulse/qubes.AudioInputEnable

This file was deleted.

74 changes: 54 additions & 20 deletions qubesguidaemon/mic.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ class MicDeviceExtension(qubes.ext.Extension):
def __init__(self):
super(MicDeviceExtension, self).__init__()

def get_device(self, app):
@staticmethod
def get_device(app):
return MicDevice(
app.domains[0], product="microphone", manufacturer="build-in"
)
Expand Down Expand Up @@ -99,7 +100,7 @@ def on_device_list_attached_mic(self, vm, event, persistent=None):
)
if untrusted_audio_input == b"1":
# (device, options)
yield (self.get_device(vm.app), {})
yield self.get_device(vm.app), {}

@qubes.ext.handler("device-pre-attach:mic")
async def on_device_pre_attach_mic(self, vm, event, device, options):
Expand All @@ -123,16 +124,22 @@ async def on_device_pre_attach_mic(self, vm, event, device, options):
raise qubes.exc.QubesVMNotRunningError(
audiovm, "Audio VM {} isn't running".format(audiovm)
)
try:
await audiovm.run_service_for_stdio(
"qubes.AudioInputEnable+{}".format(vm.name)
)
except subprocess.CalledProcessError:
raise qubes.exc.QubesVMError(
vm,
"Failed to attach audio input from {!s} to {!s}: "
"pulseaudio agent not running".format(audiovm, vm),
)

if audiovm.features.check_with_netvm(
"supported-rpc.qubes.AudioInputEnable", False
):
try:
await audiovm.run_service_for_stdio(
"qubes.AudioInputEnable+{}".format(vm.name)
)
except subprocess.CalledProcessError:
raise qubes.exc.QubesVMError(
vm,
"Failed to attach audio input from {!s} to {!s}: "
"pulseaudio agent not running".format(audiovm, vm),
)
else:
audiovm.untrusted_qdb.write("/audio-input/{}".format(vm.name), "1")

# pylint: disable=unused-argument
@qubes.ext.handler("device-pre-detach:mic")
Expand All @@ -153,13 +160,40 @@ async def on_device_pre_detach_mic(self, vm, event, device):
raise qubes.exc.QubesVMNotRunningError(
audiovm, "Audio VM {} isn't running".format(audiovm)
)
try:
await audiovm.run_service_for_stdio(
"qubes.AudioInputDisable+{}".format(vm.name)

if audiovm.features.check_with_netvm(
"supported-rpc.qubes.AudioInputDisable", False
):
try:
await audiovm.run_service_for_stdio(
"qubes.AudioInputDisable+{}".format(vm.name)
)
except subprocess.CalledProcessError:
raise qubes.exc.QubesVMError(
vm,
"Failed to detach audio input from {!s} to {!s}: "
"pulseaudio agent not running".format(audiovm, vm),
)
else:
audiovm.untrusted_qdb.write("/audio-input/{}".format(vm.name), "0")

@qubes.ext.handler("property-set:audiovm")
async def on_property_set(
self, subject, event, name, newvalue, oldvalue=None
):
if newvalue == oldvalue:
return
if oldvalue:
mic_allowed = oldvalue.untrusted_qdb.read(
"/audio-input/{}".format(subject.name)
)
except subprocess.CalledProcessError:
raise qubes.exc.QubesVMError(
vm,
"Failed to detach audio input from {!s} to {!s}: "
"pulseaudio agent not running".format(audiovm, vm),
if mic_allowed not in (b"0", b"1"):
raise qubes.exc.QubesVMError(
subject,
"Invalid value for /audio-input/{!s} from {!s}".format(
subject.name, oldvalue
),
)
newvalue.untrusted_qdb.write(
"/audio-input/{}".format(subject.name), mic_allowed
)
2 changes: 0 additions & 2 deletions rpm_spec/gui-daemon.spec.in
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,6 @@ rm -f %{name}-%{version}

%files -n qubes-audio-daemon
/usr/bin/pacat-simple-vchan
/etc/qubes-rpc/qubes.AudioInputEnable
/etc/qubes-rpc/qubes.AudioInputDisable

%files -n qubes-gui-dom0
%config(noreplace) %{_sysconfdir}/qubes/policy.d/90-default-gui-daemon.policy
Expand Down

0 comments on commit 2cae4a0

Please sign in to comment.