mirror of
				https://gitlab.gnome.org/GNOME/glib.git
				synced 2025-10-31 16:32:18 +01:00 
			
		
		
		
	The remaining call sites are either Windows-only, between fork () and exec () or in xdgmime copylib. Hope I haven't missed any site.
		
			
				
	
	
		
			1298 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1298 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* GDBus - GLib D-Bus Library
 | ||
|  *
 | ||
|  * Copyright (C) 2008-2010 Red Hat, Inc.
 | ||
|  *
 | ||
|  * SPDX-License-Identifier: LGPL-2.1-or-later
 | ||
|  *
 | ||
|  * 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.1 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, see <http://www.gnu.org/licenses/>.
 | ||
|  *
 | ||
|  * Author: David Zeuthen <davidz@redhat.com>
 | ||
|  */
 | ||
| 
 | ||
| #include "config.h"
 | ||
| 
 | ||
| #include <string.h>
 | ||
| #include <fcntl.h>
 | ||
| #include <errno.h>
 | ||
| #include <sys/types.h>
 | ||
| 
 | ||
| #include <glib/gstdio.h>
 | ||
| 
 | ||
| #ifdef G_OS_UNIX
 | ||
| #include <unistd.h>
 | ||
| #endif
 | ||
| #ifdef G_OS_WIN32
 | ||
| #include <io.h>
 | ||
| #include "gwin32sid.h"
 | ||
| #endif
 | ||
| 
 | ||
| #ifndef O_CLOEXEC
 | ||
| #define O_CLOEXEC 0
 | ||
| #endif
 | ||
| 
 | ||
| #include "gdbusauthmechanismsha1.h"
 | ||
| #include "gcredentials.h"
 | ||
| #include "gdbuserror.h"
 | ||
| #include "glocalfileinfo.h"
 | ||
| #include "gioenumtypes.h"
 | ||
| #include "gioerror.h"
 | ||
| #include "gdbusprivate.h"
 | ||
| #include "glib-private.h"
 | ||
| 
 | ||
| #include "glibintl.h"
 | ||
| 
 | ||
| /*
 | ||
|  * Arbitrary timeouts for keys in the keyring.
 | ||
|  * For interoperability, these match the reference implementation, libdbus.
 | ||
|  * To make them easier to compare, their names also match libdbus
 | ||
|  * (see dbus/dbus-keyring.c).
 | ||
|  */
 | ||
| 
 | ||
| /*
 | ||
|  * Maximum age of a key before we create a new key to use in challenges:
 | ||
|  * 5 minutes.
 | ||
|  */
 | ||
| #define NEW_KEY_TIMEOUT_SECONDS (60*5)
 | ||
| 
 | ||
| /*
 | ||
|  * Time before we drop a key from the keyring: 7 minutes.
 | ||
|  * Authentication will succeed if it takes less than
 | ||
|  * EXPIRE_KEYS_TIMEOUT_SECONDS - NEW_KEY_TIMEOUT_SECONDS (2 minutes)
 | ||
|  * to complete.
 | ||
|  * The spec says "delete any cookies that are old (the timeout can be
 | ||
|  * fairly short)".
 | ||
|  */
 | ||
| #define EXPIRE_KEYS_TIMEOUT_SECONDS (NEW_KEY_TIMEOUT_SECONDS + (60*2))
 | ||
| 
 | ||
| /*
 | ||
|  * Maximum amount of time a key can be in the future due to clock skew
 | ||
|  * with a shared home directory: 5 minutes.
 | ||
|  * The spec says "a reasonable time in the future".
 | ||
|  */
 | ||
| #define MAX_TIME_TRAVEL_SECONDS (60*5)
 | ||
| 
 | ||
| 
 | ||
| struct _GDBusAuthMechanismSha1Private
 | ||
| {
 | ||
|   gboolean is_client;
 | ||
|   gboolean is_server;
 | ||
|   GDBusAuthMechanismState state;
 | ||
|   gchar *reject_reason;  /* non-NULL iff (state == G_DBUS_AUTH_MECHANISM_STATE_REJECTED) */
 | ||
| 
 | ||
|   /* used on the client side */
 | ||
|   gchar *to_send;
 | ||
| 
 | ||
|   /* used on the server side */
 | ||
|   gchar *cookie;
 | ||
|   gchar *server_challenge;
 | ||
| };
 | ||
| 
 | ||
| static gint                     mechanism_get_priority              (void);
 | ||
| static const gchar             *mechanism_get_name                  (void);
 | ||
| 
 | ||
| static gboolean                 mechanism_is_supported              (GDBusAuthMechanism   *mechanism);
 | ||
| static gchar                   *mechanism_encode_data               (GDBusAuthMechanism   *mechanism,
 | ||
|                                                                      const gchar          *data,
 | ||
|                                                                      gsize                 data_len,
 | ||
|                                                                      gsize                *out_data_len);
 | ||
| static gchar                   *mechanism_decode_data               (GDBusAuthMechanism   *mechanism,
 | ||
|                                                                      const gchar          *data,
 | ||
|                                                                      gsize                 data_len,
 | ||
|                                                                      gsize                *out_data_len);
 | ||
| static GDBusAuthMechanismState  mechanism_server_get_state          (GDBusAuthMechanism   *mechanism);
 | ||
| static void                     mechanism_server_initiate           (GDBusAuthMechanism   *mechanism,
 | ||
|                                                                      const gchar          *initial_response,
 | ||
|                                                                      gsize                 initial_response_len);
 | ||
| static void                     mechanism_server_data_receive       (GDBusAuthMechanism   *mechanism,
 | ||
|                                                                      const gchar          *data,
 | ||
|                                                                      gsize                 data_len);
 | ||
| static gchar                   *mechanism_server_data_send          (GDBusAuthMechanism   *mechanism,
 | ||
|                                                                      gsize                *out_data_len);
 | ||
| static gchar                   *mechanism_server_get_reject_reason  (GDBusAuthMechanism   *mechanism);
 | ||
| static void                     mechanism_server_shutdown           (GDBusAuthMechanism   *mechanism);
 | ||
| static GDBusAuthMechanismState  mechanism_client_get_state          (GDBusAuthMechanism   *mechanism);
 | ||
| static gchar                   *mechanism_client_initiate           (GDBusAuthMechanism   *mechanism,
 | ||
|                                                                      GDBusConnectionFlags  conn_flags,
 | ||
|                                                                      gsize                *out_initial_response_len);
 | ||
| static void                     mechanism_client_data_receive       (GDBusAuthMechanism   *mechanism,
 | ||
|                                                                      const gchar          *data,
 | ||
|                                                                      gsize                 data_len);
 | ||
| static gchar                   *mechanism_client_data_send          (GDBusAuthMechanism   *mechanism,
 | ||
|                                                                      gsize                *out_data_len);
 | ||
| static void                     mechanism_client_shutdown           (GDBusAuthMechanism   *mechanism);
 | ||
| 
 | ||
| /* ---------------------------------------------------------------------------------------------------- */
 | ||
| 
 | ||
