forked from Mirrors/flatpak-builder
document portal: Implement the new api
parent
8bada3a819
commit
89a10ad9b2
|
@ -13,6 +13,7 @@
|
|||
#include <gio/gio.h>
|
||||
#include <gio/gunixfdlist.h>
|
||||
#include "xdp-dbus.h"
|
||||
#include "xdp-impl-dbus.h"
|
||||
#include "xdp-util.h"
|
||||
#include "flatpak-db.h"
|
||||
#include "flatpak-dbus.h"
|
||||
|
@ -122,14 +123,16 @@ portal_grant_permissions (GDBusMethodInvocation *invocation,
|
|||
entry = flatpak_db_lookup (db, id);
|
||||
if (entry == NULL)
|
||||
{
|
||||
g_dbus_method_invocation_return_error (invocation, FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_FOUND,
|
||||
g_dbus_method_invocation_return_error (invocation,
|
||||
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_FOUND,
|
||||
"No such document: %s", id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!flatpak_is_valid_name (target_app_id))
|
||||
{
|
||||
g_dbus_method_invocation_return_error (invocation, FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
|
||||
g_dbus_method_invocation_return_error (invocation,
|
||||
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
|
||||
"Invalid app name: %s", target_app_id);
|
||||
return;
|
||||
}
|
||||
|
@ -140,7 +143,8 @@ portal_grant_permissions (GDBusMethodInvocation *invocation,
|
|||
if (!xdp_entry_has_permissions (entry, app_id,
|
||||
XDP_PERMISSION_FLAGS_GRANT_PERMISSIONS | perms))
|
||||
{
|
||||
g_dbus_method_invocation_return_error (invocation, FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_ALLOWED,
|
||||
g_dbus_method_invocation_return_error (invocation,
|
||||
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_ALLOWED,
|
||||
"Not enough permissions");
|
||||
return;
|
||||
}
|
||||
|
@ -175,14 +179,16 @@ portal_revoke_permissions (GDBusMethodInvocation *invocation,
|
|||
entry = flatpak_db_lookup (db, id);
|
||||
if (entry == NULL)
|
||||
{
|
||||
g_dbus_method_invocation_return_error (invocation, FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_FOUND,
|
||||
g_dbus_method_invocation_return_error (invocation,
|
||||
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_FOUND,
|
||||
"No such document: %s", id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!flatpak_is_valid_name (target_app_id))
|
||||
{
|
||||
g_dbus_method_invocation_return_error (invocation, FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
|
||||
g_dbus_method_invocation_return_error (invocation,
|
||||
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
|
||||
"Invalid app name: %s", target_app_id);
|
||||
return;
|
||||
}
|
||||
|
@ -194,7 +200,8 @@ portal_revoke_permissions (GDBusMethodInvocation *invocation,
|
|||
XDP_PERMISSION_FLAGS_GRANT_PERMISSIONS) ||
|
||||
strcmp (app_id, target_app_id) == 0)
|
||||
{
|
||||
g_dbus_method_invocation_return_error (invocation, FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_ALLOWED,
|
||||
g_dbus_method_invocation_return_error (invocation,
|
||||
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_ALLOWED,
|
||||
"Not enough permissions");
|
||||
return;
|
||||
}
|
||||
|
@ -228,14 +235,16 @@ portal_delete (GDBusMethodInvocation *invocation,
|
|||
entry = flatpak_db_lookup (db, id);
|
||||
if (entry == NULL)
|
||||
{
|
||||
g_dbus_method_invocation_return_error (invocation, FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_FOUND,
|
||||
g_dbus_method_invocation_return_error (invocation,
|
||||
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_FOUND,
|
||||
"No such document: %s", id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!xdp_entry_has_permissions (entry, app_id, XDP_PERMISSION_FLAGS_DELETE))
|
||||
{
|
||||
g_dbus_method_invocation_return_error (invocation, FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_ALLOWED,
|
||||
g_dbus_method_invocation_return_error (invocation,
|
||||
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_ALLOWED,
|
||||
"Not enough permissions");
|
||||
return;
|
||||
}
|
||||
|
@ -317,6 +326,69 @@ do_create_doc (struct stat *parent_st_buf, const char *path, gboolean reuse_exis
|
|||
return id;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
validate_fd (int fd,
|
||||
struct stat *st_buf,
|
||||
struct stat *real_parent_st_buf,
|
||||
char *path_buffer,
|
||||
GError **error)
|
||||
{
|
||||
g_autofree char *proc_path = NULL;
|
||||
ssize_t symlink_size;
|
||||
g_autofree char *dirname = NULL;
|
||||
g_autofree char *name = NULL;
|
||||
int fd_flags;
|
||||
glnx_fd_close int dir_fd = -1;
|
||||
struct stat real_st_buf;
|
||||
|
||||
proc_path = g_strdup_printf ("/proc/self/fd/%d", fd);
|
||||
|
||||
if (fd == -1 ||
|
||||
/* Must be able to get fd flags */
|
||||
(fd_flags = fcntl (fd, F_GETFL)) == -1 ||
|
||||
/* Must be O_PATH */
|
||||
((fd_flags & O_PATH) != O_PATH) ||
|
||||
/* Must not be O_NOFOLLOW (because we want the target file) */
|
||||
((fd_flags & O_NOFOLLOW) == O_PATH) ||
|
||||
/* Must be able to fstat */
|
||||
fstat (fd, st_buf) < 0 ||
|
||||
/* Must be a regular file */
|
||||
(st_buf->st_mode & S_IFMT) != S_IFREG ||
|
||||
/* Must be able to read path from /proc/self/fd */
|
||||
/* This is an absolute and (at least at open time) symlink-expanded path */
|
||||
(symlink_size = readlink (proc_path, path_buffer, PATH_MAX)) < 0)
|
||||
{
|
||||
g_set_error (error,
|
||||
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
|
||||
"Invalid fd passed");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
path_buffer[symlink_size] = 0;
|
||||
|
||||
/* We open the parent directory and do the stat in that, so that we have
|
||||
* trustworthy parent dev/ino for later verification. Otherwise the caller
|
||||
* could later replace a parent with a symlink and make us read some other file
|
||||
*/
|
||||
dirname = g_path_get_dirname (path_buffer);
|
||||
name = g_path_get_basename (path_buffer);
|
||||
dir_fd = open (dirname, O_CLOEXEC | O_PATH);
|
||||
|
||||
if (fstat (dir_fd, real_parent_st_buf) < 0 ||
|
||||
fstatat (dir_fd, name, &real_st_buf, AT_SYMLINK_NOFOLLOW) < 0 ||
|
||||
st_buf->st_dev != real_st_buf.st_dev ||
|
||||
st_buf->st_ino != real_st_buf.st_ino)
|
||||
{
|
||||
/* Don't leak any info about real file path existence, etc */
|
||||
g_set_error (error,
|
||||
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
|
||||
"Invalid fd passed");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
portal_add (GDBusMethodInvocation *invocation,
|
||||
GVariant *parameters,
|
||||
|
@ -325,16 +397,12 @@ portal_add (GDBusMethodInvocation *invocation,
|
|||
GDBusMessage *message;
|
||||
GUnixFDList *fd_list;
|
||||
g_autofree char *id = NULL;
|
||||
g_autofree char *proc_path = NULL;
|
||||
int fd_id, fd, fds_len, fd_flags;
|
||||
glnx_fd_close int dir_fd = -1;
|
||||
const int *fds;
|
||||
int fd_id, fd, fds_len;
|
||||
char path_buffer[PATH_MAX + 1];
|
||||
ssize_t symlink_size;
|
||||
struct stat st_buf, real_st_buf, real_parent_st_buf;
|
||||
g_autofree char *dirname = NULL;
|
||||
g_autofree char *name = NULL;
|
||||
const int *fds;
|
||||
struct stat st_buf, real_parent_st_buf;
|
||||
gboolean reuse_existing, persistent;
|
||||
GError *error = NULL;
|
||||
|
||||
g_variant_get (parameters, "(hbb)", &fd_id, &reuse_existing, &persistent);
|
||||
|
||||
|
@ -349,52 +417,12 @@ portal_add (GDBusMethodInvocation *invocation,
|
|||
fd = fds[fd_id];
|
||||
}
|
||||
|
||||
proc_path = g_strdup_printf ("/proc/self/fd/%d", fd);
|
||||
|
||||
if (fd == -1 ||
|
||||
/* Must be able to get fd flags */
|
||||
(fd_flags = fcntl (fd, F_GETFL)) == -1 ||
|
||||
/* Must be O_PATH */
|
||||
((fd_flags & O_PATH) != O_PATH) ||
|
||||
/* Must not be O_NOFOLLOW (because we want the target file) */
|
||||
((fd_flags & O_NOFOLLOW) == O_PATH) ||
|
||||
/* Must be able to fstat */
|
||||
fstat (fd, &st_buf) < 0 ||
|
||||
/* Must be a regular file */
|
||||
(st_buf.st_mode & S_IFMT) != S_IFREG ||
|
||||
/* Must be able to read path from /proc/self/fd */
|
||||
/* This is an absolute and (at least at open time) symlink-expanded path */
|
||||
(symlink_size = readlink (proc_path, path_buffer, sizeof (path_buffer) - 1)) < 0)
|
||||
if (!validate_fd (fd, &st_buf, &real_parent_st_buf, path_buffer, &error))
|
||||
{
|
||||
g_dbus_method_invocation_return_error (invocation,
|
||||
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
|
||||
"Invalid fd passed");
|
||||
g_dbus_method_invocation_take_error (invocation, error);
|
||||
return;
|
||||
}
|
||||
|
||||
path_buffer[symlink_size] = 0;
|
||||
|
||||
/* We open the parent directory and do the stat in that, so that we have
|
||||
trustworthy parent dev/ino for later verification. Otherwise the caller
|
||||
could later replace a parent with a symlink and make us read some other file */
|
||||
dirname = g_path_get_dirname (path_buffer);
|
||||
name = g_path_get_basename (path_buffer);
|
||||
dir_fd = open (dirname, O_CLOEXEC | O_PATH);
|
||||
|
||||
if (fstat (dir_fd, &real_parent_st_buf) < 0 ||
|
||||
fstatat (dir_fd, name, &real_st_buf, AT_SYMLINK_NOFOLLOW) < 0 ||
|
||||
st_buf.st_dev != real_st_buf.st_dev ||
|
||||
st_buf.st_ino != real_st_buf.st_ino)
|
||||
{
|
||||
/* Don't leak any info about real file path existence, etc */
|
||||
g_dbus_method_invocation_return_error (invocation,
|
||||
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
|
||||
"Invalid fd passed");
|
||||
return;
|
||||
}
|
||||
|
||||
g_debug ("portal_add %s", path_buffer);
|
||||
|
||||
if (st_buf.st_dev == fuse_dev)
|
||||
{
|
||||
/* The passed in fd is on the fuse filesystem itself */
|
||||
|
@ -416,9 +444,10 @@ portal_add (GDBusMethodInvocation *invocation,
|
|||
AUTOLOCK (db);
|
||||
|
||||
/* If the entry doesn't exist anymore, fail. Also fail if not
|
||||
resuse_existing, because otherwise the user could use this to
|
||||
get a copy with permissions and thus escape later permission
|
||||
revocations */
|
||||
* reuse_existing, because otherwise the user could use this to
|
||||
* get a copy with permissions and thus escape later permission
|
||||
* revocations
|
||||
*/
|
||||
old_entry = flatpak_db_lookup (db, id);
|
||||
if (old_entry == NULL ||
|
||||
!reuse_existing)
|
||||
|
@ -492,7 +521,8 @@ portal_add_named (GDBusMethodInvocation *invocation,
|
|||
/* This is only allowed from the host, or else we could leak existence of files */
|
||||
if (*app_id != 0)
|
||||
{
|
||||
g_dbus_method_invocation_return_error (invocation, FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_ALLOWED,
|
||||
g_dbus_method_invocation_return_error (invocation,
|
||||
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_ALLOWED,
|
||||
"Not enough permissions");
|
||||
return;
|
||||
}
|
||||
|
@ -601,6 +631,182 @@ handle_get_mount_point (XdpDbusDocuments *object, GDBusMethodInvocation *invocat
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
portal_lookup (GDBusMethodInvocation *invocation,
|
||||
GVariant *parameters,
|
||||
const char *app_id)
|
||||
{
|
||||
const char *filename;
|
||||
char path_buffer[PATH_MAX + 1];
|
||||
int fd;
|
||||
struct stat st_buf, real_parent_st_buf;
|
||||
g_auto(GStrv) ids = NULL;
|
||||
g_autofree char *id = NULL;
|
||||
GError *error = NULL;
|
||||
|
||||
if (strcmp (app_id, "") != 0)
|
||||
{
|
||||
g_dbus_method_invocation_return_error (invocation,
|
||||
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_ALLOWED,
|
||||
"Not allowed in sandbox");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
g_variant_get (parameters, "(^&ay)", &filename);
|
||||
|
||||
fd = open (filename, O_PATH | O_CLOEXEC);
|
||||
if (fd == -1)
|
||||
{
|
||||
glnx_set_error_from_errno (&error);
|
||||
g_dbus_method_invocation_take_error (invocation, error);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (!validate_fd (fd, &st_buf, &real_parent_st_buf, path_buffer, &error))
|
||||
{
|
||||
g_dbus_method_invocation_take_error (invocation, error);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (st_buf.st_dev == fuse_dev)
|
||||
{
|
||||
/* The passed in fd is on the fuse filesystem itself */
|
||||
g_autoptr(FlatpakDbEntry) old_entry = NULL;
|
||||
|
||||
id = xdp_fuse_lookup_id_for_inode (st_buf.st_ino);
|
||||
g_debug ("path on fuse, id %s", id);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_autoptr(GVariant) data = NULL;
|
||||
|
||||
data = g_variant_ref_sink (g_variant_new ("(^ayttu)",
|
||||
path_buffer,
|
||||
(guint64)real_parent_st_buf.st_dev,
|
||||
(guint64)real_parent_st_buf.st_ino,
|
||||
0));
|
||||
ids = flatpak_db_list_ids_by_value (db, data);
|
||||
if (ids[0] != NULL)
|
||||
id = g_strdup (ids[0]);
|
||||
}
|
||||
|
||||
g_dbus_method_invocation_return_value (invocation,
|
||||
g_variant_new ("(s)", id ? id : ""));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GVariant *
|
||||
get_app_permissions (FlatpakDbEntry *entry)
|
||||
{
|
||||
g_autofree const char **apps = NULL;
|
||||
GVariantBuilder builder;
|
||||
int i;
|
||||
|
||||
apps = flatpak_db_entry_list_apps (entry);
|
||||
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sas}"));
|
||||
|
||||
for (i = 0; apps[i] != NULL; i++)
|
||||
{
|
||||
g_autofree const char **permissions = flatpak_db_entry_list_permissions (entry, apps[i]);
|
||||
g_variant_builder_add_value (&builder,
|
||||
g_variant_new ("{s^as}", apps[i], permissions));
|
||||
}
|
||||
|
||||
return g_variant_builder_end (&builder);
|
||||
}
|
||||
|
||||
static GVariant *
|
||||
get_path (FlatpakDbEntry *entry)
|
||||
{
|
||||
g_autoptr (GVariant) data = flatpak_db_entry_get_data (entry);
|
||||
const char *path;
|
||||
|
||||
g_variant_get (data, "(^ayttu)", &path, NULL, NULL, NULL);
|
||||
return g_variant_new_bytestring (path);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
portal_info (GDBusMethodInvocation *invocation,
|
||||
GVariant *parameters,
|
||||
const char *app_id)
|
||||
{
|
||||
const char *id = NULL;
|
||||
g_autoptr(FlatpakDbEntry) entry = NULL;
|
||||
|
||||
if (strcmp (app_id, "") != 0)
|
||||
{
|
||||
g_dbus_method_invocation_return_error (invocation,
|
||||
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_ALLOWED,
|
||||
"Not allowed in sandbox");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
g_variant_get (parameters, "(&s)", &id);
|
||||
|
||||
AUTOLOCK (db);
|
||||
|
||||
entry = flatpak_db_lookup (db, id);
|
||||
|
||||
if (!entry)
|
||||
{
|
||||
g_dbus_method_invocation_return_error (invocation,
|
||||
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
|
||||
"Invalid ID passed");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
g_dbus_method_invocation_return_value (invocation,
|
||||
g_variant_new ("(@ay@a{sas})",
|
||||
get_path (entry),
|
||||
get_app_permissions (entry)));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
portal_list (GDBusMethodInvocation *invocation,
|
||||
GVariant *parameters,
|
||||
const char *app_id)
|
||||
{
|
||||
g_auto(GStrv) ids = NULL;
|
||||
GVariantBuilder builder;
|
||||
int i;
|
||||
|
||||
if (strcmp (app_id, "") != 0)
|
||||
{
|
||||
g_dbus_method_invocation_return_error (invocation,
|
||||
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_NOT_ALLOWED,
|
||||
"Not allowed in sandbox");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
g_variant_get (parameters, "(&s)", &app_id);
|
||||
|
||||
AUTOLOCK (db);
|
||||
|
||||
if (strcmp (app_id, "") == 0)
|
||||
ids = flatpak_db_list_ids (db);
|
||||
else
|
||||
ids = flatpak_db_list_ids_by_app (db, app_id);
|
||||
|
||||
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{say}"));
|
||||
for (i = 0; ids[i]; i++)
|
||||
{
|
||||
g_autoptr(FlatpakDbEntry) entry = NULL;
|
||||
|
||||
entry = flatpak_db_lookup (db, ids[i]);
|
||||
|
||||
g_variant_builder_add (&builder, "{s@ay}", ids[i], get_path (entry));
|
||||
}
|
||||
|
||||
g_dbus_method_invocation_return_value (invocation,
|
||||
g_variant_new ("(@a{say})",
|
||||
g_variant_builder_end (&builder)));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
on_bus_acquired (GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
|
@ -617,6 +823,9 @@ on_bus_acquired (GDBusConnection *connection,
|
|||
g_signal_connect_swapped (helper, "handle-grant-permissions", G_CALLBACK (handle_method), portal_grant_permissions);
|
||||
g_signal_connect_swapped (helper, "handle-revoke-permissions", G_CALLBACK (handle_method), portal_revoke_permissions);
|
||||
g_signal_connect_swapped (helper, "handle-delete", G_CALLBACK (handle_method), portal_delete);
|
||||
g_signal_connect_swapped (helper, "handle-lookup", G_CALLBACK (handle_method), portal_lookup);
|
||||
g_signal_connect_swapped (helper, "handle-info", G_CALLBACK (handle_method), portal_info);
|
||||
g_signal_connect_swapped (helper, "handle-list", G_CALLBACK (handle_method), portal_list);
|
||||
|
||||
flatpak_connection_track_name_owners (connection);
|
||||
|
||||
|
@ -660,7 +869,7 @@ on_name_acquired (GDBusConnection *connection,
|
|||
g_autoptr(GError) error = NULL;
|
||||
struct stat stbuf;
|
||||
|
||||
g_debug ("org.freedesktop.portal.Documents acquired");
|
||||
g_debug ("%s acquired", name);
|
||||
|
||||
if (!xdp_fuse_init (&error))
|
||||
{
|
||||
|
@ -688,11 +897,17 @@ on_name_lost (GDBusConnection *connection,
|
|||
const gchar *name,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_debug ("org.freedesktop.portal.Documents lost");
|
||||
g_debug ("%s lost", name);
|
||||
final_exit_status = 20;
|
||||
g_main_loop_quit (loop);
|
||||
}
|
||||
|
||||
static void
|
||||
exit_handler (int sig)
|
||||
{
|
||||
g_main_loop_quit (loop);
|
||||
}
|
||||
|
||||
static void
|
||||
session_bus_closed (GDBusConnection *connection,
|
||||
gboolean remote_peer_vanished,
|
||||
|
@ -701,12 +916,6 @@ session_bus_closed (GDBusConnection *connection,
|
|||
g_main_loop_quit (loop);
|
||||
}
|
||||
|
||||
static void
|
||||
exit_handler (int sig)
|
||||
{
|
||||
g_main_loop_quit (loop);
|
||||
}
|
||||
|
||||
static int
|
||||
set_one_signal_handler (int sig,
|
||||
void (*handler)(int),
|
||||
|
|
Loading…
Reference in New Issue