diff options
-rw-r--r-- | configure.ac | 18 | ||||
-rw-r--r-- | daemon/Makefile.am | 35 | ||||
-rw-r--r-- | daemon/admin.mount.in | 4 | ||||
-rw-r--r-- | daemon/daemon-main-generic.c | 3 | ||||
-rw-r--r-- | daemon/gvfsbackendadmin.c | 1060 | ||||
-rw-r--r-- | daemon/gvfsbackendadmin.h | 61 | ||||
-rw-r--r-- | daemon/org.gtk.vfs.file-operations.policy.in.in | 32 | ||||
-rw-r--r-- | daemon/org.gtk.vfs.file-operations.rules | 8 |
8 files changed, 1220 insertions, 1 deletions
diff --git a/configure.ac b/configure.ac index 0c438294..f226712d 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 08ddec7a..22f09290 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 00000000..0996a9e5 --- /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 dddfcffa..80dbfcbe 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 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); +} diff --git a/daemon/gvfsbackendadmin.h b/daemon/gvfsbackendadmin.h new file mode 100644 index 00000000..9a70d307 --- /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 00000000..a371a0df --- /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 00000000..fb8d54ae --- /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; + } +}); |