Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use QubesDB for knowing if mic is allowed or not #145

Merged
merged 2 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ variables:

lint:
extends: .lint
script:
- black $BLACK_ARGS qubesguidaemon
variables:
DIR: qubesguidaemon
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
184 changes: 94 additions & 90 deletions pulse/pacat-simple-vchan.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
fepitre marked this conversation as resolved.
Show resolved Hide resolved
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;
Expand All @@ -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);
fepitre marked this conversation as resolved.
Show resolved Hide resolved
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);
}
Expand Down
4 changes: 3 additions & 1 deletion pulse/pacat-simple-vchan.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
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.

Loading