summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--configure.ac2
-rw-r--r--src/user.c94
-rw-r--r--src/util.c31
-rw-r--r--src/util.h2
4 files changed, 119 insertions, 10 deletions
diff --git a/configure.ac b/configure.ac
index ffc82aa..6110c2b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -17,7 +17,7 @@ AM_GLIB_GNU_GETTEXT
IT_PROG_INTLTOOL([0.40.0])
PKG_CHECK_MODULES(GLIB, glib-2.0)
-PKG_CHECK_MODULES(GIO, gio-2.0)
+PKG_CHECK_MODULES(GIO, gio-2.0 gio-unix-2.0)
PKG_CHECK_MODULES(DBUS_GLIB, dbus-glib-1)
PKG_CHECK_MODULES(POLKIT, polkit-gobject-1)
diff --git a/src/user.c b/src/user.c
index 36a3f3c..5889ff3 100644
--- a/src/user.c
+++ b/src/user.c
@@ -23,6 +23,7 @@
#include "config.h"
+#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
@@ -35,6 +36,7 @@
#include <glib-object.h>
#include <glib/gstdio.h>
#include <gio/gio.h>
+#include <gio/gunixinputstream.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-lowlevel.h>
@@ -1222,6 +1224,19 @@ user_set_shell (User *user,
}
static void
+become_user (gpointer data)
+{
+ struct passwd *pw = data;
+
+ if (pw == NULL ||
+ initgroups (pw->pw_name, pw->pw_gid) != 0 ||
+ setgid (pw->pw_gid) != 0 ||
+ setuid (pw->pw_uid) != 0) {
+ exit (1);
+ }
+}
+
+static void
user_change_icon_file_authorized_cb (Daemon *daemon,
User *user,
DBusGMethodInvocation *context,
@@ -1232,43 +1247,104 @@ user_change_icon_file_authorized_cb (Daemon *daemon,
GFile *file;
GFileInfo *info;
guint32 mode;
+ guint64 size;
filename = g_strdup (data);
file = g_file_new_for_path (filename);
- info = g_file_query_info (file, G_FILE_ATTRIBUTE_UNIX_MODE, 0, NULL, NULL);
+ info = g_file_query_info (file, G_FILE_ATTRIBUTE_UNIX_MODE ","
+ G_FILE_ATTRIBUTE_STANDARD_SIZE,
+ 0, NULL, NULL);
mode = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE);
+ size = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
+
g_object_unref (info);
+ g_object_unref (file);
+
+ if (size > 1048576) {
+ g_warning ("file too large\n");
+ /* 1MB ought to be enough for everybody */
+ throw_error (context, ERROR_FAILED, "file '%s' is too large to be used as an icon", filename);
+ g_free (filename);
+ return;
+ }
if ((mode & S_IROTH) == 0 ||
(!g_str_has_prefix (filename, DATADIR) &&
!g_str_has_prefix (filename, ICONDIR))) {
gchar *dest_path;
GFile *dest;
+ gchar *argv[3];
+ gint std_out;
GError *error;
+ GInputStream *input;
+ GOutputStream *output;
+ gint uid;
+ gssize bytes;
+ struct passwd *pw;
+
+ if (!get_caller_uid (context, &uid)) {
+ throw_error (context, ERROR_FAILED, "failed to copy file, could not determine caller UID");
+ g_free (filename);
+ return;
+ }
dest_path = g_build_filename (ICONDIR, user->user_name, NULL);
dest = g_file_new_for_path (dest_path);
error = NULL;
- if (!g_file_copy (file, dest,
- G_FILE_COPY_OVERWRITE|G_FILE_COPY_TARGET_DEFAULT_PERMS,
- NULL, NULL, NULL, &error)) {
- throw_error (context, ERROR_FAILED, "copying file from '%s' to '%s' failed: %s", filename, dest_path, error->message);
+ output = G_OUTPUT_STREAM (g_file_replace (dest, NULL, FALSE, 0, NULL, &error));
+ if (!output) {
+ throw_error (context, ERROR_FAILED, "creating file '%s' failed: %s", dest_path, error->message);
g_error_free (error);
+ g_free (filename);
g_free (dest_path);
g_object_unref (dest);
- g_object_unref (file);
+ return;
+ }
+
+ argv[0] = "/bin/cat";
+ argv[1] = filename;
+ argv[2] = NULL;
+ pw = getpwuid (uid);
+
+ error = NULL;
+ if (!g_spawn_async_with_pipes (NULL, argv, NULL, 0, become_user, pw, NULL, NULL, &std_out, NULL, &error)) {
+ throw_error (context, ERROR_FAILED, "reading file '%s' failed: %s", filename, error->message);
+ g_error_free (error);
+ g_free (filename);
+ g_free (dest_path);
+ g_object_unref (dest);
return;
}
- filename = dest_path;
+ input = g_unix_input_stream_new (std_out, FALSE);
+
+ error = NULL;
+ bytes = g_output_stream_splice (output, input, G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, &error);
+ if (bytes < 0 || bytes != size) {
+ throw_error (context, ERROR_FAILED, "copying file '%s' to '%s' failed: %s", filename, dest_path, error ? error->message : "unknown reason");
+ if (error)
+ g_error_free (error);
+
+ g_file_delete (dest, NULL, NULL);
+
+ g_free (filename);
+ g_free (dest_path);
+ g_object_unref (dest);
+ g_object_unref (input);
+ g_object_unref (output);
+ return;
+ }
g_object_unref (dest);
- }
+ g_object_unref (input);
+ g_object_unref (output);
- g_object_unref (file);
+ g_free (filename);
+ filename = dest_path;
+ }
if (g_strcmp0 (user->icon_file, filename) != 0) {
g_free (user->icon_file);
diff --git a/src/util.c b/src/util.c
index ef466b4..d748afd 100644
--- a/src/util.c
+++ b/src/util.c
@@ -251,3 +251,34 @@ get_user_groups (const gchar *user,
return res;
}
+
+
+gboolean
+get_caller_uid (DBusGMethodInvocation *context, gint *uid)
+{
+ PolkitSubject *subject;
+ PolkitSubject *process;
+ GError *error;
+
+ subject = polkit_system_bus_name_new (dbus_g_method_get_sender (context));
+ process = polkit_system_bus_name_get_process_sync (POLKIT_SYSTEM_BUS_NAME (subject), NULL, NULL);
+ if (!process) {
+ g_object_unref (subject);
+ return FALSE;
+ }
+
+ error = NULL;
+ *uid = polkit_unix_process_get_owner (POLKIT_UNIX_PROCESS (process), &error);
+ if (error) {
+ g_error_free (error);
+ g_object_unref (subject);
+ g_object_unref (process);
+
+ return FALSE;
+ }
+
+ g_object_unref (subject);
+ g_object_unref (process);
+
+ return TRUE;
+}
diff --git a/src/util.h b/src/util.h
index 9a47150..bfdf780 100644
--- a/src/util.h
+++ b/src/util.h
@@ -31,6 +31,8 @@ void sys_log (DBusGMethodInvocation *context,
const gchar *format,
...);
+gboolean get_caller_uid (DBusGMethodInvocation *context, gint *uid);
+
gboolean spawn_with_login_uid (DBusGMethodInvocation *context,
gchar *argv[],
GError **error);