| G_DEFINE_TYPE_WITH_PRIVATE (GDBusAuthMechanismSha1, _g_dbus_auth_mechanism_sha1, G_TYPE_DBUS_AUTH_MECHANISM)
 | ||
| 
 | ||
| /* ---------------------------------------------------------------------------------------------------- */
 | ||
| 
 | ||
| static void
 | ||
| _g_dbus_auth_mechanism_sha1_finalize (GObject *object)
 | ||
| {
 | ||
|   GDBusAuthMechanismSha1 *mechanism = G_DBUS_AUTH_MECHANISM_SHA1 (object);
 | ||
| 
 | ||
|   g_free (mechanism->priv->reject_reason);
 | ||
|   g_free (mechanism->priv->to_send);
 | ||
| 
 | ||
|   g_free (mechanism->priv->cookie);
 | ||
|   g_free (mechanism->priv->server_challenge);
 | ||
| 
 | ||
|   if (G_OBJECT_CLASS (_g_dbus_auth_mechanism_sha1_parent_class)->finalize != NULL)
 | ||
|     G_OBJECT_CLASS (_g_dbus_auth_mechanism_sha1_parent_class)->finalize (object);
 | ||
| }
 | ||
| 
 | ||
| static void
 | ||
| _g_dbus_auth_mechanism_sha1_class_init (GDBusAuthMechanismSha1Class *klass)
 | ||
| {
 | ||
|   GObjectClass *gobject_class;
 | ||
|   GDBusAuthMechanismClass *mechanism_class;
 | ||
| 
 | ||
|   gobject_class = G_OBJECT_CLASS (klass);
 | ||
|   gobject_class->finalize = _g_dbus_auth_mechanism_sha1_finalize;
 | ||
| 
 | ||
|   mechanism_class = G_DBUS_AUTH_MECHANISM_CLASS (klass);
 | ||
|   mechanism_class->get_priority              = mechanism_get_priority;
 | ||
|   mechanism_class->get_name                  = mechanism_get_name;
 | ||
|   mechanism_class->is_supported              = mechanism_is_supported;
 | ||
|   mechanism_class->encode_data               = mechanism_encode_data;
 | ||
|   mechanism_class->decode_data               = mechanism_decode_data;
 | ||
|   mechanism_class->server_get_state          = mechanism_server_get_state;
 | ||
|   mechanism_class->server_initiate           = mechanism_server_initiate;
 | ||
|   mechanism_class->server_data_receive       = mechanism_server_data_receive;
 | ||
|   mechanism_class->server_data_send          = mechanism_server_data_send;
 | ||
|   mechanism_class->server_get_reject_reason  = mechanism_server_get_reject_reason;
 | ||
|   mechanism_class->server_shutdown           = mechanism_server_shutdown;
 | ||
|   mechanism_class->client_get_state          = mechanism_client_get_state;
 | ||
|   mechanism_class->client_initiate           = mechanism_client_initiate;
 | ||
|   mechanism_class->client_data_receive       = mechanism_client_data_receive;
 | ||
|   mechanism_class->client_data_send          = mechanism_client_data_send;
 | ||
|   mechanism_class->client_shutdown           = mechanism_client_shutdown;
 | ||
| }
 | ||
| 
 | ||
| static void
 | ||
| _g_dbus_auth_mechanism_sha1_init (GDBusAuthMechanismSha1 *mechanism)
 | ||
| {
 | ||
|   mechanism->priv = _g_dbus_auth_mechanism_sha1_get_instance_private (mechanism);
 | ||
| }
 | ||
| 
 | ||
| /* ---------------------------------------------------------------------------------------------------- */
 | ||
| 
 | ||
| static gint
 | ||
| mechanism_get_priority (void)
 | ||
| {
 | ||
|   return 0;
 | ||
| }
 | ||
| 
 | ||
| static const gchar *
 | ||
| mechanism_get_name (void)
 | ||
| {
 | ||
|   return "DBUS_COOKIE_SHA1";
 | ||
| }
 | ||
| 
 | ||
| static gboolean
 | ||
| mechanism_is_supported (GDBusAuthMechanism *mechanism)
 | ||
| {
 | ||
|   g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), FALSE);
 | ||
|   return TRUE;
 | ||
| }
 | ||
| 
 | ||
| static gchar *
 | ||
| mechanism_encode_data (GDBusAuthMechanism   *mechanism,
 | ||
|                        const gchar          *data,
 | ||
|                        gsize                 data_len,
 | ||
|                        gsize                *out_data_len)
 | ||
