diff --git a/.gitignore b/.gitignore index 5ada4804..63375d7f 100644 --- a/.gitignore +++ b/.gitignore @@ -33,15 +33,14 @@ gtk-doc.make flatpak flatpak-*.tar.xz flatpak-session-helper -xdg-document-portal -xdg-permission-store -flatpak-builder +flatpak-portal *~ profile/flatpak.sh flatpak-dbus.[ch] flatpak-document-dbus.[ch] flatpak-systemd-dbus.[ch] flatpak-resources.[ch] +flatpak-portal-dbus.[ch] flatpak-dbus-proxy permission-store-dbus.[ch] flatpak-system-helper diff --git a/Makefile.am b/Makefile.am index ce2508ff..d379d2bf 100644 --- a/Makefile.am +++ b/Makefile.am @@ -92,6 +92,7 @@ include data/Makefile.am.inc include app/Makefile.am.inc include lib/Makefile.am.inc include session-helper/Makefile.am.inc +include portal/Makefile.am.inc include system-helper/Makefile.am.inc include dbus-proxy/Makefile.am.inc include tests/Makefile.am.inc diff --git a/common/Makefile.am.inc b/common/Makefile.am.inc index 74db3b47..42bdb25b 100644 --- a/common/Makefile.am.inc +++ b/common/Makefile.am.inc @@ -54,8 +54,6 @@ libflatpak_common_la_SOURCES = \ common/flatpak-context.h \ common/flatpak-exports.c \ common/flatpak-exports.h \ - common/flatpak-portal-error.c \ - common/flatpak-portal-error.h \ common/flatpak-utils.c \ common/flatpak-utils.h \ common/flatpak-table-printer.c \ diff --git a/common/flatpak-utils.c b/common/flatpak-utils.c index 9f5ddd4c..e061e915 100644 --- a/common/flatpak-utils.c +++ b/common/flatpak-utils.c @@ -23,7 +23,6 @@ #include "flatpak-utils.h" #include "lib/flatpak-error.h" #include "flatpak-dir.h" -#include "flatpak-portal-error.h" #include "flatpak-oci-registry.h" #include "flatpak-run.h" diff --git a/data/Makefile.am.inc b/data/Makefile.am.inc index 9bfe6782..511b319c 100644 --- a/data/Makefile.am.inc +++ b/data/Makefile.am.inc @@ -1,10 +1,12 @@ introspectiondir = $(datadir)/dbus-1/interfaces introspection_DATA = \ data/org.freedesktop.Flatpak.xml \ + data/org.freedesktop.portal.Flatpak.xml \ $(NULL) EXTRA_DIST += \ data/org.freedesktop.portal.Documents.xml \ data/org.freedesktop.systemd1.xml \ data/org.freedesktop.Flatpak.xml \ + data/org.freedesktop.portal.Flatpak.xml \ $(NULL) diff --git a/data/org.freedesktop.portal.Flatpak.xml b/data/org.freedesktop.portal.Flatpak.xml new file mode 100644 index 00000000..a452b531 --- /dev/null +++ b/data/org.freedesktop.portal.Flatpak.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/portal/Makefile.am.inc b/portal/Makefile.am.inc new file mode 100644 index 00000000..a7736351 --- /dev/null +++ b/portal/Makefile.am.inc @@ -0,0 +1,39 @@ +libexec_PROGRAMS += \ + flatpak-portal \ + $(NULL) + +service_in_files += portal/flatpak-portal.service.in +systemduserunit_DATA += portal/flatpak-portal.service + +service_in_files += portal/org.freedesktop.portal.Flatpak.service.in +dbus_service_DATA += portal/org.freedesktop.portal.Flatpak.service + +portal_built_sources = portal/flatpak-portal-dbus.c portal/flatpak-portal-dbus.h + +portal/flatpak-portal-dbus.c: data/org.freedesktop.portal.Flatpak.xml Makefile + mkdir -p $(builddir)/portal + $(AM_V_GEN) $(GDBUS_CODEGEN) \ + --interface-prefix org.freedesktop.portal \ + --c-namespace Portal \ + --generate-c-code $(builddir)/portal/flatpak-portal-dbus \ + $(srcdir)/data/org.freedesktop.portal.Flatpak.xml \ + $(NULL) + +portal/%-dbus.h: portal/%-dbus.c + @true # Built as a side-effect of the rules for the .c + +flatpak_portal_SOURCES = \ + $(portal_built_sources) \ + portal/flatpak-portal.c \ + portal/flatpak-portal.h \ + portal/flatpak-portal-app-info.c \ + portal/flatpak-portal-app-info.h \ + portal/flatpak-portal-error.c \ + portal/flatpak-portal-error.h \ + $(NULL) + +BUILT_SOURCES += $(portal_built_sources) +CLEANFILES += $(portal_built_sources) + +flatpak_portal_LDADD = $(AM_LDADD) $(BASE_LIBS) +flatpak_portal_CFLAGS = $(AM_CFLAGS) $(BASE_CFLAGS) diff --git a/portal/flatpak-portal-app-info.c b/portal/flatpak-portal-app-info.c new file mode 100644 index 00000000..1776bd69 --- /dev/null +++ b/portal/flatpak-portal-app-info.c @@ -0,0 +1,225 @@ +/* + * Copyright © 2018 Red Hat, Inc + * + * This program 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. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Alexander Larsson + */ + +#include "config.h" + +#include +#include "libglnx/libglnx.h" +#include "flatpak-portal-app-info.h" +#include "flatpak-portal-error.h" + +G_LOCK_DEFINE (app_infos); +static GHashTable *app_infos; + +static void +ensure_app_infos (void) +{ + if (app_infos == NULL) + app_infos = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, (GDestroyNotify) g_key_file_unref); +} + +static GKeyFile * +lookup_cached_app_info_by_sender (const char *sender) +{ + GKeyFile *keyfile = NULL; + + G_LOCK (app_infos); + keyfile = g_hash_table_lookup (app_infos, sender); + if (keyfile) + g_key_file_ref (keyfile); + G_UNLOCK (app_infos); + + return keyfile; +} + +static void +invalidate_cached_app_info_by_sender (const char *sender) +{ + G_LOCK (app_infos); + g_hash_table_remove (app_infos, sender); + G_UNLOCK (app_infos); +} + +static void +add_cached_app_info_by_sender (const char *sender, GKeyFile *keyfile) +{ + G_LOCK (app_infos); + g_hash_table_add (app_infos, g_key_file_ref (keyfile)); + G_UNLOCK (app_infos); +} + + +/* Returns NULL on failure, keyfile with name "" if not sandboxed, and full app-info otherwise */ +static GKeyFile * +parse_app_id_from_fileinfo (int pid) +{ + g_autofree char *root_path = NULL; + glnx_autofd int root_fd = -1; + glnx_autofd int info_fd = -1; + struct stat stat_buf; + g_autoptr(GError) local_error = NULL; + g_autoptr(GMappedFile) mapped = NULL; + g_autoptr(GKeyFile) metadata = NULL; + + root_path = g_strdup_printf ("/proc/%u/root", pid); + root_fd = openat (AT_FDCWD, root_path, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY); + if (root_fd == -1) + { + /* Not able to open the root dir shouldn't happen. Probably the app died and + *we're failing due to /proc/$pid not existing. In that case fail instead + of treating this as privileged. */ + g_debug ("Unable to open %s", root_path); + return NULL; + } + + metadata = g_key_file_new (); + + info_fd = openat (root_fd, ".flatpak-info", O_RDONLY | O_CLOEXEC | O_NOCTTY); + if (info_fd == -1) + { + if (errno == ENOENT) + { + /* No file => on the host */ + g_key_file_set_string (metadata, FLATPAK_METADATA_GROUP_APPLICATION, + FLATPAK_METADATA_KEY_NAME, ""); + return g_steal_pointer (&metadata); + } + + return NULL; /* Some weird error => failure */ + } + + if (fstat (info_fd, &stat_buf) != 0 || !S_ISREG (stat_buf.st_mode)) + return NULL; /* Some weird fd => failure */ + + mapped = g_mapped_file_new_from_fd (info_fd, FALSE, &local_error); + if (mapped == NULL) + { + g_warning ("Can't map .flatpak-info file: %s", local_error->message); + return NULL; + } + + if (!g_key_file_load_from_data (metadata, + g_mapped_file_get_contents (mapped), + g_mapped_file_get_length (mapped), + G_KEY_FILE_NONE, &local_error)) + { + g_warning ("Can't load .flatpak-info file: %s", local_error->message); + return NULL; + } + + return g_steal_pointer (&metadata); +} + +GKeyFile * +flatpak_invocation_lookup_app_info (GDBusMethodInvocation *invocation, + GCancellable *cancellable, + GError **error) +{ + GDBusConnection *connection = g_dbus_method_invocation_get_connection (invocation); + const gchar *sender = g_dbus_method_invocation_get_sender (invocation); + g_autoptr(GDBusMessage) msg = NULL; + g_autoptr(GDBusMessage) reply = NULL; + g_autoptr(GVariantIter) iter = NULL; + const char *key; + GVariant *value; + GKeyFile *keyfile; + + keyfile = lookup_cached_app_info_by_sender (sender); + if (keyfile) + return keyfile; + + msg = g_dbus_message_new_method_call ("org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetConnectionCredentials"); + g_dbus_message_set_body (msg, g_variant_new ("(s)", sender)); + + reply = g_dbus_connection_send_message_with_reply_sync (connection, msg, + G_DBUS_SEND_MESSAGE_FLAGS_NONE, + 30000, + NULL, + cancellable, + error); + if (reply == NULL) + return NULL; + + if (g_dbus_message_get_message_type (reply) == G_DBUS_MESSAGE_TYPE_METHOD_RETURN) + { + GVariant *body = g_dbus_message_get_body (reply); + + g_variant_get (body, "(a{sv})", &iter); + while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) + { + if (strcmp (key, "ProcessID") == 0) + { + guint32 pid = g_variant_get_uint32 (value); + keyfile = parse_app_id_from_fileinfo (pid); + break; + } + } + } + + if (keyfile == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Can't find peer app id"); + return NULL; + } + + add_cached_app_info_by_sender (sender, keyfile); + + return keyfile; +} + +static void +name_owner_changed (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + const char *name, *from, *to; + + g_variant_get (parameters, "(sss)", &name, &from, &to); + + if (name[0] == ':' && + strcmp (name, from) == 0 && + strcmp (to, "") == 0) + { + invalidate_cached_app_info_by_sender (name); + } +} + +void +flatpak_connection_track_name_owners (GDBusConnection *connection) +{ + ensure_app_infos (); + g_dbus_connection_signal_subscribe (connection, + "org.freedesktop.DBus", + "org.freedesktop.DBus", + "NameOwnerChanged", + "/org/freedesktop/DBus", + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + name_owner_changed, + NULL, NULL); +} diff --git a/portal/flatpak-portal-app-info.h b/portal/flatpak-portal-app-info.h new file mode 100644 index 00000000..44fb039a --- /dev/null +++ b/portal/flatpak-portal-app-info.h @@ -0,0 +1,43 @@ +/* + * Copyright © 2018 Red Hat, Inc + * + * This program 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. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Alexander Larsson + */ + +#ifndef __FLATPAK_PORTAL_APP_INFO_H__ +#define __FLATPAK_PORTAL_APP_INFO_H__ + +#define FLATPAK_METADATA_GROUP_APPLICATION "Application" +#define FLATPAK_METADATA_GROUP_RUNTIME "Runtime" +#define FLATPAK_METADATA_GROUP_INSTANCE "Instance" +#define FLATPAK_METADATA_GROUP_CONTEXT "Context" +#define FLATPAK_METADATA_KEY_NAME "name" +#define FLATPAK_METADATA_KEY_ARCH "arch" +#define FLATPAK_METADATA_KEY_BRANCH "branch" +#define FLATPAK_METADATA_KEY_EXTRA_ARGS "extra-args" +#define FLATPAK_METADATA_KEY_APP_COMMIT "app-commit" +#define FLATPAK_METADATA_KEY_RUNTIME_COMMIT "runtime-commit" +#define FLATPAK_METADATA_KEY_SHARED "shared" +#define FLATPAK_METADATA_KEY_INSTANCE_PATH "instance-path" + +GKeyFile * flatpak_invocation_lookup_app_info (GDBusMethodInvocation *invocation, + GCancellable *cancellable, + GError **error); + +void flatpak_connection_track_name_owners (GDBusConnection *connection); + +#endif /* __FLATPAK_PORTAL_APP_INFO_H__ */ diff --git a/common/flatpak-portal-error.c b/portal/flatpak-portal-error.c similarity index 100% rename from common/flatpak-portal-error.c rename to portal/flatpak-portal-error.c diff --git a/common/flatpak-portal-error.h b/portal/flatpak-portal-error.h similarity index 100% rename from common/flatpak-portal-error.h rename to portal/flatpak-portal-error.h diff --git a/portal/flatpak-portal.c b/portal/flatpak-portal.c new file mode 100644 index 00000000..e181520c --- /dev/null +++ b/portal/flatpak-portal.c @@ -0,0 +1,810 @@ +/* + * Copyright © 2018 Red Hat, Inc + * + * This program 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. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Alexander Larsson + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include "libglnx/libglnx.h" +#include "flatpak-portal-dbus.h" +#include "flatpak-portal.h" +#include "flatpak-portal-app-info.h" +#include "flatpak-portal-error.h" + +#define IDLE_TIMEOUT_SECS 10*60 + +static GHashTable *client_pid_data_hash = NULL; +static GDBusConnection *session_bus = NULL; +static gboolean no_idle_exit = FALSE; +static guint name_owner_id = 0; +static GMainLoop *main_loop; +static PortalFlatpak *portal; +static gboolean opt_verbose; + +static void +skeleton_died_cb (gpointer data) +{ + g_debug ("skeleton finalized, exiting"); + g_main_loop_quit (main_loop); +} + +static gboolean +unref_skeleton_in_timeout_cb (gpointer user_data) +{ + static gboolean unreffed = FALSE; + g_debug ("unreffing portal main ref"); + if (!unreffed) + { + g_object_unref (portal); + unreffed = TRUE; + } + + return G_SOURCE_REMOVE; +} + +static void +unref_skeleton_in_timeout (void) +{ + if (name_owner_id) + g_bus_unown_name (name_owner_id); + name_owner_id = 0; + + /* After we've lost the name or idled we drop the main ref on the helper + so that we'll exit when it drops to zero. However, if there are + outstanding calls these will keep the refcount up during the + execution of them. We do the unref on a timeout to make sure + we're completely draining the queue of (stale) requests. */ + g_timeout_add (500, unref_skeleton_in_timeout_cb, NULL); +} + +static guint idle_timeout_id = 0; + +static gboolean +idle_timeout_cb (gpointer user_data) +{ + if (name_owner_id && g_hash_table_size (client_pid_data_hash) == 0) + { + g_debug ("Idle - unowning name"); + unref_skeleton_in_timeout (); + } + + idle_timeout_id = 0; + return G_SOURCE_REMOVE; +} + +G_LOCK_DEFINE_STATIC (idle); +static void +schedule_idle_callback (void) +{ + G_LOCK(idle); + + if (!no_idle_exit) + { + if (idle_timeout_id != 0) + g_source_remove (idle_timeout_id); + + idle_timeout_id = g_timeout_add_seconds (IDLE_TIMEOUT_SECS, idle_timeout_cb, NULL); + } + + G_UNLOCK(idle); +} + +typedef struct { + GPid pid; + char *client; + guint child_watch; +} PidData; + +static void +pid_data_free (PidData *data) +{ + g_free (data->client); + g_free (data); +} + +static void +child_watch_died (GPid pid, + gint status, + gpointer user_data) +{ + PidData *pid_data = user_data; + g_autoptr(GVariant) signal_variant = NULL; + + g_debug ("Client Pid %d died", pid_data->pid); + + signal_variant = g_variant_ref_sink (g_variant_new ("(uu)", pid, status)); + g_dbus_connection_emit_signal (session_bus, + pid_data->client, + "/org/freedesktop/portal/Flatpak", + "org.freedesktop.portal.Flatpak", + "SpawnExited", + signal_variant, + NULL); + + /* This frees the pid_data, so be careful */ + g_hash_table_remove (client_pid_data_hash, GUINT_TO_POINTER(pid_data->pid)); + + /* This might have caused us to go to idle (zero children) */ + schedule_idle_callback (); +} + +typedef struct { + int from; + int to; + int final; +} FdMapEntry; + +typedef struct { + FdMapEntry *fd_map; + int fd_map_len; + gboolean set_tty; + int tty; +} ChildSetupData; + +static void +child_setup_func (gpointer user_data) +{ + ChildSetupData *data = (ChildSetupData *)user_data; + FdMapEntry *fd_map = data->fd_map; + sigset_t set; + int i; + + /* Unblock all signals */ + sigemptyset (&set); + if (pthread_sigmask (SIG_SETMASK, &set, NULL) == -1) + { + g_error ("Failed to unblock signals when starting child"); + return; + } + + /* Reset the handlers for all signals to their defaults. */ + for (i = 1; i < NSIG; i++) + { + if (i != SIGSTOP && i != SIGKILL) + signal (i, SIG_DFL); + } + + for (i = 0; i < data->fd_map_len; i++) + { + if (fd_map[i].from != fd_map[i].to) + { + dup2 (fd_map[i].from, fd_map[i].to); + close (fd_map[i].from); + } + } + + /* Second pass in case we needed an inbetween fd value to avoid conflicts */ + for (i = 0; i < data->fd_map_len; i++) + { + if (fd_map[i].to != fd_map[i].final) + { + dup2 (fd_map[i].to, fd_map[i].final); + close (fd_map[i].to); + } + } + + /* We become our own session and process group, because it never makes sense + to share the flatpak-session-helper dbus activated process group */ + setsid (); + setpgid (0, 0); + + if (data->set_tty) + { + /* data->tty is our from fd which is closed at this point. + * so locate the destnation fd and use it for the ioctl. + */ + for (i = 0; i < data->fd_map_len; i++) + { + if (fd_map[i].from == data->tty) + { + if (ioctl (fd_map[i].final, TIOCSCTTY, 0) == -1) + g_debug ("ioctl(%d, TIOCSCTTY, 0) failed: %s", + fd_map[i].final, strerror (errno)); + break; + } + } + } +} + +static gboolean +is_valid_expose (const char *expose) +{ + /* No subdirs or absolute paths */ + if (strchr (expose, '/')) + return FALSE; + + return TRUE; +} + +static gboolean +handle_spawn (PortalFlatpak *object, + GDBusMethodInvocation *invocation, + GUnixFDList *fd_list, + const gchar *arg_cwd_path, + const gchar *const *arg_argv, + GVariant *arg_fds, + GVariant *arg_envs, + guint arg_flags, + GVariant *arg_options) +{ + g_autoptr(GError) error = NULL; + ChildSetupData child_setup_data = { NULL }; + GPid pid; + PidData *pid_data; + gsize i, j, n_fds, n_envs; + const gint *fds; + g_autofree FdMapEntry *fd_map = NULL; + gchar **env; + gint32 max_fd; + GKeyFile *app_info; + g_autoptr(GPtrArray) flatpak_argv = g_ptr_array_new_with_free_func (g_free); + g_autofree char *app_id = NULL; + g_autofree char *branch = NULL; + g_autofree char *arch = NULL; + g_autofree char *app_commit = NULL; + g_autofree char *runtime_commit = NULL; + g_autofree char *instance_path = NULL; + g_auto(GStrv) extra_args = NULL; + g_auto(GStrv) shares = NULL; + g_auto(GStrv) sandbox_expose = NULL; + g_auto(GStrv) sandbox_expose_ro = NULL; + gboolean sandboxed; + + app_info = g_object_get_data (G_OBJECT (invocation), "app-info"); + g_assert (app_info != NULL); + + app_id = g_key_file_get_string (app_info, + FLATPAK_METADATA_GROUP_APPLICATION, + FLATPAK_METADATA_KEY_NAME, NULL); + g_assert (app_id != NULL); + branch = g_key_file_get_string (app_info, + FLATPAK_METADATA_GROUP_INSTANCE, + FLATPAK_METADATA_KEY_BRANCH, NULL); + instance_path = g_key_file_get_string (app_info, + FLATPAK_METADATA_GROUP_INSTANCE, + FLATPAK_METADATA_KEY_INSTANCE_PATH, NULL); + arch = g_key_file_get_string (app_info, + FLATPAK_METADATA_GROUP_INSTANCE, + FLATPAK_METADATA_KEY_ARCH, NULL); + extra_args = g_key_file_get_string_list (app_info, + FLATPAK_METADATA_GROUP_INSTANCE, + FLATPAK_METADATA_KEY_EXTRA_ARGS, NULL, NULL); + app_commit = g_key_file_get_string (app_info, + FLATPAK_METADATA_GROUP_INSTANCE, + FLATPAK_METADATA_KEY_APP_COMMIT, NULL); + runtime_commit = g_key_file_get_string (app_info, + FLATPAK_METADATA_GROUP_INSTANCE, + FLATPAK_METADATA_KEY_RUNTIME_COMMIT, NULL); + shares = g_key_file_get_string_list (app_info, FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_SHARED, NULL, NULL); + + g_debug ("spawn() called from app: %s", app_id); + + if (*app_id == 0) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "org.freedesktop.portal.Flatpak.Spawn only works in a flatpak"); + return TRUE; + } + + if (*arg_cwd_path == 0) + arg_cwd_path = NULL; + + if (arg_argv == NULL || *arg_argv == NULL) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "No command given"); + return TRUE; + } + + g_variant_lookup (arg_options, "sandbox-expose", "^as", &sandbox_expose); + g_variant_lookup (arg_options, "sandbox-expose-ro", "^as", &sandbox_expose_ro); + + if (instance_path == NULL && + ((sandbox_expose != NULL && sandbox_expose[0] != NULL) || + (sandbox_expose_ro != NULL && sandbox_expose_ro[0] != NULL))) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Invalid sandbox expose, caller has no instance path"); + return TRUE; + } + + for (i = 0; sandbox_expose != NULL && sandbox_expose[i] != NULL; i++) + { + const char *expose = sandbox_expose[i]; + g_debug ("exposing %s", expose); + if (!is_valid_expose (expose)) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Invalid sandbox expose"); + return TRUE; + } + } + + for (i = 0; sandbox_expose_ro != NULL && sandbox_expose_ro[i] != NULL; i++) + { + const char *expose = sandbox_expose_ro[i]; + g_debug ("exposing %s", expose); + if (!is_valid_expose (expose)) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Invalid sandbox expose"); + return TRUE; + } + } + + g_debug ("Running spawn command %s", arg_argv[0]); + + n_fds = 0; + fds = NULL; + if (fd_list != NULL) + { + n_fds = g_variant_n_children (arg_fds); + fds = g_unix_fd_list_peek_fds (fd_list, NULL); + } + fd_map = g_new0 (FdMapEntry, n_fds); + + child_setup_data.fd_map = fd_map; + child_setup_data.fd_map_len = n_fds; + + max_fd = -1; + for (i = 0; i < n_fds; i++) + { + gint32 handle, fd; + g_variant_get_child (arg_fds, i, "{uh}", &fd, &handle); + fd_map[i].to = fd; + fd_map[i].from = fds[i]; + fd_map[i].final = fd_map[i].to; + + /* If stdin/out/err is a tty we try to set it as the controlling + tty for the app, this way we can use this to run in a terminal. */ + if ((fd == 0 || fd == 1 || fd == 2) && + !child_setup_data.set_tty && + isatty (fds[i])) + { + child_setup_data.set_tty = TRUE; + child_setup_data.tty = fds[i]; + } + + max_fd = MAX (max_fd, fd_map[i].to); + max_fd = MAX (max_fd, fd_map[i].from); + } + + /* We make a second pass over the fds to find if any "to" fd index + overlaps an already in use fd (i.e. one in the "from" category + that are allocated randomly). If a fd overlaps "to" fd then its + a caller issue and not our fault, so we ignore that. */ + for (i = 0; i < n_fds; i++) + { + int to_fd = fd_map[i].to; + gboolean conflict = FALSE; + + /* At this point we're fine with using "from" values for this + value (because we handle to==from in the code), or values + that are before "i" in the fd_map (because those will be + closed at this point when dup:ing). However, we can't + reuse a fd that is in "from" for j > i. */ + for (j = i + 1; j < n_fds; j++) + { + int from_fd = fd_map[j].from; + if (from_fd == to_fd) + { + conflict = TRUE; + break; + } + } + + if (conflict) + fd_map[i].to = ++max_fd; + } + + if (arg_flags & FLATPAK_SPAWN_FLAGS_CLEAR_ENV) + { + char *empty[] = { NULL }; + env = g_strdupv (empty); + } + else + env = g_get_environ (); + + n_envs = g_variant_n_children (arg_envs); + for (i = 0; i < n_envs; i++) + { + const char *var = NULL; + const char *val = NULL; + g_variant_get_child (arg_envs, i, "{&s&s}", &var, &val); + + env = g_environ_setenv (env, var, val, TRUE); + } + + g_ptr_array_add (flatpak_argv, g_strdup ("flatpak")); + g_ptr_array_add (flatpak_argv, g_strdup ("run")); + + sandboxed = (arg_flags & FLATPAK_SPAWN_FLAGS_SANDBOX) != 0; + + if (sandboxed) + g_ptr_array_add (flatpak_argv, g_strdup ("--sandbox")); + else + { + for (i = 0; extra_args != NULL && extra_args[i] != NULL; i++) + g_ptr_array_add (flatpak_argv, g_strdup (extra_args[i])); + } + + /* Inherit launcher network access from launcher, unless + NO_NETWORK set. */ + if (shares != NULL && g_strv_contains ((const char * const *)shares, "network") && + !(arg_flags & FLATPAK_SPAWN_FLAGS_NO_NETWORK)) + g_ptr_array_add (flatpak_argv, g_strdup ("--share=network")); + else + g_ptr_array_add (flatpak_argv, g_strdup ("--unshare=network")); + + if (instance_path) + { + for (i = 0; sandbox_expose != NULL && sandbox_expose[i] != NULL; i++) + g_ptr_array_add (flatpak_argv, g_strdup_printf ("--filesystem=%s/sandbox/%s:rw", instance_path, sandbox_expose[i])); + for (i = 0; sandbox_expose_ro != NULL && sandbox_expose_ro[i] != NULL; i++) + g_ptr_array_add (flatpak_argv, g_strdup_printf ("--filesystem=%s/sandbox/%s:ro", instance_path, sandbox_expose_ro[i])); + } + + for (i = 0; sandbox_expose_ro != NULL && sandbox_expose_ro[i] != NULL; i++) + { + const char *expose = sandbox_expose_ro[i]; + g_debug ("exposing %s", expose); + } + + if ((arg_flags & FLATPAK_SPAWN_FLAGS_LATEST_VERSION) == 0) + { + if (app_commit) + g_ptr_array_add (flatpak_argv, g_strdup_printf ("--commit=%s", app_commit)); + if (runtime_commit) + g_ptr_array_add (flatpak_argv, g_strdup_printf ("--runtime-commit=%s", runtime_commit)); + } + + + if (arg_argv[0][0] != 0) + g_ptr_array_add (flatpak_argv, g_strdup_printf ("--command=%s", arg_argv[0])); + + g_ptr_array_add (flatpak_argv, g_strdup_printf ("%s/%s/%s", app_id, arch ? arch : "", branch ? branch : "")); + for (i = 1; arg_argv[i] != NULL; i++) + g_ptr_array_add (flatpak_argv, g_strdup (arg_argv[i])); + g_ptr_array_add (flatpak_argv, NULL); + + if (opt_verbose) + { + g_autoptr(GString) cmd = g_string_new (""); + int i; + + for (i = 0; flatpak_argv->pdata[i] != NULL; i++) + { + if (i > 0) + g_string_append (cmd, " "); + g_string_append (cmd, flatpak_argv->pdata[i]); + } + + g_debug ("Starting: %s\n", cmd->str); + } + + if (!g_spawn_async_with_pipes (arg_cwd_path, + (char **)flatpak_argv->pdata, + env, + G_SPAWN_SEARCH_PATH|G_SPAWN_DO_NOT_REAP_CHILD, + child_setup_func, &child_setup_data, + &pid, + NULL, + NULL, + NULL, + &error)) + { + gint code = G_DBUS_ERROR_FAILED; + if (g_error_matches (error, G_SPAWN_ERROR, G_SPAWN_ERROR_ACCES)) + code = G_DBUS_ERROR_ACCESS_DENIED; + else if (g_error_matches (error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT)) + code = G_DBUS_ERROR_FILE_NOT_FOUND; + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, code, + "Failed to start command: %s", + error->message); + return TRUE; + } + + pid_data = g_new0 (PidData, 1); + pid_data->pid = pid; + pid_data->client = g_strdup (g_dbus_method_invocation_get_sender (invocation)); + pid_data->child_watch = g_child_watch_add_full (G_PRIORITY_DEFAULT, + pid, + child_watch_died, + pid_data, + NULL); + + g_debug ("Client Pid is %d", pid_data->pid); + + g_hash_table_replace (client_pid_data_hash, GUINT_TO_POINTER(pid_data->pid), + pid_data); + + portal_flatpak_complete_spawn (object, invocation, NULL, pid); + return TRUE; +} + +static gboolean +handle_spawn_signal (PortalFlatpak *object, + GDBusMethodInvocation *invocation, + guint arg_pid, + guint arg_signal, + gboolean arg_to_process_group) +{ + PidData *pid_data = NULL; + + g_debug ("spawn_signal(%d %d)", arg_pid, arg_signal); + + pid_data = g_hash_table_lookup (client_pid_data_hash, GUINT_TO_POINTER(arg_pid)); + if (pid_data == NULL || + strcmp (pid_data->client, g_dbus_method_invocation_get_sender (invocation)) != 0) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN, + "No such pid"); + return TRUE; + } + + g_debug ("Sending signal %d to client pid %d", arg_signal, arg_pid); + + if (arg_to_process_group) + killpg (pid_data->pid, arg_signal); + else + kill (pid_data->pid, arg_signal); + + portal_flatpak_complete_spawn_signal (portal, invocation); + + return TRUE; +} + +static gboolean +authorize_method_handler (GDBusInterfaceSkeleton *interface, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + g_autoptr(GError) error = NULL; + g_autoptr(GKeyFile) keyfile = NULL; + g_autofree char *app_id = NULL; + + /* Ensure we don't idle exit */ + schedule_idle_callback (); + + keyfile = flatpak_invocation_lookup_app_info (invocation, NULL, &error); + if (keyfile == NULL) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, + "Authorization error: %s", error->message); + return FALSE; + } + + app_id = g_key_file_get_string (keyfile, + FLATPAK_METADATA_GROUP_APPLICATION, + FLATPAK_METADATA_KEY_NAME, &error); + if (app_id == NULL) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, + "Authorization error: %s", error->message); + return FALSE; + } + + g_object_set_data_full (G_OBJECT (invocation), "app-info", g_steal_pointer (&keyfile), (GDestroyNotify)g_key_file_unref); + + return TRUE; +} + +static void +on_bus_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + GError *error = NULL; + + g_debug ("Bus acquired, creating skeleton"); + + portal = portal_flatpak_skeleton_new (); + + g_object_set_data_full (G_OBJECT(portal), "track-alive", GINT_TO_POINTER(42), skeleton_died_cb); + + g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (portal), + G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD); + + portal_flatpak_set_version (PORTAL_FLATPAK (portal), 1); + g_signal_connect (portal, "handle-spawn", G_CALLBACK (handle_spawn), NULL); + g_signal_connect (portal, "handle-spawn-signal", G_CALLBACK (handle_spawn_signal), NULL); + + g_signal_connect (portal, "g-authorize-method", G_CALLBACK (authorize_method_handler), NULL); + + if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (portal), + connection, + "/org/freedesktop/portal/Flatpak", + &error)) + { + g_warning ("error: %s", error->message); + g_error_free (error); + } +} + +static void +on_name_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + g_debug ("Name acquired"); +} + +static void +on_name_lost (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + g_debug ("Name lost"); + unref_skeleton_in_timeout (); +} + +static void +binary_file_changed_cb (GFileMonitor *file_monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer data) +{ + static gboolean got_it = FALSE; + + if (!got_it) + { + g_debug ("binary file changed"); + unref_skeleton_in_timeout (); + } + + got_it = TRUE; +} + + +static void +message_handler (const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer user_data) +{ + /* Make this look like normal console output */ + if (log_level & G_LOG_LEVEL_DEBUG) + g_printerr ("F: %s\n", message); + else + g_printerr ("%s: %s\n", g_get_prgname (), message); +} + +int +main (int argc, + char **argv) +{ + gchar exe_path[PATH_MAX+1]; + ssize_t exe_path_len; + gboolean replace; + gboolean show_version; + GOptionContext *context; + GBusNameOwnerFlags flags; + g_autoptr(GError) error = NULL; + const GOptionEntry options[] = { + { "replace", 'r', 0, G_OPTION_ARG_NONE, &replace, "Replace old daemon.", NULL }, + { "verbose", 'v', 0, G_OPTION_ARG_NONE, &opt_verbose, "Enable debug output.", NULL }, + { "version", 0, 0, G_OPTION_ARG_NONE, &show_version, "Show program version.", NULL}, + { "no-idle-exit", 0, 0, G_OPTION_ARG_NONE, &no_idle_exit, "Don't exit when idle.", NULL }, + { NULL } + }; + + setlocale (LC_ALL, ""); + + g_setenv ("GIO_USE_VFS", "local", TRUE); + + g_set_prgname (argv[0]); + + g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, message_handler, NULL); + + context = g_option_context_new (""); + + replace = FALSE; + opt_verbose = FALSE; + show_version = FALSE; + + g_option_context_set_summary (context, "Flatpak portal"); + g_option_context_add_main_entries (context, options, GETTEXT_PACKAGE); + + if (!g_option_context_parse (context, &argc, &argv, &error)) + { + g_printerr ("%s: %s", g_get_application_name(), error->message); + g_printerr ("\n"); + g_printerr ("Try \"%s --help\" for more information.", + g_get_prgname ()); + g_printerr ("\n"); + g_option_context_free (context); + return 1; + } + + if (show_version) + { + g_print (PACKAGE_STRING "\n"); + return 0; + } + + if (opt_verbose) + g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, message_handler, NULL); + + client_pid_data_hash = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)pid_data_free); + + session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + if (session_bus == NULL) + { + g_printerr ("Can't find bus: %s\n", error->message); + return 1; + } + + exe_path_len = readlink ("/proc/self/exe", exe_path, sizeof (exe_path) - 1); + if (exe_path_len > 0) + { + exe_path[exe_path_len] = 0; + GFileMonitor *monitor; + g_autoptr(GFile) exe = NULL; + g_autoptr(GError) local_error = NULL; + + exe = g_file_new_for_path (exe_path); + monitor = g_file_monitor_file (exe, + G_FILE_MONITOR_NONE, + NULL, + &local_error); + if (monitor == NULL) + g_warning ("Failed to set watch on %s: %s", exe_path, error->message); + else + g_signal_connect (monitor, "changed", + G_CALLBACK (binary_file_changed_cb), NULL); + } + + flatpak_connection_track_name_owners (session_bus); + + flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT; + if (replace) + flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE; + + name_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, + "org.freedesktop.portal.Flatpak", + flags, + on_bus_acquired, + on_name_acquired, + on_name_lost, + NULL, + NULL); + + /* Ensure we don't idle exit */ + schedule_idle_callback (); + + main_loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (main_loop); + + return 0; +} diff --git a/portal/flatpak-portal.h b/portal/flatpak-portal.h new file mode 100644 index 00000000..da137d30 --- /dev/null +++ b/portal/flatpak-portal.h @@ -0,0 +1,31 @@ +/* + * Copyright © 2018 Red Hat, Inc + * + * This program 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. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Alexander Larsson + */ + +#ifndef __FLATPAK_PORTAL_H__ +#define __FLATPAK_PORTAL_H__ + +typedef enum { + FLATPAK_SPAWN_FLAGS_CLEAR_ENV = 1 << 0, + FLATPAK_SPAWN_FLAGS_LATEST_VERSION = 1 << 1, + FLATPAK_SPAWN_FLAGS_SANDBOX = 1 << 2, + FLATPAK_SPAWN_FLAGS_NO_NETWORK = 1 << 3, +} FlatpakSpawnFlags; + +#endif /* __FLATPAK_PORTAL_H__ */ diff --git a/portal/flatpak-portal.service.in b/portal/flatpak-portal.service.in new file mode 100644 index 00000000..a19431fa --- /dev/null +++ b/portal/flatpak-portal.service.in @@ -0,0 +1,7 @@ +[Unit] +Description=flatpak portal + +[Service] +BusName=org.freedesktop.portal.Flatpak +ExecStart=@libexecdir@/flatpak-portal +Type=dbus diff --git a/portal/org.freedesktop.portal.Flatpak.service.in b/portal/org.freedesktop.portal.Flatpak.service.in new file mode 100644 index 00000000..280e9b05 --- /dev/null +++ b/portal/org.freedesktop.portal.Flatpak.service.in @@ -0,0 +1,4 @@ +[D-BUS Service] +Name=org.freedesktop.portal.Flatpak +Exec=@libexecdir@/flatpak-portal +SystemdService=flatpak-portal.service