Skip to content
Snippets Groups Projects
flatpak-run.c 166 KiB
Newer Older
/*
 * Cut-down version of common/flatpak-run.c from Flatpak
 * Last updated: Flatpak 1.12.7
 * Copyright © 2017-2022 Collabora Ltd.
 * Copyright © 2014-2022 Red Hat, Inc
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * 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 <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *       Alexander Larsson <alexl@redhat.com>
Simon McVittie's avatar
Simon McVittie committed
#include "config.h"

#include "flatpak-run-private.h"


#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <gio/gdesktopappinfo.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/vfs.h>
#include <sys/personality.h>
#include <grp.h>
#include <unistd.h>
#include <gio/gunixfdlist.h>
#if 0

#ifdef HAVE_DCONF
#include <dconf/dconf.h>
#endif
#ifdef HAVE_LIBMALCONTENT
#include <libmalcontent/malcontent.h>
#endif

#include "flatpak-syscalls-private.h"

#ifdef ENABLE_SECCOMP
#include <seccomp.h>
#endif

#endif

#ifdef ENABLE_XAUTH
#endif

#if 0
#include <glib/gi18n-lib.h>
#endif

#include "flatpak-utils-base-private.h"
#include "flatpak-utils-private.h"
#include "flatpak-systemd-dbus-generated.h"
#include "flatpak-error.h"
#define DEFAULT_SHELL "/bin/sh"
const char * const abs_usrmerged_dirs[] =
{
  "/bin",
  "/lib",
  "/lib32",
  "/lib64",
  "/sbin",
  NULL
};
const char * const *flatpak_abs_usrmerged_dirs = abs_usrmerged_dirs;

static char *
extract_unix_path_from_dbus_address (const char *address)
{
  const char *path, *path_end;

  if (address == NULL)
    return NULL;

  if (!g_str_has_prefix (address, "unix:"))
    return NULL;

  path = strstr (address, "path=");
  if (path == NULL)
    return NULL;
  path += strlen ("path=");
  path_end = path;
  while (*path_end != 0 && *path_end != ',')
    path_end++;

  return g_strndup (path, path_end - path);
}

/* This is part of the X11 protocol, so we can safely hard-code it here */
#define FamilyInternet6 (6)

