[gvfs/wip/cosimoc/admin: 73/86] Introduce an admin gvfs backend



commit a51f4bac29056300e5ab3d1c0590d0a5b13906a7
Author: Cosimo Cecchi <cosimoc gnome org>
Date:   Sat Oct 10 15:03:02 2015 -0400

    Introduce an admin gvfs backend
    
    The new admin backend is activated through pkexec and allows
    applications to promote their I/O operations to be privileged.
    Privilege checking is achieved through polkit.

 configure.ac                                    |   18 +
 daemon/Makefile.am                              |   35 +-
 daemon/admin.mount.in                           |    4 +
 daemon/daemon-main-generic.c                    |    3 +
 daemon/gvfsbackendadmin.c                       | 1060 +++++++++++++++++++++++
 daemon/gvfsbackendadmin.h                       |   61 ++
 daemon/org.gtk.vfs.file-operations.policy.in.in |   32 +
 daemon/org.gtk.vfs.file-operations.rules        |    8 +
 8 files changed, 1220 insertions(+), 1 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 0c43829..f226712 100644
--- a/configure.ac
+++ b/configure.ac
@@ -145,6 +145,24 @@ fi
 
 AC_SEARCH_LIBS([login_tty], [util], [AC_DEFINE([HAVE_LOGIN_TTY],[],[Whether login_tty is available])])
 
+dnl ***************************************************
+dnl *** Check if we should build with admin backend ***
+dnl ***************************************************
+AC_ARG_ENABLE([admin], [AS_HELP_STRING([--disable-admin],[build without admin backend])])
+msg_admin=no
+
+if test "x$enable_admin" != "xno"; then
+  PKG_CHECK_MODULES([ADMIN], [polkit-gobject-1], [msg_admin=yes])
+
+  if test "x$msg_admin" = "xyes"; then
+    AC_DEFINE([HAVE_ADMIN], 1, [Define to 1 if admin backend is going to be built])
+  fi
+fi
+
+AC_SUBST(ADMIN_CFLAGS)
+AC_SUBST(ADMIN_LIBS)
+AM_CONDITIONAL([HAVE_ADMIN], [test "$msg_admin" = "yes"])
+
 dnl **************************************************
 dnl *** Check if we should build with http backend ***
 dnl **************************************************
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index 08ddec7..22f0929 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -55,6 +55,12 @@ libexec_PROGRAMS=gvfsd gvfsd-sftp gvfsd-trash gvfsd-computer gvfsd-burn gvfsd-lo
 mount_in_files = sftp.mount.in ftp.mount.in ftps.mount.in trash.mount.in computer.mount.in burn.mount.in 
localtest.mount.in network.mount.in
 mount_DATA =  sftp.mount ftp.mount ftps.mount trash.mount computer.mount burn.mount localtest.mount 
network.mount
 
+mount_in_files += admin.mount.in
+if HAVE_ADMIN
+mount_DATA += admin.mount
+libexec_PROGRAMS += gvfsd-admin
+endif
+
 mount_in_files +=google.mount.in
 if USE_GOOGLE
 mount_DATA += google.mount
@@ -147,6 +153,7 @@ EXTRA_DIST =                                \
        $(gvfs_gschemas_dist)           \
        $(gvfs_gschemas_convert_dist)   \
        $(gsettings_ENUM_FILES)         \
+       org.gtk.vfs.file-operations.policy.in.in \
        $(NULL)
 
 DISTCLEANFILES = $(mount_DATA) $(noinst_DATA)
@@ -155,6 +162,8 @@ CLEANFILES =                                        \
        $(gsettings__enum_file)                 \
        $(service_DATA)                         \
        $(systemd_user_DATA)                    \
+       $(gvfs_polkit_actions_DATA)             \
+       $(gvfs_polkit_actions_in_files)         \
        *.gschema.valid
 
 noinst_PROGRAMS =                              \
@@ -449,6 +458,20 @@ gvfsd_cdda_LDADD = $(libraries) $(CDDA_LIBS) $(HAL_LIBS) \
                   $(top_builddir)/common/libgvfscommon-hal.la
 endif
 
+gvfsd_admin_SOURCES = \
+       gvfsbackendadmin.c gvfsbackendadmin.h \
+       daemon-main.c daemon-main.h \
+       daemon-main-generic.c
+
+gvfsd_admin_CPPFLAGS = \
+       $(flags) \
+       -DBACKEND_HEADER=gvfsbackendadmin.h \
+       -DDEFAULT_BACKEND_TYPE=admin \
+       -DBACKEND_TYPES='"admin", G_VFS_TYPE_BACKEND_ADMIN,' \
+       $(ADMIN_CFLAGS)
+
+gvfsd_admin_LDADD = $(libraries) $(ADMIN_LIBS)
+
 gvfsd_google_SOURCES = \
        gvfsbackendgoogle.c gvfsbackendgoogle.h \
        daemon-main.c daemon-main.h \
@@ -628,7 +651,6 @@ gvfsd_nfs_CPPFLAGS = \
 
 gvfsd_nfs_LDADD = $(libraries) $(NFS_LIBS)
 
