mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-29 17:06:15 +01:00
1220 lines
37 KiB
C
1220 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 statting 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);
|
|
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 */
|
|
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
|
|
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_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;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------------- */
|