summaryrefslogtreecommitdiff
path: root/daemon/gvfsbackendadmin.c
diff options
context:
space:
mode:
authorCosimo Cecchi <cosimoc@gnome.org>2015-10-10 15:03:02 -0400
committerCosimo Cecchi <cosimo@endlessm.com>2016-07-13 13:35:14 -0700
commitc7b018d11b9aae5dea4fa436710f0bce78747978 (patch)
tree59fb07700d0ae6060c0f603f0bdd5e96973603f3 /daemon/gvfsbackendadmin.c
parent535f1e6b5d0d66c0361addde01775c04163fb59a (diff)
downloadgvfs-c7b018d11b9aae5dea4fa436710f0bce78747978.tar.gz
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.
Diffstat (limited to 'daemon/gvfsbackendadmin.c')
-rw-r--r--daemon/gvfsbackendadmin.c1060
1 files changed, 1060 insertions, 0 deletions
diff --git a/daemon/gvfsbackendadmin.c b/daemon/gvfsbackendadmin.c
new file mode 100644
index 00000000..c37316d8
--- /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);
+}