diff options
Diffstat (limited to 'daemon/gvfsbackendadmin.c')
-rw-r--r-- | daemon/gvfsbackendadmin.c | 1060 |
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); +} |