/* 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 . * * Author: David Zeuthen */ #include "config.h" #include #include #include #include #include #ifdef G_OS_UNIX #include #endif #ifdef G_OS_WIN32 #include #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; } /* ---------------------------------------------------------------------------------------------------- */