| {
 | ||
|   return NULL;
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static gchar *
 | ||
| mechanism_decode_data (GDBusAuthMechanism   *mechanism,
 | ||
|                        const gchar          *data,
 | ||
|                        gsize                 data_len,
 | ||
|                        gsize                *out_data_len)
 | ||
| {
 | ||
|   return NULL;
 | ||
| }
 | ||
| 
 | ||
| /* ---------------------------------------------------------------------------------------------------- */
 | ||
| 
 | ||
| static gint
 | ||
| random_ascii (void)
 | ||
| {
 | ||
|   gint ret;
 | ||
|   ret = g_random_int_range (0, 60);
 | ||
|   if (ret < 25)
 | ||
|     ret += 'A';
 | ||
|   else if (ret < 50)
 | ||
|     ret += 'a' - 25;
 | ||
|   else
 | ||
|     ret += '0' - 50;
 | ||
|   return ret;
 | ||
| }
 | ||
| 
 | ||
| static gchar *
 | ||
| random_ascii_string (guint len)
 | ||
| {
 | ||
|   GString *challenge;
 | ||
|   guint n;
 | ||
| 
 | ||
|   challenge = g_string_new (NULL);
 | ||
|   for (n = 0; n < len; n++)
 | ||
|     g_string_append_c (challenge, random_ascii ());
 | ||
|   return g_string_free (challenge, FALSE);
 | ||
| }
 | ||
| 
 | ||
| static gchar *
 | ||
| random_blob (guint len)
 | ||
| {
 | ||
|   GString *challenge;
 | ||
|   guint n;
 | ||
| 
 | ||
|   challenge = g_string_new (NULL);
 | ||
|   for (n = 0; n < len; n++)
 | ||
|     g_string_append_c (challenge, g_random_int_range (0, 256));
 | ||
|   return g_string_free (challenge, FALSE);
 | ||
| }
 | ||
| 
 | ||
| /* ---------------------------------------------------------------------------------------------------- */
 | ||
| 
 | ||
| /* ensure keyring dir exists and permissions are correct */
 | ||
| static gchar *
 | ||
| ensure_keyring_directory (GError **error)
 | ||
| {
 | ||
|   gchar *path;
 | ||
|   const gchar *e;
 | ||
|   gboolean is_setuid;
 | ||
| #ifdef G_OS_UNIX
 | ||
|   struct stat statbuf;
 | ||
| #endif
 | ||
| 
 | ||
|   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 | ||
| 
 | ||
|   e = g_getenv ("G_DBUS_COOKIE_SHA1_KEYRING_DIR");
 | ||
|   if (e != NULL)
 | ||
|     {
 | ||
|       path = g_strdup (e);
 | ||
|     }
 | ||
|   else
 | ||
|     {
 | ||
|       path = g_build_filename (g_get_home_dir (),
 | ||
|                                ".dbus-keyrings",
 | ||
|                                NULL);
 | ||
|     }
 | ||
| 
 | ||
| #ifdef G_OS_UNIX
 | ||
|   if (stat (path, &statbuf) != 0)
 | ||
|     {
 | ||
|       int errsv = errno;
 | ||
| 
 | ||
|       if (errsv != ENOENT)
 | ||
|         {
 | ||
|           g_set_error (error,
 | ||
|                        G_IO_ERROR,
 | ||
|                        g_io_error_from_errno (errsv),
 | ||
|                        _("Error when getting information for directory “%s”: %s"),
 | ||
|                        path,
 | ||
|                        g_strerror (errsv));
 | ||
|           g_clear_pointer (&path, g_free);
 | ||
|           return NULL;
 | ||
|         }
 | ||
|     }
 | ||
|   else if (S_ISDIR (statbuf.st_mode))
 | ||
|     {
 | ||
|       if (g_getenv ("G_DBUS_COOKIE_SHA1_KEYRING_DIR_IGNORE_PERMISSION") == NULL &&
 | ||
|           (statbuf.st_mode & 0777) != 0700)
 | ||
|         {
 | ||
|           g_set_error (error,
 | ||
|                        G_IO_ERROR,
 | ||
|                        G_IO_ERROR_FAILED,
 | ||
|                        _("Permissions on directory “%s” are malformed. Expected mode 0700, got 0%o"),
 | ||
|                        path,
 | ||
|                        (guint) (statbuf.st_mode & 0777));
 | ||
|           g_clear_pointer (&path, g_free);
 | ||
|           return NULL;
 | ||
|         }
 | ||
| 
 | ||
|       return g_steal_pointer (&path);
 | ||
|     }
 | ||
| #else  /* if !G_OS_UNIX */
 | ||
|   /* On non-Unix platforms, check that it exists as a directory, but don’t do
 | ||
|    * permissions checks at the moment. */
 | ||
|   if (g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
 | ||
|     {
 | ||
| #ifdef __GNUC__
 | ||
| #pragma GCC diagnostic push
 | ||
| #pragma GCC diagnostic warning "-Wcpp"
 | ||
| #warning Please implement permission checking on this non-UNIX platform
 | ||
| #pragma GCC diagnostic pop
 | ||
| #endif  /* __GNUC__ */
 | ||
|       return g_steal_pointer (&path);
 | ||
|     }
 | ||
| #endif  /* if !G_OS_UNIX */
 | ||
| 
 | ||
|   /* Only create the directory if not running as setuid */
 | ||
|   is_setuid = GLIB_PRIVATE_CALL (g_check_setuid) ();
 | ||
|   if (!is_setuid &&
 | ||
|       g_mkdir_with_parents (path, 0700) != 0)
 | ||
|     {
 | ||
|       int errsv = errno;
 | ||
|       g_set_error (error,
 | ||
|                    G_IO_ERROR,
 | ||
|                    g_io_error_from_errno (errsv),
 | ||
|                    _("Error creating directory “%s”: %s"),
 | ||
|                    path,
 | ||
|                    g_strerror (errsv));
 | ||
|       g_clear_pointer (&path, g_free);
 | ||
|       return NULL;
 | ||
|     }
 | ||
|   else if (is_setuid)
 | ||
|     {
 | ||
|       g_set_error (error,
 | ||
|                    G_IO_ERROR,
 | ||
|                    G_IO_ERROR_PERMISSION_DENIED,
 | ||
|                    _("Error creating directory “%s”: %s"),
 | ||
|                    path,
 | ||
|                    _("Operation not supported"));
 | ||
|       g_clear_pointer (&path, g_free);
 | ||
|       return NULL;
 | ||
|     }
 | ||
| 
 | ||
|   return g_steal_pointer (&path);
 | ||
| }
 | ||
| 
 | ||
| /* ---------------------------------------------------------------------------------------------------- */
 | ||
| 
 | ||
| /* looks up an entry in the keyring */
 | ||
| static gchar *
 | ||
| keyring_lookup_entry (const gchar  *cookie_context,
 | ||
|                       gint          cookie_id,
 | ||
|                       GError      **error)
 | ||
| {
 | ||
|   gchar *ret;
 | ||
|   gchar *keyring_dir;
 | ||
|   gchar *contents;
 | ||
|   gchar *path;
 | ||
|   guint n;
 | ||
|   gchar **lines;
 | ||
| 
 | ||
|   g_return_val_if_fail (cookie_context != NULL, NULL);
 | ||
|   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 | ||
| 
 | ||
|   ret = NULL;
 | ||
|   path = NULL;
 | ||
|   contents = NULL;
 | ||
|   lines = NULL;
 | ||
| 
 | ||
|   keyring_dir = ensure_keyring_directory (error);
 | ||
|   if (keyring_dir == NULL)
 | ||
|     goto out;
 | ||
| 
 | ||
|   path = g_build_filename (keyring_dir, cookie_context, NULL);
 | ||
| 
 | ||
|   if (!g_file_get_contents (path,
 | ||
|                             &contents,
 | ||
|                             NULL,
 | ||
|                             error))
 | ||
|     {
 | ||
|       g_prefix_error (error,
 | ||
|                       _("Error opening keyring “%s” for reading: "),
 | ||
|                       path);
 | ||
|       goto out;
 | ||
|     }
 | ||
|   g_assert (contents != NULL);
 | ||
| 
 | ||
|   lines = g_strsplit (contents, "\n", 0);
 | ||
|   for (n = 0; lines[n] != NULL; n++)
 | ||
|     {
 | ||
|       const gchar *line = lines[n];
 | ||
|       gchar **tokens;
 | ||
|       gchar *endp;
 | ||
|       gint line_id;
 | ||
| 
 | ||
|       if (line[0] == '\0')
 | ||
|         continue;
 | ||
| 
 | ||
|       tokens = g_strsplit (line, " ", 0);
 | ||
|       if (g_strv_length (tokens) != 3)
 | ||
|         {
 | ||
|           g_set_error (error,
 | ||
|                        G_IO_ERROR,
 | ||
|                        G_IO_ERROR_FAILED,
 | ||
|                        _("Line %d of the keyring at “%s” with content “%s” is malformed"),
 | ||
|                        n + 1,
 | ||
|                        path,
 | ||
|                        line);
 | ||
|           g_strfreev (tokens);
 | ||
|           goto out;
 | ||
|         }
 | ||
| 
 | ||
|       line_id = g_ascii_strtoll (tokens[0], &endp, 10);
 | ||
|       if (*endp != '\0')
 | ||
|         {
 | ||
|           g_set_error (error,
 | ||
|                        G_IO_ERROR,
 | ||
|                        G_IO_ERROR_FAILED,
 | ||
|                        _("First token of line %d of the keyring at “%s” with content “%s” is malformed"),
 | ||
|                        n + 1,
 | ||
|                        path,
 | ||
|                        line);
 | ||
|           g_strfreev (tokens);
 | ||
|           goto out;
 | ||
|         }
 | ||
| 
 | ||
|       (void)g_ascii_strtoll (tokens[1], &endp, 10); /* do not care what the timestamp is */
 | ||
|       if (*endp != '\0')
 | ||
|         {
 | ||
|           g_set_error (error,
 | ||
|                        G_IO_ERROR,
 | ||
|                        G_IO_ERROR_FAILED,
 | ||
|                        _("Second token of line %d of the keyring at “%s” with content “%s” is malformed"),
 | ||
|                        n + 1,
 | ||
|                        path,
 | ||
|                        line);
 | ||
|           g_strfreev (tokens);
 | ||
|           goto out;
 | ||
|         }
 | ||
| 
 | ||
|       if (line_id == cookie_id)
 | ||
|         {
 | ||
|           /* YAY, success */
 | ||
|           ret = tokens[2]; /* steal pointer */
 | ||
|           tokens[2] = NULL;
 | ||
|           g_strfreev (tokens);
 | ||
|           goto out;
 | ||
|         }
 | ||
| 
 | ||
|       g_strfreev (tokens);
 | ||
|     }
 | ||
| 
 | ||
|   /* BOOH, didn't find the cookie */
 | ||
|   g_set_error (error,
 | ||
|                G_IO_ERROR,
 | ||
|                G_IO_ERROR_FAILED,
 | ||
|                _("Didn’t find cookie with id %d in the keyring at “%s”"),
 | ||
|                cookie_id,
 | ||
|                path);
 | ||
| 
 | ||
|  out:
 | ||
|   g_free (keyring_dir);
 | ||
|   g_free (path);
 | ||
|   g_free (contents);
 | ||
|   g_strfreev (lines);
 | ||
|   return ret;
 | ||
| }
 | ||
| 
 | ||
| /* function for logging important events that the system administrator should take notice of */
 | ||
| G_GNUC_PRINTF(1, 2)
 | ||
| static void
 | ||
| _log (const gchar *message,
 | ||
|       ...)
 | ||
| {
 | ||
|   gchar *s;
 | ||
|   va_list var_args;
 | ||
| 
 | ||
|   va_start (var_args, message);
 | ||
|   s = g_strdup_vprintf (message, var_args);
 | ||
|   va_end (var_args);
 | ||
| 
 | ||
|   /* TODO: might want to send this to syslog instead */
 | ||
|   g_printerr ("GDBus-DBUS_COOKIE_SHA1: %s\n", s);
 | ||
|   g_free (s);
 | ||
| }
 | ||
| 
 | ||
| /* Returns FD for lock file, if it was created exclusively (didn't exist already,
 | ||
|  * and was created successfully) */
 | ||
| static gint
 | ||
| create_lock_exclusive (const gchar  *lock_path,
 | ||
|                        gint64       *mtime_nsec,
 | ||
|                        GError      **error)
 | ||
| {
 | ||
|   int errsv;
 | ||
|   gint ret;
 | ||
| 
 | ||
|   ret = g_open (lock_path, O_CREAT | O_EXCL | O_CLOEXEC, 0600);
 | ||
|   errsv = errno;
 | ||
|   if (ret < 0)
 | ||
|     {
 | ||
|       GLocalFileStat stat_buf;
 | ||
| 
 | ||
|       /* Get the modification time to distinguish between the lock being stale
 | ||
|        * or highly contested. */
 | ||
|       if (mtime_nsec != NULL &&
 | ||
|           g_local_file_stat (lock_path, G_LOCAL_FILE_STAT_FIELD_MTIME, G_LOCAL_FILE_STAT_FIELD_ALL, &stat_buf) == 0)
 | ||
|         *mtime_nsec = _g_stat_mtime (&stat_buf) * G_USEC_PER_SEC * 1000 + _g_stat_mtim_nsec (&stat_buf);
 | ||
|       else if (mtime_nsec != NULL)
 | ||
|         *mtime_nsec = 0;
 | ||
| 
 | ||
|       g_set_error (error,
 | ||
|                    G_IO_ERROR,
 | ||
|                    g_io_error_from_errno (errsv),
 | ||
|                    _("Error creating lock file “%s”: %s"),
 | ||
|                    lock_path,
 | ||
|                    g_strerror (errsv));
 | ||
|       return -1;
 | ||
|     }
 | ||
| 
 | ||
|   return ret;
 | ||
| }
 | ||
| 
 | ||
| static gint
 | ||
| keyring_acquire_lock (const gchar  *path,
 | ||
|                       GError      **error)
 | ||
| {
 | ||
|   gchar *lock = NULL;
 | ||
|   gint ret;
 | ||
|   guint num_tries;
 | ||
|   int errsv;
 | ||
|   gint64 lock_mtime_nsec = 0, lock_mtime_nsec_prev = 0;
 | ||
| 
 | ||
|   /* Total possible sleep period = max_tries * timeout_usec = 0.5s */
 | ||
|   const guint max_tries = 50;
 | ||
|   const guint timeout_usec = 1000 * 10;
 | ||
| 
 | ||
|   g_return_val_if_fail (path != NULL, -1);
 | ||
|   g_return_val_if_fail (error == NULL || *error == NULL, -1);
 | ||
| 
 | ||
|   ret = -1;
 | ||
|   lock = g_strconcat (path, ".lock", NULL);
 | ||
| 
 | ||
|   /* This is what the D-Bus spec says
 | ||
|    * (https://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms-sha)
 | ||
|    *
 | ||
|    *  Create a lockfile name by appending ".lock" to the name of the
 | ||
|    *  cookie file. The server should attempt to create this file using
 | ||
|    *  O_CREAT | O_EXCL. If file creation fails, the lock
 | ||
|    *  fails. Servers should retry for a reasonable period of time,
 | ||
|    *  then they may choose to delete an existing lock to keep users
 | ||
|    *  from having to manually delete a stale lock. [1]
 | ||
|    *
 | ||
|    *  [1] : Lockfiles are used instead of real file locking fcntl() because
 | ||
|    *         real locking implementations are still flaky on network filesystems
 | ||
|    */
 | ||
| 
 | ||
|   for (num_tries = 0; num_tries < max_tries; num_tries++)
 | ||
|     {
 | ||
|       lock_mtime_nsec_prev = lock_mtime_nsec;
 | ||
| 
 | ||
|       /* Ignore the error until the final call. */
 | ||
|       ret = create_lock_exclusive (lock, &lock_mtime_nsec, NULL);
 | ||
|       if (ret >= 0)
 | ||
|         break;
 | ||
| 
 | ||
|       /* sleep 10ms, then try again */
 | ||
|       g_usleep (timeout_usec);
 | ||
| 
 | ||
|       /* If the mtime of the lock file changed, don’t count the retry, as it
 | ||
|        * seems like there’s contention between processes for the lock file,
 | ||
|        * rather than a stale lock file from a crashed process. */
 | ||
|       if (num_tries > 0 && lock_mtime_nsec != lock_mtime_nsec_prev)
 | ||
|         num_tries--;
 | ||
|     }
 | ||
| 
 | ||
|   if (num_tries == max_tries)
 | ||
|     {
 | ||
|       /* ok, we slept 50*10ms = 0.5 seconds. Conclude that the lock file must be
 | ||
|        * stale (nuke it from orbit)
 | ||
|        */
 | ||
|       if (g_unlink (lock) != 0)
 | ||
|         {
 | ||
|           errsv = errno;
 | ||
|           g_set_error (error,
 | ||
|                        G_IO_ERROR,
 | ||
|                        g_io_error_from_errno (errsv),
 | ||
|                        _("Error deleting stale lock file “%s”: %s"),
 | ||
|                        lock,
 | ||
|                        g_strerror (errsv));
 | ||
|           goto out;
 | ||
|         }
 | ||
| 
 | ||
|       _log ("Deleted stale lock file '%s'", lock);
 | ||
| 
 | ||
|       /* Try one last time to create it, now that we've deleted the stale one */
 | ||
|       ret = create_lock_exclusive (lock, NULL, error);
 | ||
|       if (ret < 0)
 | ||
|         goto out;
 | ||
|     }
 | ||
| 
 | ||
| out:
 | ||
|   g_free (lock);
 | ||
|   return ret;
 | ||
| }
 | ||
| 
 | ||
| static gboolean
 | ||
| keyring_release_lock (const gchar  *path,
 | ||
|                       gint          lock_fd,
 | ||
|                       GError      **error)
 | ||
| {
 | ||
|   gchar *lock;
 | ||
|   gboolean ret;
 | ||
| 
 | ||
|   g_return_val_if_fail (path != NULL, FALSE);
 | ||
|   g_return_val_if_fail (lock_fd != -1, FALSE);
 | ||
|   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
 | ||
| 
 | ||
|   ret = FALSE;
 | ||
|   lock = g_strdup_printf ("%s.lock", path);
 | ||
|   if (close (lock_fd) != 0)
 | ||
|     {
 | ||
|       int errsv = errno;
 | ||
|       g_set_error (error,
 | ||
|                    G_IO_ERROR,
 | ||
|                    g_io_error_from_errno (errsv),
 | ||
|                    _("Error closing (unlinked) lock file “%s”: %s"),
 | ||
|                    lock,
 | ||
|                    g_strerror (errsv));
 | ||
|       goto out;
 | ||
|     }
 | ||
|   if (g_unlink (lock) != 0)
 | ||
|     {
 | ||
|       int errsv = errno;
 | ||
|       g_set_error (error,
 | ||
|                    G_IO_ERROR,
 | ||
|                    g_io_error_from_errno (errsv),
 | ||
|                    _("Error unlinking lock file “%s”: %s"),
 | ||
|                    lock,
 | ||
|                    g_strerror (errsv));
 | ||
|       goto out;
 | ||
|     }
 | ||
| 
 | ||
|   ret = TRUE;
 | ||
| 
 | ||
|  out:
 | ||
|   g_free (lock);
 | ||
|   return ret;
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| /* adds an entry to the keyring, taking care of locking and deleting stale/future entries */
 | ||
| static gboolean
 | ||
| keyring_generate_entry (const gchar  *cookie_context,
 | ||
|                         gint         *out_id,
 | ||
|                         gchar       **out_cookie,
 | ||
|                         GError      **error)
 | ||
| {
 | ||
|   gboolean ret;
 | ||
|   gchar *keyring_dir;
 | ||
|   gchar *path;
 | ||
|   gchar *contents;
 | ||
|   GError *local_error = NULL;
 | ||
|   gchar **lines;
 | ||
|   gint max_line_id;
 | ||
|   GString *new_contents;
 | ||
|   gint64 now;
 | ||
|   gboolean have_id;
 | ||
|   gint use_id;
 | ||
|   gchar *use_cookie;
 | ||
|   gboolean changed_file;
 | ||
|   gint lock_fd;
 | ||
| 
 | ||
|   g_return_val_if_fail (cookie_context != NULL, FALSE);
 | ||
|   g_return_val_if_fail (out_id != NULL, FALSE);
 | ||
|   g_return_val_if_fail (out_cookie != NULL, FALSE);
 | ||
|   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
 | ||
| 
 | ||
|   ret = FALSE;
 | ||
|   path = NULL;
 | ||
|   contents = NULL;
 | ||
|   lines = NULL;
 | ||
|   new_contents = NULL;
 | ||
|   have_id = FALSE;
 | ||
|   use_id = 0;
 | ||
|   use_cookie = NULL;
 | ||
|   lock_fd = -1;
 | ||
| 
 | ||
|   keyring_dir = ensure_keyring_directory (error);
 | ||
|   if (keyring_dir == NULL)
 | ||
|     goto out;
 | ||
| 
 | ||
|   path = g_build_filename (keyring_dir, cookie_context, NULL);
 | ||
| 
 | ||
|   lock_fd = keyring_acquire_lock (path, error);
 | ||
|   if (lock_fd == -1)
 | ||
|     goto out;
 | ||
| 
 | ||
|   contents = NULL;
 | ||
|   if (!g_file_get_contents (path,
 | ||
|                             &contents,
 | ||
|                             NULL,
 | ||
|                             &local_error))
 | ||
|     {
 | ||
|       if (local_error->domain == G_FILE_ERROR && local_error->code == G_FILE_ERROR_NOENT)
 | ||
|         {
 | ||
|           /* file doesn't have to exist */
 | ||
|           g_clear_error (&local_error);
 | ||
|         }
 | ||
|       else
 | ||
|         {
 | ||
|           g_propagate_prefixed_error (error,
 | ||
|                                       g_steal_pointer (&local_error),
 | ||
|                                       _("Error opening keyring “%s” for writing: "),
 | ||
|                                       path);
 | ||
|           goto out;
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|   new_contents = g_string_new (NULL);
 | ||
|   now = g_get_real_time () / G_USEC_PER_SEC;
 | ||
|   changed_file = FALSE;
 | ||
| 
 | ||
|   max_line_id = 0;
 | ||
|   if (contents != NULL)
 | ||
|     {
 | ||
|       guint n;
 | ||
|       lines = g_strsplit (contents, "\n", 0);
 | ||
|       for (n = 0; lines[n] != NULL; n++)
 | ||
|         {
 | ||
|           const gchar *line = lines[n];
 | ||
|           gchar **tokens;
 | ||
|           gchar *endp;
 | ||
|           gint line_id;
 | ||
|           gint64 line_when;
 | ||
|           gboolean keep_entry;
 | ||
| 
 | ||
|           if (line[0] == '\0')
 | ||
|             continue;
 | ||
| 
 | ||
|           tokens = g_strsplit (line, " ", 0);
 | ||
|           if (g_strv_length (tokens) != 3)
 | ||
|             {
 | ||
|               g_set_error (error,
 | ||
|                            G_IO_ERROR,
 | ||
|                            G_IO_ERROR_FAILED,
 | ||
|                            _("Line %d of the keyring at “%s” with content “%s” is malformed"),
 | ||
|                            n + 1,
 | ||
|                            path,
 | ||
|                            line);
 | ||
|               g_strfreev (tokens);
 | ||
|               goto out;
 | ||
|             }
 | ||
| 
 | ||
|           line_id = g_ascii_strtoll (tokens[0], &endp, 10);
 | ||
|           if (*endp != '\0')
 | ||
|             {
 | ||
|               g_set_error (error,
 | ||
|                            G_IO_ERROR,
 | ||
|                            G_IO_ERROR_FAILED,
 | ||
|                            _("First token of line %d of the keyring at “%s” with content “%s” is malformed"),
 | ||
|                            n + 1,
 | ||
|                            path,
 | ||
|                            line);
 | ||
|               g_strfreev (tokens);
 | ||
|               goto out;
 | ||
|             }
 | ||
| 
 | ||
|           line_when = g_ascii_strtoll (tokens[1], &endp, 10);
 | ||
|           if (*endp != '\0')
 | ||
|             {
 | ||
|               g_set_error (error,
 | ||
|                            G_IO_ERROR,
 | ||
|                            G_IO_ERROR_FAILED,
 | ||
|                            _("Second token of line %d of the keyring at “%s” with content “%s” is malformed"),
 | ||
|                            n + 1,
 | ||
|                            path,
 | ||
|                            line);
 | ||
|               g_strfreev (tokens);
 | ||
|               goto out;
 | ||
|             }
 | ||
| 
 | ||
| 
 | ||
|           /* D-Bus spec says:
 | ||
|            *
 | ||
|            *  Once the lockfile has been created, the server loads the
 | ||
|            *  cookie file. It should then delete any cookies that are
 | ||
|            *  old (the timeout can be fairly short), or more than a
 | ||
|            *  reasonable time in the future (so that cookies never
 | ||
|            *  accidentally become permanent, if the clock was set far
 | ||
|            *  into the future at some point). If no recent keys remain,
 | ||
|            *  the server may generate a new key.
 | ||
|            *
 | ||
|            */
 | ||
|           keep_entry = TRUE;
 | ||
|           if (line_when > now)
 | ||
|             {
 | ||
|               /* Oddball case: entry is more recent than our current wall-clock time..
 | ||
|                * This is OK, it means that another server on another machine but with
 | ||
|                * same $HOME wrote the entry. */
 | ||
|               if (line_when - now > MAX_TIME_TRAVEL_SECONDS)
 | ||
|                 {
 | ||
|                   keep_entry = FALSE;
 | ||
|                   _log ("Deleted SHA1 cookie from %" G_GUINT64_FORMAT " seconds in the future", line_when - now);
 | ||
|                 }
 | ||
|             }
 | ||
|           else
 | ||
|             {
 | ||
|               /* Discard entry if it's too old. */
 | ||
|               if (now - line_when > EXPIRE_KEYS_TIMEOUT_SECONDS)
 | ||
|                 {
 | ||
|                   keep_entry = FALSE;
 | ||
|                 }
 | ||
|             }
 | ||
| 
 | ||
|           if (!keep_entry)
 | ||
|             {
 | ||
|               changed_file = FALSE;
 | ||
|             }
 | ||
|           else
 | ||
|             {
 | ||
|               g_string_append_printf (new_contents,
 | ||
|                                       "%d %" G_GUINT64_FORMAT " %s\n",
 | ||
|                                       line_id,
 | ||
|                                       line_when,
 | ||
|                                       tokens[2]);
 | ||
|               max_line_id = MAX (line_id, max_line_id);
 | ||
|               /* Only reuse entry if not older than 5 minutes.
 | ||
|                *
 | ||
|                * (We need a bit of grace time compared to 7 minutes above.. otherwise
 | ||
|                * there's a race where we reuse the 6min59.9 secs old entry and a
 | ||
|                * split-second later another server purges the now 7 minute old entry.)
 | ||
|                */
 | ||
|               if (now - line_when < NEW_KEY_TIMEOUT_SECONDS)
 | ||
|                 {
 | ||
|                   if (!have_id)
 | ||
|                     {
 | ||
|                       use_id = line_id;
 | ||
|                       use_cookie = tokens[2]; /* steal memory */
 | ||
|                       tokens[2] = NULL;
 | ||
|                       have_id = TRUE;
 | ||
|                     }
 | ||
|                 }
 | ||
|             }
 | ||
|           g_strfreev (tokens);
 | ||
|         }
 | ||
|     } /* for each line */
 | ||
| 
 | ||
|   ret = TRUE;
 | ||
| 
 | ||
|   if (have_id)
 | ||
|     {
 | ||
|       *out_id = use_id;
 | ||
|       *out_cookie = use_cookie;
 | ||
|       use_cookie = NULL;
 | ||
|     }
 | ||
|   else
 | ||
|     {
 | ||
|       gchar *raw_cookie;
 | ||
|       *out_id = max_line_id + 1;
 | ||
|       raw_cookie = random_blob (32);
 | ||
|       *out_cookie = _g_dbus_hexencode (raw_cookie, 32);
 | ||
|       g_free (raw_cookie);
 | ||
| 
 | ||
|       g_string_append_printf (new_contents,
 | ||
|                               "%d %" G_GINT64_FORMAT " %s\n",
 | ||
|                               *out_id,
 | ||
|                               g_get_real_time () / G_USEC_PER_SEC,
 | ||
|                               *out_cookie);
 | ||
|       changed_file = TRUE;
 | ||
|     }
 | ||
| 
 | ||
|   /* and now actually write the cookie file if there are changes (this is atomic) */
 | ||
|   if (changed_file)
 | ||
|     {
 | ||
|       if (!g_file_set_contents_full (path,
 | ||
|                                      new_contents->str,
 | ||
|                                      -1,
 | ||
|                                      G_FILE_SET_CONTENTS_CONSISTENT,
 | ||
|                                      0600,
 | ||
|                                      error))
 | ||
|         {
 | ||
|           *out_id = 0;
 | ||
|           g_free (*out_cookie);
 | ||
|           *out_cookie = 0;
 | ||
|           ret = FALSE;
 | ||
|           goto out;
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|  out:
 | ||
|   /* Any error should have been propagated to @error by now */
 | ||
|   g_assert (local_error == NULL);
 | ||
| 
 | ||
|   if (lock_fd != -1)
 | ||
|     {
 | ||
|       if (!keyring_release_lock (path, lock_fd, &local_error))
 | ||
|         {
 | ||
|           if (error != NULL)
 | ||
|             {
 | ||
|               if (*error == NULL)
 | ||
|                 {
 | ||
|                   *error = local_error;
 | ||
|                 }
 | ||
|               else
 | ||
|                 {
 | ||
|                   g_prefix_error (error,
 | ||
|                                   _("(Additionally, releasing the lock for “%s” also failed: %s) "),
 | ||
|                                   path,
 | ||
|                                   local_error->message);
 | ||
|                   g_error_free (local_error);
 | ||
|                 }
 | ||
|             }
 | ||
|           else
 | ||
|             {
 | ||
|               g_error_free (local_error);
 | ||
|             }
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|   g_free (keyring_dir);
 | ||
|   g_free (path);
 | ||
|   g_strfreev (lines);
 | ||
|   g_free (contents);
 | ||
|   if (new_contents != NULL)
 | ||
|     g_string_free (new_contents, TRUE);
 | ||
|   g_free (use_cookie);
 | ||
|   return ret;
 | ||
| }
 | ||
| 
 | ||
| /* ---------------------------------------------------------------------------------------------------- */
 | ||
| 
 | ||
| static gchar *
 | ||
| generate_sha1 (const gchar *server_challenge,
 | ||
|                const gchar *client_challenge,
 | ||
|                const gchar *cookie)
 | ||
| {
 | ||
|   GString *str;
 | ||
|   gchar *sha1;
 | ||
| 
 | ||
|   str = g_string_new (server_challenge);
 | ||
|   g_string_append_c (str, ':');
 | ||
|   g_string_append (str, client_challenge);
 | ||
|   g_string_append_c (str, ':');
 | ||
|   g_string_append (str, cookie);
 | ||
|   sha1 = g_compute_checksum_for_string (G_CHECKSUM_SHA1, str->str, -1);
 | ||
|   g_string_free (str, TRUE);
 | ||
| 
 | ||
|   return sha1;
 | ||
| }
 | ||
| 
 | ||
| /* ---------------------------------------------------------------------------------------------------- */
 | ||
| 
 | ||
| static GDBusAuthMechanismState
 | ||
| mechanism_server_get_state (GDBusAuthMechanism   *mechanism)
 | ||
| {
 | ||
|   GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
 | ||
| 
 | ||
|   g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID);
 | ||
|   g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, G_DBUS_AUTH_MECHANISM_STATE_INVALID);
 | ||
| 
 | ||
|   return m->priv->state;
 | ||
| }
 | ||
| 
 | ||
| static void
 | ||
| mechanism_server_initiate (GDBusAuthMechanism   *mechanism,
 | ||
|                            const gchar          *initial_response,
 | ||
|                            gsize                 initial_response_len)
 | ||
| {
 | ||
|   GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
 | ||
| 
 | ||
|   g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism));
 | ||
|   g_return_if_fail (!m->priv->is_server && !m->priv->is_client);
 | ||
| 
 | ||
|   m->priv->is_server = TRUE;
 | ||
|   m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
 | ||
| 
 | ||
|   if (initial_response != NULL && initial_response_len > 0)
 | ||
|     {
 | ||
| #ifdef G_OS_UNIX
 | ||
|       gint64 uid;
 | ||
|       gchar *endp;
 | ||
| 
 | ||
|       uid = g_ascii_strtoll (initial_response, &endp, 10);
 | ||
|       if (*endp == '\0')
 | ||
|         {
 | ||
|           if (uid == getuid ())
 | ||
|             {
 | ||
|               m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND;
 | ||
|             }
 | ||
|         }
 | ||
| #elif defined(G_OS_WIN32)
 | ||
|       gchar *sid;
 | ||
| 
 | ||
|       sid = _g_win32_current_process_sid_string (NULL);
 | ||
| 
 | ||
|       if (g_strcmp0 (initial_response, sid) == 0)
 | ||
|         m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND;
 | ||
| 
 | ||
|       g_free (sid);
 | ||
| #else
 | ||
| #error Please implement for your OS
 | ||
| #endif
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| static void
 | ||
| mechanism_server_data_receive (GDBusAuthMechanism   *mechanism,
 | ||
|                                const gchar          *data,
 | ||
|                                gsize                 data_len)
 | ||
| {
 | ||
|   GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
 | ||
|   gchar **tokens;
 | ||
|   const gchar *client_challenge;
 | ||
|   const gchar *alleged_sha1;
 | ||
|   gchar *sha1;
 | ||
| 
 | ||
|   g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism));
 | ||
|   g_return_if_fail (m->priv->is_server && !m->priv->is_client);
 | ||
|   g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA);
 | ||
| 
 | ||
|   tokens = NULL;
 | ||
|   sha1 = NULL;
 | ||
| 
 | ||
|   tokens = g_strsplit (data, " ", 0);
 | ||
|   if (g_strv_length (tokens) != 2)
 | ||
|     {
 | ||
|       g_free (m->priv->reject_reason);
 | ||
|       m->priv->reject_reason = g_strdup_printf ("Malformed data '%s'", data);
 | ||
|       m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
 | ||
|       goto out;
 | ||
|     }
 | ||
| 
 | ||
|   client_challenge = tokens[0];
 | ||
|   alleged_sha1 = tokens[1];
 | ||
| 
 | ||
|   sha1 = generate_sha1 (m->priv->server_challenge, client_challenge, m->priv->cookie);
 | ||
| 
 | ||
|   if (g_strcmp0 (sha1, alleged_sha1) == 0)
 | ||
|     {
 | ||
|       m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED;
 | ||
|     }
 | ||
|   else
 | ||
|     {
 | ||
|       g_free (m->priv->reject_reason);
 | ||
|       m->priv->reject_reason = g_strdup_printf ("SHA-1 mismatch");
 | ||
|       m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
 | ||
|     }
 | ||
| 
 | ||
|  out:
 | ||
|   g_strfreev (tokens);
 | ||
|   g_free (sha1);
 | ||
| }
 | ||
| 
 | ||
| static gchar *
 | ||
| mechanism_server_data_send (GDBusAuthMechanism   *mechanism,
 | ||
|                             gsize                *out_data_len)
 | ||
| {
 | ||
|   GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
 | ||
|   gchar *s;
 | ||
|   gint cookie_id;
 | ||
|   const gchar *cookie_context;
 | ||
|   GError *error;
 | ||
| 
 | ||
|   g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), NULL);
 | ||
|   g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL);
 | ||
|   g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL);
 | ||
| 
 | ||
|   s = NULL;
 | ||
|   *out_data_len = 0;
 | ||
| 
 | ||
|   /* TODO: use GDBusAuthObserver here to get the cookie context to use? */
 | ||
|   cookie_context = "org_gtk_gdbus_general";
 | ||
| 
 | ||
|   cookie_id = -1;
 | ||
|   error = NULL;
 | ||
|   if (!keyring_generate_entry (cookie_context,
 | ||
|                                &cookie_id,
 | ||
|                                &m->priv->cookie,
 | ||
|                                &error))
 | ||
|     {
 | ||
|       g_free (m->priv->reject_reason);
 | ||
|       m->priv->reject_reason = g_strdup_printf ("Error adding entry to keyring: %s", error->message);
 | ||
|       g_error_free (error);
 | ||
|       m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
 | ||
|       goto out;
 | ||
|     }
 | ||
| 
 | ||
|   m->priv->server_challenge = random_ascii_string (16);
 | ||
|   s = g_strdup_printf ("%s %d %s",
 | ||
|                        cookie_context,
 | ||
|                        cookie_id,
 | ||
|                        m->priv->server_challenge);
 | ||
|   *out_data_len = strlen (s);
 | ||
| 
 | ||
|   m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA;
 | ||
| 
 | ||
|  out:
 | ||
|   return s;
 | ||
| }
 | ||
