glib/gio/gdbusauthmechanismsha1.c
Dan Winship 4b94c0831e Use 'dumb quotes' rather than `really dumb quotes'
Back in the far-off twentieth century, it was normal on unix
workstations for U+0060 GRAVE ACCENT to be drawn as "‛" and for U+0027
APOSTROPHE to be drawn as "’". This led to the convention of using
them as poor-man's ‛smart quotes’ in ASCII-only text.

However, "'" is now universally drawn as a vertical line, and "`" at a
45-degree angle, making them an `odd couple' when used together.

Unfortunately, there are lots of very old strings in glib, and also
lots of new strings in which people have kept up the old tradition,
perhaps entirely unaware that it used to not look stupid.

Fix this by just using 'dumb quotes' everywhere.

https://bugzilla.gnome.org/show_bug.cgi?id=700746
2013-05-21 11:23:22 -03:00

1226 lines
37 KiB
C

/* GDBus - GLib D-Bus Library
*
* Copyright (C) 2008-2010 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* Author: David Zeuthen <davidz@redhat.com>
*/
#include "config.h"
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef _WIN32
#include <io.h>
#endif
#include <glib/gstdio.h>
#include "gdbusauthmechanismsha1.h"
#include "gcredentials.h"
#include "gdbuserror.h"
#include "gioenumtypes.h"
#include "gioerror.h"
#include "gdbusprivate.h"
#include "glibintl.h"
struct _GDBusAuthMechanismSha1Private
{
gboolean is_client;
gboolean is_server;
GDBusAuthMechanismState state;
/* 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,
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 (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->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;
g_type_class_add_private (klass, sizeof (GDBusAuthMechanismSha1Private));
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_TYPE_INSTANCE_GET_PRIVATE (mechanism,
G_TYPE_DBUS_AUTH_MECHANISM_SHA1,
GDBusAuthMechanismSha1Private);
}
/* ---------------------------------------------------------------------------------------------------- */
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;
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);
}
if (g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
{
if (g_getenv ("G_DBUS_COOKIE_SHA1_KEYRING_DIR_IGNORE_PERMISSION") == NULL)
{
#ifdef G_OS_UNIX
struct stat statbuf;
if (stat (path, &statbuf) != 0)
{
g_set_error (error,
G_IO_ERROR,
g_io_error_from_errno (errno),
_("Error when getting information for directory '%s': %s"),
path,
strerror (errno));
g_free (path);
path = NULL;
goto out;
}
if ((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,
statbuf.st_mode & 0777);
g_free (path);
path = NULL;
goto out;
}
#else
#ifdef __GNUC__
#warning Please implement permission checking on this non-UNIX platform
#endif
#endif
}
goto out;
}
if (g_mkdir (path, 0700) != 0)
{
g_set_error (error,
G_IO_ERROR,
g_io_error_from_errno (errno),
_("Error creating directory '%s': %s"),
path,
strerror (errno));
g_free (path);
path = NULL;
goto out;
}
out:
return path;
}
/* ---------------------------------------------------------------------------------------------------- */
static void
append_nibble (GString *s, gint val)
{
g_string_append_c (s, val >= 10 ? ('a' + val - 10) : ('0' + val));
}
static gchar *
hexencode (const gchar *str,
gssize len)
{
guint n;
GString *s;
if (len == -1)
len = strlen (str);
s = g_string_new (NULL);
for (n = 0; n < len; n++)
{
gint val;
gint upper_nibble;
gint lower_nibble;
val = ((const guchar *) str)[n];
upper_nibble = val >> 4;
lower_nibble = val & 0x0f;
append_nibble (s, upper_nibble);
append_nibble (s, lower_nibble);
}
return g_string_free (s, FALSE);
}
/* ---------------------------------------------------------------------------------------------------- */
/* 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;
guint64 line_when;
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);
line_when = line_when; /* To avoid -Wunused-but-set-variable */
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);
}
static gint
keyring_acquire_lock (const gchar *path,
GError **error)
{
gchar *lock;
gint ret;
guint num_tries;
guint num_create_tries;
g_return_val_if_fail (path != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
ret = -1;
lock = g_strdup_printf ("%s.lock", path);
/* This is what the D-Bus spec says
*
* 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
*/
num_create_tries = 0;
#ifdef EEXISTS
again:
#endif
num_tries = 0;
while (g_file_test (lock, G_FILE_TEST_EXISTS))
{
/* sleep 10ms, then try again */
g_usleep (1000*10);
num_tries++;
if (num_tries == 50)
{
/* ok, we slept 50*10ms = 0.5 seconds. Conclude that the lock file must be
* stale (nuke the it from orbit)
*/
if (g_unlink (lock) != 0)
{
g_set_error (error,
G_IO_ERROR,
g_io_error_from_errno (errno),
_("Error deleting stale lock file '%s': %s"),
lock,
strerror (errno));
goto out;
}
_log ("Deleted stale lock file '%s'", lock);
break;
}
}
ret = g_open (lock, O_CREAT |
#ifdef O_EXCL
O_EXCL,
#else
0,
#endif
0700);
if (ret == -1)
{
#ifdef EEXISTS
/* EEXIST: pathname already exists and O_CREAT and O_EXCL were used. */
if (errno == EEXISTS)
{
num_create_tries++;
if (num_create_tries < 5)
goto again;
}
#endif
num_create_tries = num_create_tries; /* To avoid -Wunused-but-set-variable */
g_set_error (error,
G_IO_ERROR,
g_io_error_from_errno (errno),
_("Error creating lock file '%s': %s"),
lock,
strerror (errno));
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)
{
g_set_error (error,
G_IO_ERROR,
g_io_error_from_errno (errno),
_("Error closing (unlinked) lock file '%s': %s"),
lock,
strerror (errno));
goto out;
}
if (g_unlink (lock) != 0)
{
g_set_error (error,
G_IO_ERROR,
g_io_error_from_errno (errno),
_("Error unlinking lock file '%s': %s"),
lock,
strerror (errno));
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;
gchar **lines;
gint max_line_id;
GString *new_contents;
guint64 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;
local_error = NULL;
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_error_free (local_error);
}
else
{
g_propagate_prefixed_error (error,
local_error,
_("Error opening keyring '%s' for writing: "),
path);
goto out;
}
}
new_contents = g_string_new (NULL);
now = time (NULL);
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;
guint64 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;
}
line_when = line_when; /* To avoid -Wunused-but-set-variable */
/* 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.
*
* So discard the entry if it's more than 1 day in the future ("reasonable
* time in the future").
*/
if (line_when - now > 24*60*60)
{
keep_entry = FALSE;
_log ("Deleted SHA1 cookie from %" G_GUINT64_FORMAT " seconds in the future", line_when - now);
}
}
else
{
/* Discard entry if it's older than 15 minutes ("can be fairly short") */
if (now - line_when > 15*60)
{
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 10 minutes.
*
* (We need a bit of grace time compared to 15 minutes above.. otherwise
* there's a race where we reuse the 14min59.9 secs old entry and a
* split-second later another server purges the now 15 minute old entry.)
*/
if (now - line_when < 10 * 60)
{
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 = hexencode (raw_cookie, 32);
g_free (raw_cookie);
g_string_append_printf (new_contents,
"%d %" G_GUINT64_FORMAT " %s\n",
*out_id,
(guint64) time (NULL),
*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 (path,
new_contents->str,
-1,
error))
{
*out_id = 0;
*out_cookie = 0;
g_free (*out_cookie);
ret = FALSE;
goto out;
}
}
out:
if (lock_fd != -1)
{
GError *local_error;
local_error = NULL;
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);
}
}
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 && strlen (initial_response) > 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_dbus_win32_get_user_sid ();
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_warning ("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
{
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;
/* 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_warning ("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);
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);
/* can never end up here because we are never in the REJECTED state */
g_assert_not_reached ();
return NULL;
}
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,
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;
m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA;
*out_initial_response_len = -1;
#ifdef G_OS_UNIX
initial_response = g_strdup_printf ("%" G_GINT64_FORMAT, (gint64) getuid ());
#elif defined (G_OS_WIN32)
initial_response = _g_dbus_win32_get_user_sid ();
#else
#error Please implement for your OS
#endif
g_assert (initial_response != NULL);
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_warning ("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_warning ("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_warning ("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;
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;
}
/* ---------------------------------------------------------------------------------------------------- */