-
 # GSettings stuff
 gsettings_ENUM_NAMESPACE = org.gnome.system.gvfs
 gsettings_ENUM_FILES = $(top_srcdir)/daemon/gvfs-enums.h
@@ -639,3 +661,14 @@ gsettings_SCHEMAS = $(gvfs_gschemas)
 
 gvfs_gschemas_convertdir = $(datadir)/GConf/gsettings
 gvfs_gschemas_convert_DATA = $(gvfs_gschemas_convert)
+
+org.gtk.vfs.file-operations.policy.in: org.gtk.vfs.file-operations.policy.in.in Makefile
+       $(AM_V_GEN) sed -e "s|\ libexecdir\@|$(libexecdir)|" $< > $@
+
+ INTLTOOL_POLICY_RULE@
+gvfs_polkit_actionsdir = $(datadir)/polkit-1/actions
+gvfs_polkit_actions_in_files = org.gtk.vfs.file-operations.policy.in
+gvfs_polkit_actions_DATA = org.gtk.vfs.file-operations.policy
+
+gvfs_polkit_rulesdir = $(datadir)/polkit-1/rules.d
+dist_gvfs_polkit_rules_DATA = org.gtk.vfs.file-operations.rules
diff --git a/daemon/admin.mount.in b/daemon/admin.mount.in
new file mode 100644
index 0000000..0996a9e
--- /dev/null
+++ b/daemon/admin.mount.in
@@ -0,0 +1,4 @@
+[Mount]
+Type=admin
+Exec=/bin/sh -c 'pkexec @libexecdir@/gvfsd-admin --address "$@" $DBUS_SESSION_BUS_ADDRESS'
+AutoMount=true
diff --git a/daemon/daemon-main-generic.c b/daemon/daemon-main-generic.c
index dddfcff..80dbfcb 100644
--- a/daemon/daemon-main-generic.c
+++ b/daemon/daemon-main-generic.c
@@ -33,6 +33,9 @@ main (int argc, char *argv[])
 #ifndef BACKEND_USES_GVFS
   g_setenv ("GIO_USE_VFS", "local", TRUE);
 #endif
+#ifdef BACKEND_PRE_SETUP_FUNC
+  BACKEND_PRE_SETUP_FUNC (&argc, &argv);
+#endif
   daemon_init ();
 #ifdef BACKEND_SETUP_FUNC
   BACKEND_SETUP_FUNC ();