| 
 | ||
| static gchar *
 | ||
| mechanism_server_get_reject_reason (GDBusAuthMechanism   *mechanism)
 | ||
| {
 | ||
|   GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
 | ||
| 
 | ||
|   g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), NULL);
 | ||
|   g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL);
 | ||
|   g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_REJECTED, NULL);
 | ||
| 
 | ||
|   return g_strdup (m->priv->reject_reason);
 | ||
| }
 | ||
| 
 | ||
| static void
 | ||
| mechanism_server_shutdown (GDBusAuthMechanism   *mechanism)
 | ||
| {
 | ||
|   GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
 | ||
| 
 | ||
|   g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism));
 | ||
|   g_return_if_fail (m->priv->is_server && !m->priv->is_client);
 | ||
| 
 | ||
|   m->priv->is_server = FALSE;
 | ||
| }
 | ||
| 
 | ||
| /* ---------------------------------------------------------------------------------------------------- */
 | ||
| 
 | ||
| static GDBusAuthMechanismState
 | ||
| mechanism_client_get_state (GDBusAuthMechanism   *mechanism)
 | ||
| {
 | ||
|   GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
 | ||
| 
 | ||
|   g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID);
 | ||
|   g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, G_DBUS_AUTH_MECHANISM_STATE_INVALID);
 | ||
