diff --git a/Makefile b/Makefile index 3b88e459..54e36d99 100644 --- a/Makefile +++ b/Makefile @@ -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 @@ -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 diff --git a/debian/qubes-audio-daemon.install b/debian/qubes-audio-daemon.install index 0b57c584..5064a855 100644 --- a/debian/qubes-audio-daemon.install +++ b/debian/qubes-audio-daemon.install @@ -1,3 +1 @@ usr/bin/pacat-simple-vchan -etc/qubes-rpc/qubes.AudioInputEnable -etc/qubes-rpc/qubes.AudioInputDisable diff --git a/pulse/pacat-simple-vchan.c b/pulse/pacat-simple-vchan.c index ec799010..1cc997da 100644 --- a/pulse/pacat-simple-vchan.c +++ b/pulse/pacat-simple-vchan.c @@ -458,6 +458,9 @@ static void vchan_rec_callback(pa_mainloop_api *UNUSED(a), case QUBES_PA_SOURCE_START_CMD: g_mutex_lock(&u->prop_mutex); u->rec_requested = 1; + if (!qdb_write(u->qdb, u->qdb_request_path, "1", 1)) { + pacat_log("Failed to write QubesDB %s: %s", u->qdb_request_path, strerror(errno)); + } if (u->rec_allowed) { pacat_log("Recording start"); pa_stream_cork(u->rec_stream, 0, NULL, u); @@ -468,6 +471,9 @@ static void vchan_rec_callback(pa_mainloop_api *UNUSED(a), case QUBES_PA_SOURCE_STOP_CMD: g_mutex_lock(&u->prop_mutex); u->rec_requested = 0; + if (!qdb_write(u->qdb, u->qdb_request_path, "0", 1)) { + pacat_log("Failed to write QubesDB %s: %s", u->qdb_request_path, strerror(errno)); + } if (!pa_stream_is_corked(u->rec_stream)) { pacat_log("Recording stop"); pa_stream_cork(u->rec_stream, 1, NULL, u); @@ -809,124 +815,114 @@ 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_config_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_config_path); + else + pacat_log("unable to obtain %s entry from QubesDB", u->qdb_config_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; - client_fd = accept(fd, NULL, NULL); - if (client_fd < 0) { - pacat_log("Accept control connection failed: %s", strerror(errno)); - 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; - } - if (new_rec_allowed != -1) { + new_rec_allowed = is_rec_allowed_from_qdb(u); + if (new_rec_allowed >= 0) { 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); + if (new_rec_allowed != u->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_status_path, new_rec_allowed ? "1" : "0", 1)) { + pacat_log("Failed to write QubesDB %s: %s", u->qdb_status_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 }; + int rec_allowed; - 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"); + // QubesDB mic status: it allows to retrieve the daemon status when mic is allowed + if (asprintf(&u->qdb_status_path, "/audio-input/%s", u->name) < 0) { + pacat_log("QubesDB path setup failed: %s", strerror(errno)); + u->qdb_config_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)); + // QubesDB mic requested: it allows to know if an application has requested the mic + if (asprintf(&u->qdb_request_path, "/audio-input-request/%s", u->name) < 0) { + pacat_log("QubesDB path setup failed: %s", strerror(errno)); + u->qdb_config_path = NULL; goto fail; } - if (listen(socket_fd, 5) == -1) { - pacat_log("listen on %s failed: %s", addr.sun_path, strerror(errno)); + // QubesDB mic allowed: set authorization for using mic + if (asprintf(&u->qdb_config_path, "/audio-input-config/%s", u->name) < 0) { + pacat_log("QubesDB path setup failed: %s", strerror(errno)); + u->qdb_config_path = NULL; goto fail; } - - u->control_socket_event = u->mainloop_api->io_new(u->mainloop_api, - socket_fd, PA_IO_EVENT_INPUT, control_socket_callback, u); - if (!u->control_socket_event) { - pacat_log("io_new control failed"); + // Setup a QubesDB watch to get authorization on demand + if (!qdb_watch(u->qdb, u->qdb_config_path)) { + pacat_log("failed to setup watch on %s: %m\n", u->qdb_config_path); goto fail; } - u->qdb = qdb_open(NULL); - if (!u->qdb) { - pacat_log("qdb_open failed: %s", strerror(errno)); + socket_fd = qdb_watch_fd(u->qdb); + if (socket_fd < 0) 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; + rec_allowed = is_rec_allowed_from_qdb(u); + if (rec_allowed >= 0) { + pacat_log("mic allowed: initial value read from Qubes DB '%d'", rec_allowed); + u->rec_allowed = rec_allowed; } - if (!qdb_write(u->qdb, u->qdb_path, "0", 1)) { - pacat_log("qdb_write failed: %s", strerror(errno)); + u->control_socket_event = u->mainloop_api->io_new(u->mainloop_api, + socket_fd, PA_IO_EVENT_INPUT, control_socket_callback, u); + if (!u->control_socket_event) { + pacat_log("io_new control failed"); goto fail; } @@ -935,9 +931,15 @@ static int setup_control(struct userdata *u) { return 0; fail: - if (u->qdb_path) - free(u->qdb_path); - u->qdb_path = NULL; + if (u->qdb_config_path) + free(u->qdb_config_path); + u->qdb_config_path = NULL; + if (u->qdb_status_path) + free(u->qdb_status_path); + u->qdb_status_path = NULL; + if (u->qdb_request_path) + free(u->qdb_request_path); + u->qdb_request_path = NULL; if (u->qdb) qdb_close(u->qdb); u->qdb = NULL; @@ -956,10 +958,12 @@ 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_config_path) + free(u->qdb_config_path); + if (u->qdb_status_path) + free(u->qdb_status_path); + if (u->qdb_request_path) + free(u->qdb_request_path); if (u->qdb) qdb_close(u->qdb); } diff --git a/pulse/pacat-simple-vchan.h b/pulse/pacat-simple-vchan.h index 2612ffa7..f117eb88 100644 --- a/pulse/pacat-simple-vchan.h +++ b/pulse/pacat-simple-vchan.h @@ -32,7 +32,9 @@ struct userdata { GMutex prop_mutex; qdb_handle_t qdb; - char *qdb_path; + char *qdb_config_path; + char *qdb_status_path; + char *qdb_request_path; int control_socket_fd; pa_io_event* control_socket_event; bool rec_allowed; diff --git a/pulse/qubes.AudioInputDisable b/pulse/qubes.AudioInputDisable deleted file mode 100755 index 7c82eb5f..00000000 --- a/pulse/qubes.AudioInputDisable +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -[ -n "$1" ] || exit 1 -control_socket="/var/run/qubes/audio-control.$1" -echo "audio-input 0" | socat -u - "UNIX-CONNECT:$control_socket" diff --git a/pulse/qubes.AudioInputEnable b/pulse/qubes.AudioInputEnable deleted file mode 100755 index 150f5af9..00000000 --- a/pulse/qubes.AudioInputEnable +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -[ -n "$1" ] || exit 1 -control_socket="/var/run/qubes/audio-control.$1" -echo "audio-input 1" | socat -u - "UNIX-CONNECT:$control_socket" diff --git a/qubesguidaemon/mic.py b/qubesguidaemon/mic.py index e5006bde..ae340e74 100644 --- a/qubesguidaemon/mic.py +++ b/qubesguidaemon/mic.py @@ -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" ) @@ -95,11 +96,11 @@ def on_device_list_attached_mic(self, vm, event, persistent=None): return untrusted_audio_input = audiovm.untrusted_qdb.read( - "/audio-input/{}".format(vm.name) + "/audio-input-config/{}".format(vm.name) ) 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): @@ -123,15 +124,23 @@ 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-config/{}".format(vm.name), "1" ) # pylint: disable=unused-argument @@ -153,13 +162,72 @@ 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-config/{}".format(vm.name), "0" + ) + + @qubes.ext.handler("property-set:audiovm") + def on_property_set(self, subject, event, name, newvalue, oldvalue=None): + if not subject.is_running() or not newvalue: + return + if not newvalue.is_running(): + subject.log.warning( + "Cannot attach mic to {!s}: " + "AudioVM '{!s}' is powered off.".format(subject, newvalue) + ) + if newvalue == oldvalue: + return + if oldvalue and oldvalue.is_running(): + mic_allowed = oldvalue.untrusted_qdb.read( + "/audio-input-config/{}".format(subject.name) + ) + if mic_allowed is None: + return + try: + mic_allowed_value = mic_allowed.decode("ascii") + except UnicodeError: + raise qubes.exc.QubesVMError( + subject, + "Cannot decode ASCII value for '/audio-input-config/{!s}'".format( + subject.name + ), + ) + if mic_allowed_value in ("0", "1"): + newvalue.untrusted_qdb.write( + "/audio-input-config/{}".format(subject.name), + mic_allowed_value, + ) + else: + raise qubes.exc.QubesVMError( + subject, + "Invalid value '{!s}' for '/audio-input-config/{!s}' from {!s}".format( + mic_allowed_value, subject.name, oldvalue + ), + ) + + @qubes.ext.handler("domain-qdb-create") + def on_domain_qdb_create(self, vm, event): + if vm.audiovm and vm.audiovm.is_running(): + # Remove previous config, status and request entries on audiovm start + vm.audiovm.untrusted_qdb.rm( + "/audio-input-config/{}".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), + vm.audiovm.untrusted_qdb.rm("/audio-input/{}".format(vm.name)) + vm.audiovm.untrusted_qdb.rm( + "/audio-input-request/{}".format(vm.name) ) diff --git a/rpm_spec/gui-daemon.spec.in b/rpm_spec/gui-daemon.spec.in index bc5288d4..961a0880 100644 --- a/rpm_spec/gui-daemon.spec.in +++ b/rpm_spec/gui-daemon.spec.in @@ -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 diff --git a/window-icon-updater/Makefile b/window-icon-updater/Makefile index 0e8009ba..295ccbef 100644 --- a/window-icon-updater/Makefile +++ b/window-icon-updater/Makefile @@ -3,6 +3,7 @@ all: install: install -D icon-receiver $(DESTDIR)/usr/lib/qubes/icon-receiver + install -d $(DESTDIR)/etc/qubes-rpc ln -s /var/run/qubes/icon-receiver.sock $(DESTDIR)/etc/qubes-rpc/qubes.WindowIconUpdater install -d $(DESTDIR)/etc/qubes/rpc-config install -m 0664 -D qubes.WindowIconUpdater.config $(DESTDIR)/etc/qubes/rpc-config/qubes.WindowIconUpdater