/* 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;
}
/* ---------------------------------------------------------------------------------------------------- */