| 
 | ||
|   return m->priv->state;
 | ||
| }
 | ||
| 
 | ||
| static gchar *
 | ||
| mechanism_client_initiate (GDBusAuthMechanism   *mechanism,
 | ||
|                            GDBusConnectionFlags  conn_flags,
 | ||
|                            gsize                *out_initial_response_len)
 | ||
| {
 | ||
|   GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
 | ||
|   gchar *initial_response;
 | ||
| 
 | ||
|   g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), NULL);
 | ||
|   g_return_val_if_fail (!m->priv->is_server && !m->priv->is_client, NULL);
 | ||
| 
 | ||
|   m->priv->is_client = TRUE;
 | ||
| 
 | ||
|   *out_initial_response_len = 0;
 | ||
| 
 | ||
| #ifdef G_OS_UNIX
 | ||
|   initial_response = g_strdup_printf ("%" G_GINT64_FORMAT, (gint64) getuid ());
 | ||
| #elif defined (G_OS_WIN32)
 | ||
|   initial_response = _g_win32_current_process_sid_string (NULL);
 | ||
| #else
 | ||
| #error Please implement for your OS
 | ||
| #endif
 | ||
|   if (initial_response)
 | ||
|     {
 | ||
|       m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA;
 | ||
|       *out_initial_response_len = strlen (initial_response);
 | ||
|     }
 | ||