diff --git a/daemon/gvfsbackendadmin.c b/daemon/gvfsbackendadmin.c
new file mode 100644
index 0000000..c37316d
--- /dev/null
+++ b/daemon/gvfsbackendadmin.c
@@ -0,0 +1,1060 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/* gvfs - extensions for gio
+ *
+ * Copyright (C) 2015 Cosimo Cecchi <cosimoc gnome org>
+ *
+ * This library 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 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, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/capability.h>
+#include <sys/fsuid.h>
+#include <sys/prctl.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <polkit/polkit.h>
+
+#include "gvfsbackendadmin.h"
+#include "gvfsjobcreatemonitor.h"
+#include "gvfsjobenumerate.h"
+#include "gvfsjobopenforread.h"
+#include "gvfsjobopenforwrite.h"
+#include "gvfsjobqueryattributes.h"
+#include "gvfsjobqueryinfo.h"
+#include "gvfsjobread.h"
+#include "gvfsjobseekread.h"
+#include "gvfsjobseekwrite.h"
+#include "gvfsjobsetdisplayname.h"
+#include "gvfsjobwrite.h"
+#include "gvfsmonitor.h"
+
+struct _GVfsBackendAdmin
+{
+  GVfsBackend parent_instance;
+
+  GMutex polkit_mutex;
+  PolkitAuthority *authority;
+};
+
+struct _GVfsBackendAdminClass
+{
+  GVfsBackendClass parent_class;
+};
+
+G_DEFINE_TYPE(GVfsBackendAdmin, g_vfs_backend_admin, G_VFS_TYPE_BACKEND)
+
+static void
+do_finalize (GObject *object)
+{
+  GVfsBackendAdmin *self = G_VFS_BACKEND_ADMIN (object);
+
+  g_clear_object (&self->authority);
+  g_mutex_clear (&self->polkit_mutex);
+
+  G_OBJECT_CLASS (g_vfs_backend_admin_parent_class)->finalize (object);
+}
+
+static gboolean
+check_permission (GVfsBackendAdmin *self,
+                  GVfsJob *job)
+{
+  GVfsJobDBus *dbus_job = G_VFS_JOB_DBUS (job);
+  GError *error = NULL;
+
+  GDBusMethodInvocation *invocation = dbus_job->invocation;
+  GDBusConnection *connection = g_dbus_method_invocation_get_connection (invocation);
+  GCredentials *credentials = g_dbus_connection_get_peer_credentials (connection);
+
+  pid_t pid = g_credentials_get_unix_pid (credentials, &error);
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return FALSE;
+    }
+
+  uid_t uid = g_credentials_get_unix_user (credentials, &error);
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return FALSE;
+    }
+
+  /* Only one polkit dialog at a time */
+  g_mutex_lock (&self->polkit_mutex);
+
+  PolkitSubject *subject =
+    polkit_unix_process_new_for_owner (pid, 0, uid);
+  PolkitAuthorizationResult *result = polkit_authority_check_authorization_sync
+    (self->authority, subject,
+     "org.gtk.vfs.file-operations",
+     NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION,
+     NULL, &error);
+  g_object_unref (subject);
+
+  g_mutex_unlock (&self->polkit_mutex);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return FALSE;
+    }
+
+  gboolean is_authorized =
+    polkit_authorization_result_get_is_authorized (result) ||
+    polkit_authorization_result_get_is_challenge (result);
+
+  g_object_unref (result);
+
+  if (!is_authorized)
+    g_vfs_job_failed_literal (job, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
+                              _("Permission denied"));
+
+  return is_authorized;
+}
+
+static void
+do_query_info (GVfsBackend *backend,
+               GVfsJobQueryInfo *query_info_job,
+               const char *filename,
+               GFileQueryInfoFlags flags,
+               GFileInfo *info,
+               GFileAttributeMatcher *matcher)
+{
+  GVfsBackendAdmin *self = G_VFS_BACKEND_ADMIN (backend);
+  GVfsJob *job = G_VFS_JOB (query_info_job);
+
+  if (!check_permission (self, job))
+    return;
+
+  GError *error = NULL;
+  GFile *file = g_file_new_for_path (filename);
+  GFileInfo *real_info = g_file_query_info (file, query_info_job->attributes,
+                                            flags, job->cancellable,
+                                            &error);
+  g_object_unref (file);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  /* Override read/write flags, since the above call will use access()
+   * to determine permissions, which does not honor our privileged
+   * capabilities.
+   */
+  g_file_info_set_attribute_boolean
+    (real_info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, TRUE);
+  g_file_info_set_attribute_boolean
+    (real_info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, TRUE);
+
+  g_file_info_copy_into (real_info, info);
+  g_object_unref (real_info);
+  g_vfs_job_succeeded (job);
+}
+
+static void
+do_close_write (GVfsBackend *backend,
+                GVfsJobCloseWrite *close_write_job,
+                GVfsBackendHandle handle)
+{
+  GVfsJob *job = G_VFS_JOB (close_write_job);
+  GOutputStream *stream = handle;
+
+  GError *error = NULL;
+  g_output_stream_close (stream, job->cancellable, &error);
+  g_object_unref (stream);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  g_vfs_job_succeeded (job);
+}
+
+static void
+do_write (GVfsBackend *backend,
+          GVfsJobWrite *write_job,
+          GVfsBackendHandle handle,
+          char *buffer,
+          gsize buffer_size)
+{
+  GVfsJob *job = G_VFS_JOB (write_job);
+  GOutputStream *stream = handle;
+
+  GError *error = NULL;
+  gssize bytes_written = g_output_stream_write (stream, buffer, buffer_size,
+                                                job->cancellable, &error);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  g_vfs_job_write_set_written_size (write_job, bytes_written);
+  g_vfs_job_succeeded (job);
+}
+
+static void
+do_append_to (GVfsBackend *backend,
+              GVfsJobOpenForWrite *open_write_job,
+              const char *filename,
+              GFileCreateFlags flags)
+{
+  GVfsBackendAdmin *self = G_VFS_BACKEND_ADMIN (backend);
+  GVfsJob *job = G_VFS_JOB (open_write_job);
+
+  if (!check_permission (self, job))
+    return;
+
+  GError *error = NULL;
+  GFile *file = g_file_new_for_path (filename);
+  GFileOutputStream *stream = g_file_append_to (file, flags,
+                                                job->cancellable, &error);
+  g_object_unref (file);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  GSeekable *seekable = G_SEEKABLE (stream);
+
+  /* Seek to the end of the file */
+  g_seekable_seek (seekable, 0, G_SEEK_END,
+                   job->cancellable, &error);
+  if (error != NULL)
+    {
+      g_object_unref (stream);
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  g_vfs_job_open_for_write_set_handle (open_write_job, stream);
+  g_vfs_job_open_for_write_set_can_seek
+    (open_write_job, g_seekable_can_seek (seekable));
+  g_vfs_job_open_for_write_set_can_truncate
+    (open_write_job, g_seekable_can_truncate (seekable));
+  g_vfs_job_open_for_write_set_initial_offset
+    (open_write_job, g_seekable_tell (seekable));
+
+  g_vfs_job_succeeded (job);
+}
+
+static void
+do_create (GVfsBackend *backend,
+           GVfsJobOpenForWrite *open_write_job,
+           const char *filename,
+           GFileCreateFlags flags)
+{
+  GVfsBackendAdmin *self = G_VFS_BACKEND_ADMIN (backend);
+  GVfsJob *job = G_VFS_JOB (open_write_job);
+
+  if (!check_permission (self, job))
+    return;
+
+  GError *error = NULL;
+  GFile *file = g_file_new_for_path (filename);
+  GFileOutputStream *stream = g_file_create (file, flags,
+                                             job->cancellable, &error);
+  g_object_unref (file);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  GSeekable *seekable = G_SEEKABLE (stream);
+
+  g_vfs_job_open_for_write_set_handle (open_write_job, stream);
+  g_vfs_job_open_for_write_set_can_seek
+    (open_write_job, g_seekable_can_seek (seekable));
+  g_vfs_job_open_for_write_set_can_truncate
+    (open_write_job, g_seekable_can_truncate (seekable));
+
+  g_vfs_job_succeeded (job);
+}
+
+static void
+do_replace (GVfsBackend *backend,
+            GVfsJobOpenForWrite *open_write_job,
+            const char *filename,
+            const char *etag,
+            gboolean make_backup,
+            GFileCreateFlags flags)
+{
+  GVfsBackendAdmin *self = G_VFS_BACKEND_ADMIN (backend);
+  GVfsJob *job = G_VFS_JOB (open_write_job);
+
+  if (!check_permission (self, job))
+    return;
+
+  GError *error = NULL;
+  GFile *file = g_file_new_for_path (filename);
+  GFileOutputStream *stream = g_file_replace (file, etag, make_backup, flags,
+                                              job->cancellable, &error);
+  g_object_unref (file);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  GSeekable *seekable = G_SEEKABLE (stream);
+
+  g_vfs_job_open_for_write_set_handle (open_write_job, stream);
+  g_vfs_job_open_for_write_set_can_seek
+    (open_write_job, g_seekable_can_seek (seekable));
+  g_vfs_job_open_for_write_set_can_truncate
+    (open_write_job, g_seekable_can_truncate (seekable));
+
+  g_vfs_job_succeeded (job);
+}
+
+static void
+do_close_read (GVfsBackend *backend,
+               GVfsJobCloseRead *close_read_job,
+               GVfsBackendHandle handle)
+{
+  GVfsJob *job = G_VFS_JOB (close_read_job);
+  GInputStream *stream = handle;
+  GError *error = NULL;
+
+  g_input_stream_close (stream, job->cancellable, &error);
+  g_object_unref (stream);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  g_vfs_job_succeeded (job);
+}
+
+static void
+do_read (GVfsBackend *backend,
+         GVfsJobRead *read_job,
+         GVfsBackendHandle handle,
+         char *buffer,
+         gsize bytes_requested)
+{
+  GVfsJob *job = G_VFS_JOB (read_job);
+  GError *error = NULL;
+  GInputStream *stream = handle;
+
+  gssize bytes = g_input_stream_read (stream, buffer, bytes_requested,
+                                      job->cancellable, &error);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  g_vfs_job_read_set_size (read_job, bytes);
+  g_vfs_job_succeeded (job);
+}
+
+static void
+do_open_for_read (GVfsBackend        *backend,
+                  GVfsJobOpenForRead *open_read_job,
+                  const char         *filename)
+{
+  GVfsBackendAdmin *self = G_VFS_BACKEND_ADMIN (backend);
+  GVfsJob *job = G_VFS_JOB (open_read_job);
+
+  if (!check_permission (self, job))
+    return;
+
+  GFile *file = g_file_new_for_path (filename);
+  GError *error = NULL;
+  GFileInputStream *stream = g_file_read (file, job->cancellable, &error);
+  g_object_unref (file);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  g_vfs_job_open_for_read_set_handle (open_read_job, stream);
+  g_vfs_job_open_for_read_set_can_seek
+    (open_read_job,
+     g_seekable_can_seek (G_SEEKABLE (stream)));
+  g_vfs_job_succeeded (job);
+}
+
+static void
+do_truncate (GVfsBackend *backend,
+             GVfsJobTruncate *truncate_job,
+             GVfsBackendHandle handle,
+             goffset size)
+{
+  GVfsBackendAdmin *self = G_VFS_BACKEND_ADMIN (backend);
+  GVfsJob *job = G_VFS_JOB (truncate_job);
+  GSeekable *seekable = handle;
+
+  GError *error = NULL;
+  g_seekable_truncate (seekable, size, job->cancellable, &error);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  g_vfs_job_succeeded (job);
+}
+
+static void
+do_seek_on_read (GVfsBackend *backend,
+                 GVfsJobSeekRead *seek_read_job,
+                 GVfsBackendHandle handle,
+                 goffset offset,
+                 GSeekType type)
+{
+  GVfsBackendAdmin *self = G_VFS_BACKEND_ADMIN (backend);
+  GVfsJob *job = G_VFS_JOB (seek_read_job);
+  GSeekable *seekable = handle;
+
+  GError *error = NULL;
+  g_seekable_seek (seekable, offset, type,
+                   job->cancellable, &error);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  g_vfs_job_seek_read_set_offset (seek_read_job, g_seekable_tell (seekable));
+  g_vfs_job_succeeded (job);
+}
+
+static void
+do_seek_on_write (GVfsBackend *backend,
+                  GVfsJobSeekWrite *seek_write_job,
+                  GVfsBackendHandle handle,
+                  goffset offset,
+                  GSeekType type)
+{
+  GVfsBackendAdmin *self = G_VFS_BACKEND_ADMIN (backend);
+  GVfsJob *job = G_VFS_JOB (seek_write_job);
+  GSeekable *seekable = handle;
+
+  GError *error = NULL;
+  g_seekable_seek (seekable, offset, type,
+                   job->cancellable, &error);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  g_vfs_job_seek_write_set_offset (seek_write_job, g_seekable_tell (seekable));
+  g_vfs_job_succeeded (job);
+}
+
+static void
+do_enumerate (GVfsBackend *backend,
+              GVfsJobEnumerate *enumerate_job,
+              const char *filename,
+              GFileAttributeMatcher *attribute_matcher,
+              GFileQueryInfoFlags flags)
+{
+  GVfsBackendAdmin *self = G_VFS_BACKEND_ADMIN (backend);
+  GVfsJob *job = G_VFS_JOB (enumerate_job);
+
+  if (!check_permission (self, job))
+    return;
+
+  GFile *file = g_file_new_for_path (filename);
+  GError *error = NULL;
+  GFileEnumerator *enumerator =
+    g_file_enumerate_children (file, enumerate_job->attributes, flags,
+                               job->cancellable, &error);
+  g_object_unref (file);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  while (TRUE)
+    {
+      GFileInfo *info;
+      if (!g_file_enumerator_iterate (enumerator, &info, NULL,
+                                      job->cancellable, &error))
+        {
+          g_object_unref (enumerator);
+          g_vfs_job_failed_from_error (job, error);
+          g_error_free (error);
+          return;
+        }
+
+      if (!info)
+        break;
+
+      g_vfs_job_enumerate_add_info (enumerate_job, g_object_ref (info));
+    }
+
+  g_file_enumerator_close (enumerator, job->cancellable, &error);
+  g_object_unref (enumerator);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  g_vfs_job_succeeded (job);
+  g_vfs_job_enumerate_done (enumerate_job);
+}
+
+static void
+do_make_directory (GVfsBackend *backend,
+                   GVfsJobMakeDirectory *mkdir_job,
+                   const char *filename)
+{
+  GVfsBackendAdmin *self = G_VFS_BACKEND_ADMIN (backend);
+  GVfsJob *job = G_VFS_JOB (mkdir_job);
+
+  if (!check_permission (self, job))
+    return;
+
+  GError *error = NULL;
+  GFile *file = g_file_new_for_path (filename);
+
+  g_file_make_directory (file, job->cancellable, &error);
+  g_object_unref (file);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  g_vfs_job_succeeded (job);
+}
+
+static void
+do_make_symlink (GVfsBackend *backend,
+                 GVfsJobMakeSymlink *symlink_job,
+                 const char *filename,
+                 const char *symlink_value)
+{
+  GVfsBackendAdmin *self = G_VFS_BACKEND_ADMIN (backend);
+  GVfsJob *job = G_VFS_JOB (symlink_job);
+
+  if (!check_permission (self, job))
+    return;
+
+  GError *error = NULL;
+  GFile *file = g_file_new_for_path (filename);
+  g_file_make_symbolic_link (file, symlink_value, job->cancellable, &error);
+  g_object_unref (file);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  g_vfs_job_succeeded (job);
+}
+
+static void
+do_query_fs_info (GVfsBackend *backend,
+                 GVfsJobQueryFsInfo *query_info_job,
+                 const char *filename,
+                 GFileInfo *info,
+                 GFileAttributeMatcher *attribute_matcher)
+{
+  GVfsBackendAdmin *self = G_VFS_BACKEND_ADMIN (backend);
+  GVfsJob *job = G_VFS_JOB (query_info_job);
+
+  if (!check_permission (self, job))
+    return;
+
+  GError *error = NULL;
+  GFile *file = g_file_new_for_path (filename);
+  char *attributes = g_file_attribute_matcher_to_string (attribute_matcher);
+  GFileInfo *real_info = g_file_query_filesystem_info
+    (file, attributes,
+     job->cancellable, &error);
+  g_object_unref (file);
+  g_free (attributes);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  g_file_info_copy_into (real_info, info);
+  g_object_unref (real_info);
+  g_vfs_job_succeeded (job);
+}
+
+static void
+monitor_changed (GFileMonitor* monitor,
+                 GFile* file,
+                 GFile* other_file,
+                 GFileMonitorEvent event_type,
+                 GVfsMonitor *vfs_monitor)
+{
+  char *file_path;
+  char *other_file_path;
+
+  file_path = g_file_get_path (file);
+  if (other_file)
+    other_file_path = g_file_get_path (other_file);
+  else
+    other_file_path = NULL;
+
+  g_vfs_monitor_emit_event (vfs_monitor,
+                            event_type,
+                            file_path,
+                            other_file_path);
+
+  g_free (file_path);
+  g_free (other_file_path);
+}
+
+static void
+create_dir_file_monitor (GVfsBackend *backend,
+                         GVfsJobCreateMonitor *monitor_job,
+                         const char *filename,
+                         GFileMonitorFlags flags,
+                         gboolean is_dir_monitor)
+{
+  GVfsBackendAdmin *self = G_VFS_BACKEND_ADMIN (backend);
+  GVfsJob *job = G_VFS_JOB (monitor_job);
+
+  if (!check_permission (self, job))
+    return;
+
+  GFile *file = g_file_new_for_path (filename);
+  GError *error = NULL;
+  GFileMonitor *monitor;
+
+  if (is_dir_monitor)
+    monitor = g_file_monitor_directory (file, flags,
+                                        job->cancellable, &error);
+  else
+    monitor = g_file_monitor_file (file, flags,
+                                   job->cancellable, &error);
+  g_object_unref (file);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  GVfsMonitor *vfs_monitor = g_vfs_monitor_new (backend);
+  g_signal_connect (monitor, "changed",
+                    G_CALLBACK (monitor_changed), vfs_monitor);
+
+  g_object_set_data_full (G_OBJECT (vfs_monitor),
+                          "real-monitor", monitor,
+                          (GDestroyNotify) g_object_unref);
+
+  g_vfs_job_create_monitor_set_monitor (monitor_job, vfs_monitor);
+  g_object_unref (vfs_monitor);
+
+  g_vfs_job_succeeded (job);
+}
+
+static void
+do_create_dir_monitor (GVfsBackend *backend,
+                       GVfsJobCreateMonitor *job,
+                       const char *filename,
+                       GFileMonitorFlags flags)
+{
+  create_dir_file_monitor (backend, job, filename, flags, TRUE);
+}
+
+
+static void
+do_create_file_monitor (GVfsBackend *backend,
+                        GVfsJobCreateMonitor *job,
+                        const char *filename,
+                        GFileMonitorFlags flags)
+{
+  create_dir_file_monitor (backend, job, filename, flags, FALSE);
+}
+
+static void
+do_set_display_name (GVfsBackend *backend,
+                     GVfsJobSetDisplayName *display_name_job,
+                     const char *filename,
+                     const char *display_name)
+{
+  GVfsBackendAdmin *self = G_VFS_BACKEND_ADMIN (backend);
+  GVfsJob *job = G_VFS_JOB (display_name_job);
+
+  if (!check_permission (self, job))
+    return;
+
+  GFile *file = g_file_new_for_path (filename);
+  GError *error = NULL;
+  g_file_set_display_name (file, display_name,
+                           job->cancellable, &error);
+  g_object_unref (file);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  char *dirname, *new_path;
+  dirname = g_path_get_dirname (filename);
+  new_path = g_build_filename (dirname, display_name, NULL);
+
+  g_vfs_job_set_display_name_set_new_path (display_name_job, new_path);
+  g_vfs_job_succeeded (job);
+  g_free (dirname);
+  g_free (new_path);
+}
+
+static void
+do_set_attribute (GVfsBackend *backend,
+                  GVfsJobSetAttribute *set_attribute_job,
+                  const char *filename,
+                  const char *attribute,
+                  GFileAttributeType type,
+                  gpointer value_p,
+                  GFileQueryInfoFlags flags)
+{
+  GVfsBackendAdmin *self = G_VFS_BACKEND_ADMIN (backend);
+  GVfsJob *job = G_VFS_JOB (set_attribute_job);
+
+  if (!check_permission (self, job))
+    return;
+
+  GFile *file = g_file_new_for_path (filename);
+  GError *error = NULL;
+  g_file_set_attribute (file, attribute, type, value_p, flags,
+                        job->cancellable, &error);
+  g_object_unref (file);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  g_vfs_job_succeeded (job);
+}
+
+static void
+do_delete (GVfsBackend *backend,
+           GVfsJobDelete *delete_job,
+           const char *filename)
+{
+  GVfsBackendAdmin *self = G_VFS_BACKEND_ADMIN (backend);
+  GVfsJob *job = G_VFS_JOB (delete_job);
+
+  if (!check_permission (self, job))
+    return;
+
+  GFile *file = g_file_new_for_path (filename);
+  GError *error = NULL;
+  g_file_delete (file, job->cancellable, &error);
+  g_object_unref (file);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  g_vfs_job_succeeded (job);
+}
+
+static void
+do_move (GVfsBackend *backend,
+         GVfsJobMove *move_job,
+         const char *source,
+         const char *destination,
+         GFileCopyFlags flags,
+         GFileProgressCallback progress_callback,
+         gpointer progress_callback_data)
+{
+  GVfsBackendAdmin *self = G_VFS_BACKEND_ADMIN (backend);
+  GVfsJob *job = G_VFS_JOB (move_job);
+
+  if (!check_permission (self, job))
+    return;
+
+  GFile *src_file = g_file_new_for_path (source);
+  GFile *dst_file = g_file_new_for_path (destination);
+  GError *error = NULL;
+  g_file_move (src_file, dst_file, flags,
+               job->cancellable,
+               progress_callback, progress_callback_data,
+               &error);
+
+  g_object_unref (src_file);
+  g_object_unref (dst_file);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  g_vfs_job_succeeded (job);
+}
+
+static void
+do_query_settable_attributes (GVfsBackend *backend,
+                              GVfsJobQueryAttributes *query_job,
+                              const char *filename)
+{
+  GVfsBackendAdmin *self = G_VFS_BACKEND_ADMIN (backend);
+  GVfsJob *job = G_VFS_JOB (query_job);
+
+  if (!check_permission (self, job))
+    return;
+
+  GFile *file = g_file_new_for_path (filename);
+  GError *error = NULL;
+  GFileAttributeInfoList *attr_list =
+    g_file_query_settable_attributes (file, job->cancellable, &error);
+  g_object_unref (file);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  g_vfs_job_query_attributes_set_list (query_job, attr_list);
+  g_file_attribute_info_list_unref (attr_list);
+
+  g_vfs_job_succeeded (job);
+}
+
+static void
+do_query_writable_namespaces (GVfsBackend *backend,
+                              GVfsJobQueryAttributes *query_job,
+                              const char *filename)
+{
+  GVfsBackendAdmin *self = G_VFS_BACKEND_ADMIN (backend);
+  GVfsJob *job = G_VFS_JOB (query_job);
+
+  if (!check_permission (self, job))
+    return;
+
+  GFile *file = g_file_new_for_path (filename);
+  GError *error = NULL;
+  GFileAttributeInfoList *attr_list =
+    g_file_query_writable_namespaces (file, job->cancellable, &error);
+  g_object_unref (file);
+
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  g_vfs_job_query_attributes_set_list (query_job, attr_list);
+  g_file_attribute_info_list_unref (attr_list);
+
+  g_vfs_job_succeeded (job);
+}
+
+static void
+do_mount (GVfsBackend *backend,
+          GVfsJobMount *mount_job,
+          GMountSpec *mount_spec,
+          GMountSource *mount_source,
+          gboolean is_automount)
+{
+  GVfsBackendAdmin *self = G_VFS_BACKEND_ADMIN (backend);
+  GVfsJob *job = G_VFS_JOB (mount_job);
+
+  GError *error = NULL;
+  self->authority = polkit_authority_get_sync (NULL, &error);
+  if (error != NULL)
+    {
+      g_vfs_job_failed_from_error (job, error);
+      g_error_free (error);
+      return;
+    }
+
+  g_vfs_job_succeeded (job);
+}
+
+static void
+g_vfs_backend_admin_class_init (GVfsBackendAdminClass * klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass);
+
+  object_class->finalize = do_finalize;
+
+  backend_class->mount = do_mount;
+  backend_class->open_for_read = do_open_for_read;
+  backend_class->query_info = do_query_info;
+  backend_class->read = do_read;
+  backend_class->create = do_create;
+  backend_class->append_to = do_append_to;
+  backend_class->replace = do_replace;
+  backend_class->write = do_write;
+  backend_class->close_read = do_close_read;
+  backend_class->close_write = do_close_write;
+  backend_class->seek_on_read = do_seek_on_read;
+  backend_class->seek_on_write = do_seek_on_write;
+  backend_class->enumerate = do_enumerate;
+  backend_class->truncate = do_truncate;
+  backend_class->make_directory = do_make_directory;
+  backend_class->make_symlink = do_make_symlink;
+  backend_class->query_fs_info = do_query_fs_info;
+  backend_class->create_dir_monitor = do_create_dir_monitor;
+  backend_class->set_display_name = do_set_display_name;
+  backend_class->set_attribute = do_set_attribute;
+  backend_class->delete = do_delete;
+  backend_class->move = do_move;
+  backend_class->query_settable_attributes = do_query_settable_attributes;
+  backend_class->query_writable_namespaces = do_query_writable_namespaces;
+}
+
+static void
+g_vfs_backend_admin_init (GVfsBackendAdmin *self)
+{
+  GVfsBackend *backend = G_VFS_BACKEND (self);
+  GMountSpec *mount_spec;
+
+  g_mutex_init (&self->polkit_mutex);
+  g_vfs_backend_set_user_visible (backend, FALSE);
+
+  mount_spec = g_mount_spec_new ("admin");
+  g_vfs_backend_set_mount_spec (backend, mount_spec);
+  g_mount_spec_unref (mount_spec);
+
+  g_vfs_backend_set_icon_name (backend, "folder");
+  g_vfs_backend_set_symbolic_icon_name (backend, "folder-symbolic");
+}
+
+#define REQUIRED_CAPS (CAP_TO_MASK(CAP_FOWNER) | \
+                       CAP_TO_MASK(CAP_DAC_OVERRIDE) | \
+                       CAP_TO_MASK(CAP_DAC_READ_SEARCH))
+
+static void
+acquire_caps (uid_t uid)
+{
+  struct __user_cap_header_struct hdr;
+  struct __user_cap_data_struct data;
+
+  /* Tell kernel not clear capabilities when dropping root */
+  if (prctl (PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0)
+    g_error ("prctl(PR_SET_KEEPCAPS) failed");
+
+  /* Drop root uid, but retain the required permitted caps */
+  if (setuid (uid) < 0)
+    g_error ("unable to drop privs");
+
+  memset (&hdr, 0, sizeof(hdr));
+  hdr.version = _LINUX_CAPABILITY_VERSION;
+
+  /* Drop all non-require capabilities */
+  data.effective = REQUIRED_CAPS;
+  data.permitted = REQUIRED_CAPS;
+  data.inheritable = 0;
+  if (capset (&hdr, &data) < 0)
+    g_error ("capset failed");
+}
+
+static char *session_address = NULL;
+static GOptionEntry entries[] = {
+  { "address", 0, 0, G_OPTION_ARG_STRING, &session_address, "DBus session address", NULL },
+  { NULL }
+};
+
+void
+g_vfs_backend_admin_pre_setup (int *argc,
+                               char **argv[])
+{
+  const char *pkexec_uid = g_getenv ("PKEXEC_UID");
+  if (pkexec_uid == NULL)
+    g_error ("gvfsd-admin must be executed under pkexec");
+
+  uid_t uid = strtol (pkexec_uid, NULL, 10);
+  if ((errno == ERANGE && (uid == LONG_MAX || uid == LONG_MIN))
+      || (errno != 0 && uid == 0))
+    g_error ("Unable to convert PKEXEC_UID string to uid_t");
+
+  GError *error = NULL;
+  GOptionContext *context = g_option_context_new (NULL);
+  g_option_context_set_ignore_unknown_options (context, TRUE);
+  g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
+  g_option_context_parse (context, argc, argv, &error);
+
+  if (error != NULL)
+    g_error ("Can't parse arguments: %s", error->message);
+
+  acquire_caps (uid);
+  g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_address, TRUE);
+}
diff --git a/daemon/gvfsbackendadmin.h b/daemon/gvfsbackendadmin.h
new file mode 100644
index 0000000..9a70d30
--- /dev/null
+++ b/daemon/gvfsbackendadmin.h
@@ -0,0 +1,61 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/* gvfs - extensions for gio
+ *
+ * Copyright (C) 2015 Cosimo Cecchi <cosimoc gnome org>
+ *
+ * This library 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 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, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __G_VFS_BACKEND_ADMIN_H__
+#define __G_VFS_BACKEND_ADMIN_H__
+
+#include <gvfsbackend.h>
+
+G_BEGIN_DECLS
+
+#define BACKEND_PRE_SETUP_FUNC g_vfs_backend_admin_pre_setup
+#define G_VFS_TYPE_BACKEND_ADMIN (g_vfs_backend_admin_get_type())
+
+#define G_VFS_BACKEND_ADMIN(o) \
+  (G_TYPE_CHECK_INSTANCE_CAST((o), \
+   G_VFS_TYPE_BACKEND_ADMIN, GVfsBackendAdmin))
+
+#define G_VFS_BACKEND_ADMIN_CLASS(k) \
+  (G_TYPE_CHECK_CLASS_CAST((k), \
+   G_VFS_TYPE_BACKEND_ADMIN, GVfsBackendAdminClass))
+
+#define G_VFS_IS_BACKEND_ADMIN(o) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((o), \
+   G_VFS_TYPE_BACKEND_ADMIN))
+
+#define G_VFS_IS_BACKEND_ADMIN_CLASS(k) \
+  (G_TYPE_CHECK_CLASS_TYPE((k), \
+   G_VFS_TYPE_BACKEND_ADMIN))
+
+#define G_VFS_BACKEND_ADMIN_GET_CLASS(o) \
+  (G_TYPE_INSTANCE_GET_CLASS((o), \
+   G_VFS_TYPE_BACKEND_ADMIN, GVfsBackendAdminClass))
+
+typedef struct _GVfsBackendAdmin GVfsBackendAdmin;
+typedef struct _GVfsBackendAdminClass GVfsBackendAdminClass;
+
+GType g_vfs_backend_admin_get_type (void) G_GNUC_CONST;
+
+void g_vfs_backend_admin_pre_setup (int *argc, char **argv[]);
+
+G_END_DECLS
+
+#endif /* __G_VFS_BACKEND_ADMIN_H__ */
diff --git a/daemon/org.gtk.vfs.file-operations.policy.in.in b/daemon/org.gtk.vfs.file-operations.policy.in.in
new file mode 100644
index 0000000..a371a0d
--- /dev/null
+++ b/daemon/org.gtk.vfs.file-operations.policy.in.in
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE policyconfig PUBLIC
+ "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd";>
+<policyconfig>
+
+  <vendor>GVfs</vendor>
+  <vendor_url>http://git.gnome.org/browse/gvfs</vendor_url>
+
+  <action id="org.gtk.vfs.file-operations-helper">
+    <_description>Perform file operations</_description>
+    <_message>Authentication is required to perform file operations</_message>
+    <defaults>
+      <allow_any>no</allow_any>
+      <allow_inactive>no</allow_inactive>
+      <allow_active>auth_admin_keep</allow_active>
+    </defaults>
+    <annotate key="org.freedesktop.policykit.exec.path">@libexecdir@/gvfsd-admin</annotate>
+  </action>
+
+  <action id="org.gtk.vfs.file-operations">
+    <_description>Perform file operations</_description>
+    <_message>Authentication is required to perform file operations</_message>
+    <defaults>
+      <allow_any>no</allow_any>
+      <allow_inactive>no</allow_inactive>
+      <allow_active>auth_admin_keep</allow_active>
+    </defaults>
+  </action>
+
+</policyconfig>
diff --git a/daemon/org.gtk.vfs.file-operations.rules b/daemon/org.gtk.vfs.file-operations.rules
new file mode 100644
index 0000000..fb8d54a
--- /dev/null
+++ b/daemon/org.gtk.vfs.file-operations.rules
@@ -0,0 +1,8 @@
+polkit.addRule(function(action, subject) {
+        if ((action.id == "org.gtk.vfs.file-operations-helper") &&
+            subject.local &&
+            subject.active &&
+            subject.isInGroup ("wheel")) {
+            return polkit.Result.YES;
+        }
+});



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]