#ifdef ENABLE_XAUTH
static gboolean
auth_streq (const char *str,
            const char *au_str,
{
  return au_len == strlen (str) && memcmp (str, au_str, au_len) == 0;
}

static gboolean
xauth_entry_should_propagate (const Xauth *xa,
                              int          family,
                              const char  *remote_hostname,
                              const char  *local_hostname,
  /* ensure entry isn't for a different type of access */
  if (family != FamilyWild && xa->family != family && xa->family != FamilyWild)
    return FALSE;

  /* ensure entry isn't for remote access, except that if remote_hostname
   * is specified, then remote access to that hostname is OK */
  if (xa->family != FamilyWild && xa->family != FamilyLocal &&
      (remote_hostname == NULL ||
       !auth_streq (remote_hostname, xa->address, xa->address_length)))
    return FALSE;

  /* ensure entry is for this machine */
  if (xa->family == FamilyLocal && !auth_streq (local_hostname, xa->address, xa->address_length))
    {
      /* OpenSUSE inherits the hostname value from DHCP without updating
       * its X11 authentication cookie. The old hostname value can still
       * be found in the environment variable XAUTHLOCALHOSTNAME.
       * For reference:
       * https://bugzilla.opensuse.org/show_bug.cgi?id=262309
       * For this reason if we have a cookie whose address is equal to the
       * variable XAUTHLOCALHOSTNAME, we still need to propagate it, but
       * we also need to change its address to `unames.nodename`.
       */
      const char *xauth_local_hostname;
      xauth_local_hostname = g_getenv ("XAUTHLOCALHOSTNAME");
      if (xauth_local_hostname == NULL)
        return FALSE;

      if (!auth_streq ((char *) xauth_local_hostname, xa->address, xa->address_length))
        return FALSE;
    }

  /* ensure entry is for this session */
  if (xa->number != NULL && !auth_streq (number, xa->number, xa->number_length))
    return FALSE;

  return TRUE;
}

static void
write_xauth (int family,
             const char *remote_host,
             const char *number,
{
  Xauth *xa, local_xa;
  char *filename;
  FILE *f;
  struct utsname unames;

  if (uname (&unames))
    {
      g_warning ("uname failed");
      return;
    }

  filename = XauFileName ();
  f = fopen (filename, "rb");
  if (f == NULL)
    return;

  while (TRUE)
    {
      xa = XauReadAuth (f);
      if (xa == NULL)
        break;
      if (xauth_entry_should_propagate (xa, family, remote_host,
                                        unames.nodename, number))
          if (local_xa.family == FamilyLocal &&
              !auth_streq (unames.nodename, local_xa.address, local_xa.address_length))
            {
              /* If we decided to propagate this cookie, but its address
               * doesn't match `unames.nodename`, we need to change it or
               * inside the container it will not work.
               */
              local_xa.address = unames.nodename;
              local_xa.address_length = strlen (local_xa.address);
            }

          if (!XauWriteAuth (output, &local_xa))
            g_warning ("xauth write error");
        }

      XauDisposeAuth (xa);
    }

  fclose (f);
}
#else /* !ENABLE_XAUTH */

/* When not doing Xauth, any distinct values will do, but use the same
 * ones Xauth does so that we can refer to them in our unit test. */
#define FamilyLocal (256)
#define FamilyWild (65535)

#endif /* !ENABLE_XAUTH */
 * @family: (out) (not optional):
 * @x11_socket: (out) (not optional):
 * @display_nr_out: (out) (not optional):
 */
gboolean
flatpak_run_parse_x11_display (const char  *display,
                               char       **display_nr_out,
                               GError     **error)
{
  const char *colon;
  const char *display_nr;
  const char *display_nr_end;

  /* Use the last ':', not the first, to cope with [::1]:0 */
  colon = strrchr (display, ':');

  if (colon == NULL)
    return glnx_throw (error, "No colon found in DISPLAY=%s", display);

  if (!g_ascii_isdigit (colon[1]))
    return glnx_throw (error, "Colon not followed by a digit in DISPLAY=%s", display);

  display_nr = &colon[1];
  display_nr_end = display_nr;

  while (g_ascii_isdigit (*display_nr_end))
    display_nr_end++;

  *display_nr_out = g_strndup (display_nr, display_nr_end - display_nr);

  if (display == colon || g_str_has_prefix (display, "unix:"))
      *x11_socket = g_strdup_printf ("/tmp/.X11-unix/X%s", *display_nr_out);
    }
  else if (display[0] == '[' && display[colon - display - 1] == ']')
    {
      *family = FamilyInternet6;
      *remote_host = g_strndup (display + 1, colon - display - 2);
    }
      *family = FamilyWild;
      *remote_host = g_strndup (display, colon - display);
flatpak_run_add_x11_args (FlatpakBwrap         *bwrap,
                          gboolean              allowed,
                          FlatpakContextShares  shares)
{
  g_autofree char *x11_socket = NULL;
  const char *display;
  g_autoptr(GError) local_error = NULL;

  /* Always cover /tmp/.X11-unix, that way we never see the host one in case
   * we have access to the host /tmp. If you request X access we'll put the right
   * thing in this anyway.
   *
   * We need to be a bit careful here, because there are two situations in
   * which potentially hostile processes have access to /tmp and could
   * create symlinks, which in principle could cause us to create the
   * directory and mount the tmpfs at the target of the symlink instead
   * of in the intended place:
   *
   * - With --filesystem=/tmp, it's the host /tmp - but because of the
   *   special historical status of /tmp/.X11-unix, we can assume that
   *   it is pre-created by the host system before user code gets to run.
   *
   * - When /tmp is shared between all instances of the same app ID,
   *   in principle the app has control over what's in /tmp, but in
   *   practice it can't interfere with /tmp/.X11-unix, because we do
   *   this unconditionally - therefore by the time app code runs,
   *   /tmp/.X11-unix is already a mount point, meaning the app cannot
   *   rename or delete it.
   */
  flatpak_bwrap_add_args (bwrap,
                          "--tmpfs", "/tmp/.X11-unix",
                          NULL);

  if (!allowed)
    {
      flatpak_bwrap_unset_env (bwrap, "DISPLAY");
      return;
    }

  g_debug ("Allowing x11 access");

  display = g_getenv ("DISPLAY");
      g_autofree char *remote_host = NULL;
      g_autofree char *display_nr = NULL;
      if (!flatpak_run_parse_x11_display (display, &family, &x11_socket,
                                          &local_error))
        {
          g_warning ("%s", local_error->message);
          flatpak_bwrap_unset_env (bwrap, "DISPLAY");
          return;
        }
      if (x11_socket != NULL
          && g_file_test (x11_socket, G_FILE_TEST_EXISTS))
          g_assert (g_str_has_prefix (x11_socket, "/tmp/.X11-unix/X"));
          flatpak_bwrap_add_args (bwrap,
                                  "--ro-bind", x11_socket, x11_socket,
          flatpak_bwrap_set_env (bwrap, "DISPLAY", display, TRUE);
        }
      else if ((shares & FLATPAK_CONTEXT_SHARED_NETWORK) == 0)
        {
          /* If DISPLAY is for example :42 but /tmp/.X11-unix/X42
           * doesn't exist, then the only way this is going to work
           * is if the app can connect to abstract socket
           * @/tmp/.X11-unix/X42 or to TCP port localhost:6042,
           * either of which requires a shared network namespace.
           *
           * Alternatively, if DISPLAY is othermachine:23, then we
           * definitely need access to TCP port othermachine:6023. */
          if (x11_socket != NULL)
            g_warning ("X11 socket %s does not exist in filesystem.",
                       x11_socket);
          else
            g_warning ("Remote X11 display detected.");
          g_warning ("X11 access will require --share=network permission.");
        }
      else if (x11_socket != NULL)
        {
          g_warning ("X11 socket %s does not exist in filesystem, "
                     "trying to use abstract socket instead.",
                     x11_socket);
        }
      else
        {
          flatpak_debug2 ("Assuming --share=network gives access to remote X11");
        }

#ifdef ENABLE_XAUTH
      g_auto(GLnxTmpfile) xauth_tmpf  = { 0, };

      if (glnx_open_anonymous_tmpfile_full (O_RDWR | O_CLOEXEC, "/tmp", &xauth_tmpf, NULL))
        {
          FILE *output = fdopen (xauth_tmpf.fd, "wb");
          if (output != NULL)
            {
              /* fd is now owned by output, steal it from the tmpfile */
              int tmp_fd = dup (glnx_steal_fd (&xauth_tmpf.fd));
              if (tmp_fd != -1)
                {
                  static const char dest[] = "/run/pressure-vessel/Xauthority";
                  write_xauth (family, remote_host, display_nr, output);
                  flatpak_bwrap_add_args_data_fd (bwrap, "--ro-bind-data", tmp_fd, dest);

                  flatpak_bwrap_set_env (bwrap, "XAUTHORITY", dest, TRUE);
                }

              fclose (output);

              if (tmp_fd != -1)
                lseek (tmp_fd, 0, SEEK_SET);
            }
        }
#endif
    }
  else
    {
      flatpak_bwrap_unset_env (bwrap, "DISPLAY");
    }
}