|   else
 | ||
|     {
 | ||
|       m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
 | ||
|     }
 | ||
| 
 | ||
|   return initial_response;
 | ||
| }
 | ||
| 
 | ||
| static void
 | ||
| mechanism_client_data_receive (GDBusAuthMechanism   *mechanism,
 | ||
|                                const gchar          *data,
 | ||
|                                gsize                 data_len)
 | ||
| {
 | ||
|   GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
 | ||
|   gchar **tokens;
 | ||
|   const gchar *cookie_context;
 | ||
|   guint cookie_id;
 | ||
|   const gchar *server_challenge;
 | ||
|   gchar *client_challenge;
 | ||
|   gchar *endp;
 | ||
|   gchar *cookie;
 | ||
|   GError *error;
 | ||
|   gchar *sha1;
 | ||
| 
 | ||
|   g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism));
 | ||
|   g_return_if_fail (m->priv->is_client && !m->priv->is_server);
 | ||
|   g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA);
 | ||
| 
 | ||
|   tokens = NULL;
 | ||
|   cookie = NULL;
 | ||
|   client_challenge = NULL;
 | ||
| 
 | ||
|   tokens = g_strsplit (data, " ", 0);
 | ||
|   if (g_strv_length (tokens) != 3)
 | ||
|     {
 | ||
|       g_free (m->priv->reject_reason);
 | ||
|       m->priv->reject_reason = g_strdup_printf ("Malformed data '%s'", data);
 | ||
|       m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
 | ||
|       goto out;
 | ||
|     }
 | ||