gboolean
flatpak_run_add_wayland_args (FlatpakBwrap *bwrap)
{
  const char *wayland_display;
  g_autofree char *user_runtime_dir = flatpak_get_real_xdg_runtime_dir ();
  g_autofree char *wayland_socket = NULL;
  g_autofree char *sandbox_wayland_socket = NULL;
  gboolean res = FALSE;
  struct stat statbuf;

  wayland_display = g_getenv ("WAYLAND_DISPLAY");
  if (!wayland_display)
    wayland_display = "wayland-0";

  if (wayland_display[0] == '/')
    wayland_socket = g_strdup (wayland_display);
  else
    wayland_socket = g_build_filename (user_runtime_dir, wayland_display, NULL);

  if (!g_str_has_prefix (wayland_display, "wayland-") ||
      strchr (wayland_display, '/') != NULL)
    {
      wayland_display = "wayland-0";
      flatpak_bwrap_set_env (bwrap, "WAYLAND_DISPLAY", wayland_display, TRUE);
    }

  sandbox_wayland_socket = g_strdup_printf ("/run/pressure-vessel/%s", wayland_display);

  if (stat (wayland_socket, &statbuf) == 0 &&
      (statbuf.st_mode & S_IFMT) == S_IFSOCK)
    {
      res = TRUE;
      flatpak_bwrap_add_args (bwrap,
                              "--ro-bind", wayland_socket, sandbox_wayland_socket,
                              NULL);
      flatpak_bwrap_add_runtime_dir_member (bwrap, wayland_display);
#if 0
static void
flatpak_run_add_ssh_args (FlatpakBwrap *bwrap)
{
  static const char sandbox_auth_socket[] = "/run/flatpak/ssh-auth";
  const char * auth_socket;

  auth_socket = g_getenv ("SSH_AUTH_SOCK");

  if (!auth_socket)
    return; /* ssh agent not present */

  if (!g_file_test (auth_socket, G_FILE_TEST_EXISTS))
    {
      /* Let's clean it up, so that the application will not try to connect */
      flatpak_bwrap_unset_env (bwrap, "SSH_AUTH_SOCK");
      return;
    }

  flatpak_bwrap_add_args (bwrap,
                          "--ro-bind", auth_socket, sandbox_auth_socket,
                          NULL);
  flatpak_bwrap_set_env (bwrap, "SSH_AUTH_SOCK", sandbox_auth_socket, TRUE);
}

static void
flatpak_run_add_pcsc_args (FlatpakBwrap *bwrap)
{
  const char * pcsc_socket;
  const char * sandbox_pcsc_socket = "/run/pcscd/pcscd.comm";

  pcsc_socket = g_getenv ("PCSCLITE_CSOCK_NAME");
  if (pcsc_socket)
    {
      if (!g_file_test (pcsc_socket, G_FILE_TEST_EXISTS))
        {
          flatpak_bwrap_unset_env (bwrap, "PCSCLITE_CSOCK_NAME");
          return;
        }
    }
  else
    {
      pcsc_socket = "/run/pcscd/pcscd.comm";
      if (!g_file_test (pcsc_socket, G_FILE_TEST_EXISTS))
        return;
    }

  flatpak_bwrap_add_args (bwrap,
                          "--ro-bind", pcsc_socket, sandbox_pcsc_socket,
                          NULL);
  flatpak_bwrap_set_env (bwrap, "PCSCLITE_CSOCK_NAME", sandbox_pcsc_socket, TRUE);
}

static gboolean
flatpak_run_cups_check_server_is_socket (const char *server)
{
  if (g_str_has_prefix (server, "/") && strstr (server, ":") == NULL)
    return TRUE;

  return FALSE;
}

/* Try to find a default server from a cups confguration file */
static char *
flatpak_run_get_cups_server_name_config (const char *path)
{
  g_autoptr(GFile) file = g_file_new_for_path (path);
  g_autoptr(GError) my_error = NULL;
  g_autoptr(GFileInputStream) input_stream = NULL;
  g_autoptr(GDataInputStream) data_stream = NULL;
  size_t len;

  input_stream = g_file_read (file, NULL, &my_error);
  if (my_error)
    {
      g_debug ("CUPS configuration file '%s': %s", path, my_error->message);
      return NULL;
    }

  data_stream = g_data_input_stream_new (G_INPUT_STREAM (input_stream));

  while (TRUE)
    {
      g_autofree char *line = g_data_input_stream_read_line (data_stream, &len, NULL, NULL);
      if (line == NULL)
        break;

      g_strchug (line);

      if ((*line  == '\0') || (*line == '#'))
        continue;

      g_auto(GStrv) tokens = g_strsplit (line, " ", 2);

      if ((tokens[0] != NULL) && (tokens[1] != NULL))
        {
          if (strcmp ("ServerName", tokens[0]) == 0)
            {
              g_strchug (tokens[1]);

              if (flatpak_run_cups_check_server_is_socket (tokens[1]))
                return g_strdup (tokens[1]);
            }
        }
    }

    return NULL;
}

static char *
flatpak_run_get_cups_server_name (void)
{
  g_autofree char * cups_server = NULL;
  g_autofree char * cups_config_path = NULL;

  /* TODO
   * we don't currently support cups servers located on the network, if such
   * server is detected, we simply ignore it and in the worst case we fallback
   * to the default socket
   */
  cups_server = g_strdup (g_getenv ("CUPS_SERVER"));
  if (cups_server && flatpak_run_cups_check_server_is_socket (cups_server))
    return g_steal_pointer (&cups_server);
  g_clear_pointer (&cups_server, g_free);

  cups_config_path = g_build_filename (g_get_home_dir (), ".cups/client.conf", NULL);
  cups_server = flatpak_run_get_cups_server_name_config (cups_config_path);
  if (cups_server && flatpak_run_cups_check_server_is_socket (cups_server))
    return g_steal_pointer (&cups_server);
  g_clear_pointer (&cups_server, g_free);

  cups_server = flatpak_run_get_cups_server_name_config ("/etc/cups/client.conf");
  if (cups_server && flatpak_run_cups_check_server_is_socket (cups_server))
    return g_steal_pointer (&cups_server);

  // Fallback to default socket
  return g_strdup ("/var/run/cups/cups.sock");
}

static void
flatpak_run_add_cups_args (FlatpakBwrap *bwrap)
{
  g_autofree char * sandbox_server_name = g_strdup ("/var/run/cups/cups.sock");
  g_autofree char * cups_server_name = flatpak_run_get_cups_server_name ();

  if (!g_file_test (cups_server_name, G_FILE_TEST_EXISTS))
    {
      g_debug ("Could not find CUPS server");
      return;
    }

  flatpak_bwrap_add_args (bwrap,
                          "--ro-bind", cups_server_name, sandbox_server_name,
                          NULL);
}
#endif

/* Try to find a default server from a pulseaudio confguration file */
static char *
flatpak_run_get_pulseaudio_server_user_config (const char *path)
{
  g_autoptr(GFile) file = g_file_new_for_path (path);
  g_autoptr(GError) my_error = NULL;
  g_autoptr(GFileInputStream) input_stream = NULL;
  g_autoptr(GDataInputStream) data_stream = NULL;
  size_t len;

  input_stream = g_file_read (file, NULL, &my_error);
  if (my_error)
    {
      g_debug ("Pulseaudio user configuration file '%s': %s", path, my_error->message);
      return NULL;
    }

  data_stream = g_data_input_stream_new (G_INPUT_STREAM (input_stream));

  while (TRUE)
    {
      g_autofree char *line = g_data_input_stream_read_line (data_stream, &len, NULL, NULL);
      if (line == NULL)
        break;

      g_strchug (line);

      if ((*line  == '\0') || (*line == ';') || (*line == '#'))
        continue;

      if (g_str_has_prefix (line, ".include "))
        {
          g_autofree char *rec_path = g_strdup (line + 9);
          g_strstrip (rec_path);
          char *found = flatpak_run_get_pulseaudio_server_user_config (rec_path);
          if (found)
            return found;
        }
      else if (g_str_has_prefix (line, "["))
        {
          return NULL;
        }
      else
        {
          g_auto(GStrv) tokens = g_strsplit (line, "=", 2);

          if ((tokens[0] != NULL) && (tokens[1] != NULL))
            {
              g_strchomp (tokens[0]);
              if (strcmp ("default-server", tokens[0]) == 0)
                {
                  g_strstrip (tokens[1]);
                  g_debug ("Found pulseaudio socket from configuration file '%s': %s", path, tokens[1]);
                  return g_strdup (tokens[1]);
                }
            }
        }
    }

  return NULL;
}

static char *
flatpak_run_get_pulseaudio_server (void)
{
  const char * pulse_clientconfig;
  char *pulse_server;
  g_autofree char *pulse_user_config = NULL;

  pulse_server = g_strdup (g_getenv ("PULSE_SERVER"));
  if (pulse_server)
    return pulse_server;

  pulse_clientconfig = g_getenv ("PULSE_CLIENTCONFIG");
  if (pulse_clientconfig)
    return flatpak_run_get_pulseaudio_server_user_config (pulse_clientconfig);

  pulse_user_config = g_build_filename (g_get_user_config_dir (), "pulse/client.conf", NULL);
  pulse_server = flatpak_run_get_pulseaudio_server_user_config (pulse_user_config);
  if (pulse_server)
    return pulse_server;

  pulse_server = flatpak_run_get_pulseaudio_server_user_config ("/etc/pulse/client.conf");
  if (pulse_server)
    return pulse_server;

  return NULL;
}

/*
 * Parse a PulseAudio server string, as documented on
 * https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/ServerStrings/.
 * Returns the first supported server address, or NULL if none are supported,
 * or NULL with @remote set if @value points to a remote server.
flatpak_run_parse_pulse_server (const char *value,
                                gboolean   *remote)
{
  g_auto(GStrv) servers = g_strsplit (value, " ", 0);
  gsize i;

  for (i = 0; servers[i] != NULL; i++)
    {
      const char *server = servers[i];
      if (g_str_has_prefix (server, "{"))
        {
          /*
           * TODO: compare the value within {} to the local hostname and D-Bus machine ID,
           * and skip if it matches neither.
           */
          const char * closing = strstr (server, "}");
          if (closing == NULL)
            continue;
          server = closing + 1;
        }
      if (g_str_has_prefix (server, "unix:"))
        return g_strdup (server + 5);
      if (server[0] == '/')
        return g_strdup (server);

      if (g_str_has_prefix (server, "tcp:"))
        {
          *remote = TRUE;
          return NULL;
        }
/*
 * Get the machine ID as used by PulseAudio. This is the systemd/D-Bus
 * machine ID, or failing that, the hostname.
 */
static char *
flatpak_run_get_pulse_machine_id (void)
{
  static const char * const machine_ids[] =
  {
    "/etc/machine-id",
    "/var/lib/dbus/machine-id",
  };
  gsize i;

  for (i = 0; i < G_N_ELEMENTS (machine_ids); i++)
    {
      g_autofree char *ret = NULL;

      if (g_file_get_contents (machine_ids[i], &ret, NULL, NULL))
        {
          gsize j;

          g_strstrip (ret);

          for (j = 0; ret[j] != '\0'; j++)
            {
              if (!g_ascii_isxdigit (ret[j]))
                break;
            }

          if (ret[0] != '\0' && ret[j] == '\0')
            return g_steal_pointer (&ret);
        }
    }

  return g_strdup (g_get_host_name ());
}

/*
 * Get the directory used by PulseAudio for its configuration.
 */
static char *
flatpak_run_get_pulse_home (void)
{
  /* Legacy path ~/.pulse is tried first, for compatibility */
  {
    const char *parent = g_get_home_dir ();
    g_autofree char *ret = g_build_filename (parent, ".pulse", NULL);

    if (g_file_test (ret, G_FILE_TEST_IS_DIR))
      return g_steal_pointer (&ret);
  }

  /* The more modern path, usually ~/.config/pulse */
  {
    const char *parent = g_get_user_config_dir ();
    /* Usually ~/.config/pulse */
    g_autofree char *ret = g_build_filename (parent, "pulse", NULL);

    if (g_file_test (ret, G_FILE_TEST_IS_DIR))
      return g_steal_pointer (&ret);
  }

  return NULL;
}

/*
 * Get the runtime directory used by PulseAudio for its socket.
 */
static char *
flatpak_run_get_pulse_runtime_dir (void)
{
  const char *val = NULL;

  val = g_getenv ("PULSE_RUNTIME_PATH");

  if (val != NULL)
    return realpath (val, NULL);

  {
    const char *user_runtime_dir = g_get_user_runtime_dir ();

    if (user_runtime_dir != NULL)
      {
        g_autofree char *dir = g_build_filename (user_runtime_dir, "pulse", NULL);

        if (g_file_test (dir, G_FILE_TEST_IS_DIR))
          return realpath (dir, NULL);
      }
  }

  {
    g_autofree char *pulse_home = flatpak_run_get_pulse_home ();
    g_autofree char *machine_id = flatpak_run_get_pulse_machine_id ();

    if (pulse_home != NULL && machine_id != NULL)
      {
        /* This is usually a symlink, but we take its realpath() anyway */
        g_autofree char *dir = g_strdup_printf ("%s/%s-runtime", pulse_home, machine_id);

        if (g_file_test (dir, G_FILE_TEST_IS_DIR))
          return realpath (dir, NULL);
      }
  }

  return NULL;
}

flatpak_run_add_pulseaudio_args (FlatpakBwrap         *bwrap,
                                 FlatpakContextShares  shares)
{
  g_autofree char *pulseaudio_server = flatpak_run_get_pulseaudio_server ();
  g_autofree char *pulseaudio_socket = NULL;
  g_autofree char *pulse_runtime_dir = flatpak_run_get_pulse_runtime_dir ();
    pulseaudio_socket = flatpak_run_parse_pulse_server (pulseaudio_server,
                                                        &remote);
  if (pulseaudio_socket == NULL && !remote)
    {
      pulseaudio_socket = g_build_filename (pulse_runtime_dir, "native", NULL);
      if (!g_file_test (pulseaudio_socket, G_FILE_TEST_EXISTS))
        g_clear_pointer (&pulseaudio_socket, g_free);
    }
  if (pulseaudio_socket == NULL && !remote)
      pulseaudio_socket = realpath ("/var/run/pulse/native", NULL);

      if (pulseaudio_socket && !g_file_test (pulseaudio_socket, G_FILE_TEST_EXISTS))
        g_clear_pointer (&pulseaudio_socket, g_free);
  flatpak_bwrap_unset_env (bwrap, "PULSE_SERVER");

  if (remote)
    {
      if ((shares & FLATPAK_CONTEXT_SHARED_NETWORK) == 0)
        {
          g_warning ("Remote PulseAudio server configured.");
          g_warning ("PulseAudio access will require --share=network permission.");
        }

      g_debug ("Using remote PulseAudio server \"%s\"", pulseaudio_server);
      flatpak_bwrap_set_env (bwrap, "PULSE_SERVER", pulseaudio_server, TRUE);
    }
  else if (pulseaudio_socket && g_file_test (pulseaudio_socket, G_FILE_TEST_EXISTS))
      static const char sandbox_socket_path[] = "/run/pressure-vessel/pulse/native";
      static const char pulse_server[] = "unix:/run/pressure-vessel/pulse/native";
      static const char config_path[] = "/run/pressure-vessel/pulse/config";
      gboolean share_shm = FALSE; /* TODO: When do we add this? */
      g_autofree char *client_config = g_strdup_printf ("enable-shm=%s\n", share_shm ? "yes" : "no");

      /* FIXME - error handling */
      if (!flatpak_bwrap_add_args_data (bwrap, "pulseaudio", client_config, -1, config_path, NULL))
        return;

      flatpak_bwrap_add_args (bwrap,
                              "--ro-bind", pulseaudio_socket, sandbox_socket_path,
                              NULL);

      flatpak_bwrap_set_env (bwrap, "PULSE_SERVER", pulse_server, TRUE);
      flatpak_bwrap_set_env (bwrap, "PULSE_CLIENTCONFIG", config_path, TRUE);
      flatpak_bwrap_add_runtime_dir_member (bwrap, "pulse");
    }
  else
    g_debug ("Could not find pulseaudio socket");

  /* Also allow ALSA access. This was added in 1.8, and is not ideally named. However,
   * since the practical permission of ALSA and PulseAudio are essentially the same, and
   * since we don't want to add more permissions for something we plan to replace with
   * portals/pipewire going forward we reinterpret pulseaudio to also mean ALSA.
   */
  if (!remote && g_file_test ("/dev/snd", G_FILE_TEST_IS_DIR))
    flatpak_bwrap_add_args (bwrap, "--dev-bind", "/dev/snd", "/dev/snd", NULL);
flatpak_run_add_resolved_args (FlatpakBwrap *bwrap)
{
  const char *resolved_socket = "/run/systemd/resolve/io.systemd.Resolve";

  if (g_file_test (resolved_socket, G_FILE_TEST_EXISTS))
    flatpak_bwrap_add_args (bwrap, "--bind", resolved_socket, resolved_socket, NULL);
}

flatpak_run_add_journal_args (FlatpakBwrap *bwrap)
{
  g_autofree char *journal_socket_socket = g_strdup ("/run/systemd/journal/socket");
  g_autofree char *journal_stdout_socket = g_strdup ("/run/systemd/journal/stdout");

  if (g_file_test (journal_socket_socket, G_FILE_TEST_EXISTS))
    {
      flatpak_bwrap_add_args (bwrap,
                              "--ro-bind", journal_socket_socket, journal_socket_socket,
                              NULL);
    }
  if (g_file_test (journal_stdout_socket, G_FILE_TEST_EXISTS))
    {
      flatpak_bwrap_add_args (bwrap,
                              "--ro-bind", journal_stdout_socket, journal_stdout_socket,
                              NULL);
    }
}

static char *
create_proxy_socket (char *template)
{
  g_autofree char *user_runtime_dir = flatpak_get_real_xdg_runtime_dir ();
  g_autofree char *proxy_socket_dir = g_build_filename (user_runtime_dir, ".dbus-proxy", NULL);
  g_autofree char *proxy_socket = g_build_filename (proxy_socket_dir, template, NULL);
  int fd;

  if (!glnx_shutil_mkdir_p_at (AT_FDCWD, proxy_socket_dir, 0755, NULL, NULL))
    return NULL;

  fd = g_mkstemp (proxy_socket);
  if (fd == -1)
    return NULL;

  close (fd);

  return g_steal_pointer (&proxy_socket);
}
#endif

/* Simplified from Flatpak: we never restrict access to the D-Bus system bus */
gboolean
flatpak_run_add_system_dbus_args (FlatpakBwrap   *app_bwrap)
{
  gboolean unrestricted;
  const char *dbus_address = g_getenv ("DBUS_SYSTEM_BUS_ADDRESS");
#if 0
  g_autofree char *real_dbus_address = NULL;
#endif
  g_autofree char *dbus_system_socket = NULL;

  unrestricted = TRUE;
  if (unrestricted)
    g_debug ("Allowing system-dbus access");

  if (dbus_address != NULL)
    dbus_system_socket = extract_unix_path_from_dbus_address (dbus_address);
  else if (g_file_test ("/var/run/dbus/system_bus_socket", G_FILE_TEST_EXISTS))
    dbus_system_socket = g_strdup ("/var/run/dbus/system_bus_socket");