| 
 | ||
|   cookie_context = tokens[0];
 | ||
|   cookie_id = g_ascii_strtoll (tokens[1], &endp, 10);
 | ||
|   if (*endp != '\0')
 | ||
|     {
 | ||
|       g_free (m->priv->reject_reason);
 | ||
|       m->priv->reject_reason = g_strdup_printf ("Malformed cookie_id '%s'", tokens[1]);
 | ||
|       m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
 | ||
|       goto out;
 | ||
|     }
 | ||
|   server_challenge = tokens[2];
 | ||
| 
 | ||
|   error = NULL;
 | ||
|   cookie = keyring_lookup_entry (cookie_context, cookie_id, &error);
 | ||
|   if (cookie == NULL)
 | ||
|     {
 | ||
|       g_free (m->priv->reject_reason);
 | ||
|       m->priv->reject_reason = g_strdup_printf ("Problems looking up entry in keyring: %s", error->message);
 | ||
|       g_error_free (error);
 | ||
|       m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
 | ||
|       goto out;
 | ||
|     }
 | ||
| 
 | ||
|   client_challenge = random_ascii_string (16);
 | ||
|   sha1 = generate_sha1 (server_challenge, client_challenge, cookie);
 | ||
|   m->priv->to_send = g_strdup_printf ("%s %s", client_challenge, sha1);
 | ||
|   g_free (sha1);
 | ||
|   m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND;
 | ||
| 
 | ||
|  out:
 | ||
|   g_strfreev (tokens);
 | ||
|   g_free (cookie);
 | ||
|   g_free (client_challenge);
 | ||
| }
 | ||
| 
 | ||
| static gchar *
 | ||
| mechanism_client_data_send (GDBusAuthMechanism   *mechanism,
 | ||
|                             gsize                *out_data_len)
 | ||
| {
 | ||
|   GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
 | ||
| 
 | ||
|   g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), NULL);
 | ||
|   g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, NULL);
 | ||
|   g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL);
 | ||
| 
 | ||
|   g_assert (m->priv->to_send != NULL);
 | ||
| 
 | ||
|   m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED;
 | ||
| 
 | ||
|   *out_data_len = strlen (m->priv->to_send);
 | ||
|   return g_strdup (m->priv->to_send);
 | ||
| }
 | ||
| 
 | ||
| static void
 | ||
| mechanism_client_shutdown (GDBusAuthMechanism   *mechanism)
 | ||
| {
 | ||
|   GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
 | ||
| 
 | ||
|   g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism));
 | ||
|   g_return_if_fail (m->priv->is_client && !m->priv->is_server);
 | ||
| 
 | ||
|   m->priv->is_client = FALSE;
 | ||
| }
 | ||
| 
 | ||
| /* ---------------------------------------------------------------------------------------------------- */
 |