| /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ |
| /* dbus-keyring.c Store secret cookies in your homedir |
| * |
| * Copyright (C) 2003, 2004 Red Hat Inc. |
| * |
| * Licensed under the Academic Free License version 2.1 |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program 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 General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #include <config.h> |
| #include "dbus-keyring.h" |
| #include "dbus-protocol.h" |
| #include <dbus/dbus-string.h> |
| #include <dbus/dbus-list.h> |
| #include <dbus/dbus-sysdeps.h> |
| |
| /** |
| * @defgroup DBusKeyring keyring class |
| * @ingroup DBusInternals |
| * @brief DBusKeyring data structure |
| * |
| * Types and functions related to DBusKeyring. DBusKeyring is intended |
| * to manage cookies used to authenticate clients to servers. This is |
| * essentially the "verify that client can read the user's homedir" |
| * authentication mechanism. Both client and server must have access |
| * to the homedir. |
| * |
| * The secret keys are not kept in locked memory, and are written to a |
| * file in the user's homedir. However they are transient (only used |
| * by a single server instance for a fixed period of time, then |
| * discarded). Also, the keys are not sent over the wire. |
| * |
| * @todo there's a memory leak on some codepath in here, I saw it once |
| * when running make check - probably some specific initial cookies |
| * present in the cookie file, then depending on what we do with them. |
| */ |
| |
| /** |
| * @defgroup DBusKeyringInternals DBusKeyring implementation details |
| * @ingroup DBusInternals |
| * @brief DBusKeyring implementation details |
| * |
| * The guts of DBusKeyring. |
| * |
| * @{ |
| */ |
| |
| /** The maximum age of a key before we create a new key to use in |
| * challenges. This isn't super-reliably enforced, since system |
| * clocks can change or be wrong, but we make a best effort to only |
| * use keys for a short time. |
| */ |
| #define NEW_KEY_TIMEOUT_SECONDS (60*5) |
| /** |
| * The time after which we drop a key from the secrets file. |
| * The EXPIRE_KEYS_TIMEOUT_SECONDS - NEW_KEY_TIMEOUT_SECONDS is the minimum |
| * time window a client has to complete authentication. |
| */ |
| #define EXPIRE_KEYS_TIMEOUT_SECONDS (NEW_KEY_TIMEOUT_SECONDS + (60*2)) |
| /** |
| * The maximum amount of time a key can be in the future. |
| */ |
| #define MAX_TIME_TRAVEL_SECONDS (60*5) |
| |
| /** |
| * Maximum number of keys in the keyring before |
| * we just ignore the rest |
| */ |
| #ifdef DBUS_BUILD_TESTS |
| #define MAX_KEYS_IN_FILE 10 |
| #else |
| #define MAX_KEYS_IN_FILE 256 |
| #endif |
| |
| /** |
| * A single key from the cookie file |
| */ |
| typedef struct |
| { |
| dbus_int32_t id; /**< identifier used to refer to the key */ |
| |
| long creation_time; /**< when the key was generated, |
| * as unix timestamp. signed long |
| * matches struct timeval. |
| */ |
| |
| DBusString secret; /**< the actual key */ |
| |
| } DBusKey; |
| |
| /** |
| * @brief Internals of DBusKeyring. |
| * |
| * DBusKeyring internals. DBusKeyring is an opaque object, it must be |
| * used via accessor functions. |
| */ |
| struct DBusKeyring |
| { |
| int refcount; /**< Reference count */ |
| DBusString directory; /**< Directory the below two items are inside */ |
| DBusString filename; /**< Keyring filename */ |
| DBusString filename_lock; /**< Name of lockfile */ |
| DBusKey *keys; /**< Keys loaded from the file */ |
| int n_keys; /**< Number of keys */ |
| DBusCredentials *credentials; /**< Credentials containing user the keyring is for */ |
| }; |
| |
| static DBusKeyring* |
| _dbus_keyring_new (void) |
| { |
| DBusKeyring *keyring; |
| |
| keyring = dbus_new0 (DBusKeyring, 1); |
| if (keyring == NULL) |
| goto out_0; |
| |
| if (!_dbus_string_init (&keyring->directory)) |
| goto out_1; |
| |
| if (!_dbus_string_init (&keyring->filename)) |
| goto out_2; |
| |
| if (!_dbus_string_init (&keyring->filename_lock)) |
| goto out_3; |
| |
| keyring->refcount = 1; |
| keyring->keys = NULL; |
| keyring->n_keys = 0; |
| |
| return keyring; |
| |
| out_3: |
| _dbus_string_free (&keyring->filename); |
| out_2: |
| _dbus_string_free (&keyring->directory); |
| out_1: |
| dbus_free (keyring); |
| out_0: |
| return NULL; |
| } |
| |
| static void |
| free_keys (DBusKey *keys, |
| int n_keys) |
| { |
| int i; |
| |
| /* should be safe for args NULL, 0 */ |
| |
| i = 0; |
| while (i < n_keys) |
| { |
| _dbus_string_free (&keys[i].secret); |
| ++i; |
| } |
| |
| dbus_free (keys); |
| } |
| |
| /* Our locking scheme is highly unreliable. However, there is |
| * unfortunately no reliable locking scheme in user home directories; |
| * between bugs in Linux NFS, people using Tru64 or other total crap |
| * NFS, AFS, random-file-system-of-the-week, and so forth, fcntl() in |
| * homedirs simply generates tons of bug reports. This has been |
| * learned through hard experience with GConf, unfortunately. |
| * |
| * This bad hack might work better for the kind of lock we have here, |
| * which we don't expect to hold for any length of time. Crashing |
| * while we hold it should be unlikely, and timing out such that we |
| * delete a stale lock should also be unlikely except when the |
| * filesystem is running really slowly. Stuff might break in corner |
| * cases but as long as it's not a security-level breakage it should |
| * be OK. |
| */ |
| |
| /** Maximum number of timeouts waiting for lock before we decide it's stale */ |
| #define MAX_LOCK_TIMEOUTS 32 |
| /** Length of each timeout while waiting for a lock */ |
| #define LOCK_TIMEOUT_MILLISECONDS 250 |
| |
| static dbus_bool_t |
| _dbus_keyring_lock (DBusKeyring *keyring) |
| { |
| int n_timeouts; |
| |
| n_timeouts = 0; |
| while (n_timeouts < MAX_LOCK_TIMEOUTS) |
| { |
| DBusError error = DBUS_ERROR_INIT; |
| |
| if (_dbus_create_file_exclusively (&keyring->filename_lock, |
| &error)) |
| break; |
| |
| _dbus_verbose ("Did not get lock file, sleeping %d milliseconds (%s)\n", |
| LOCK_TIMEOUT_MILLISECONDS, error.message); |
| dbus_error_free (&error); |
| |
| _dbus_sleep_milliseconds (LOCK_TIMEOUT_MILLISECONDS); |
| |
| ++n_timeouts; |
| } |
| |
| if (n_timeouts == MAX_LOCK_TIMEOUTS) |
| { |
| DBusError error = DBUS_ERROR_INIT; |
| |
| _dbus_verbose ("Lock file timed out %d times, assuming stale\n", |
| n_timeouts); |
| |
| if (!_dbus_delete_file (&keyring->filename_lock, &error)) |
| { |
| _dbus_verbose ("Couldn't delete old lock file: %s\n", |
| error.message); |
| dbus_error_free (&error); |
| return FALSE; |
| } |
| |
| if (!_dbus_create_file_exclusively (&keyring->filename_lock, |
| &error)) |
| { |
| _dbus_verbose ("Couldn't create lock file after deleting stale one: %s\n", |
| error.message); |
| dbus_error_free (&error); |
| return FALSE; |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| static void |
| _dbus_keyring_unlock (DBusKeyring *keyring) |
| { |
| DBusError error = DBUS_ERROR_INIT; |
| |
| if (!_dbus_delete_file (&keyring->filename_lock, &error)) |
| { |
| _dbus_warn ("Failed to delete lock file: %s\n", |
| error.message); |
| dbus_error_free (&error); |
| } |
| } |
| |
| static DBusKey* |
| find_key_by_id (DBusKey *keys, |
| int n_keys, |
| int id) |
| { |
| int i; |
| |
| i = 0; |
| while (i < n_keys) |
| { |
| if (keys[i].id == id) |
| return &keys[i]; |
| |
| ++i; |
| } |
| |
| return NULL; |
| } |
| |
| static dbus_bool_t |
| add_new_key (DBusKey **keys_p, |
| int *n_keys_p, |
| DBusError *error) |
| { |
| DBusKey *new; |
| DBusString bytes; |
| int id; |
| long timestamp; |
| const unsigned char *s; |
| dbus_bool_t retval; |
| DBusKey *keys; |
| int n_keys; |
| |
| _DBUS_ASSERT_ERROR_IS_CLEAR (error); |
| |
| if (!_dbus_string_init (&bytes)) |
| { |
| dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); |
| return FALSE; |
| } |
| |
| keys = *keys_p; |
| n_keys = *n_keys_p; |
| retval = FALSE; |
| |
| /* Generate an integer ID and then the actual key. */ |
| retry: |
| |
| if (!_dbus_generate_random_bytes (&bytes, 4)) |
| { |
| dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); |
| goto out; |
| } |
| |
| s = (const unsigned char*) _dbus_string_get_const_data (&bytes); |
| |
| id = s[0] | (s[1] << 8) | (s[2] << 16) | (s[3] << 24); |
| if (id < 0) |
| id = - id; |
| _dbus_assert (id >= 0); |
| |
| if (find_key_by_id (keys, n_keys, id) != NULL) |
| { |
| _dbus_string_set_length (&bytes, 0); |
| _dbus_verbose ("Key ID %d already existed, trying another one\n", |
| id); |
| goto retry; |
| } |
| |
| _dbus_verbose ("Creating key with ID %d\n", id); |
| |
| #define KEY_LENGTH_BYTES 24 |
| _dbus_string_set_length (&bytes, 0); |
| if (!_dbus_generate_random_bytes (&bytes, KEY_LENGTH_BYTES)) |
| { |
| dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); |
| goto out; |
| } |
| |
| new = dbus_realloc (keys, sizeof (DBusKey) * (n_keys + 1)); |
| if (new == NULL) |
| { |
| dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); |
| goto out; |
| } |
| |
| keys = new; |
| *keys_p = keys; /* otherwise *keys_p ends up invalid */ |
| n_keys += 1; |
| |
| if (!_dbus_string_init (&keys[n_keys-1].secret)) |
| { |
| n_keys -= 1; /* we don't want to free the one we didn't init */ |
| dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); |
| goto out; |
| } |
| |
| _dbus_get_current_time (×tamp, NULL); |
| |
| keys[n_keys-1].id = id; |
| keys[n_keys-1].creation_time = timestamp; |
| if (!_dbus_string_move (&bytes, 0, |
| &keys[n_keys-1].secret, |
| 0)) |
| { |
| dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); |
| _dbus_string_free (&keys[n_keys-1].secret); |
| n_keys -= 1; |
| goto out; |
| } |
| |
| retval = TRUE; |
| |
| out: |
| *n_keys_p = n_keys; |
| |
| _dbus_string_free (&bytes); |
| return retval; |
| } |
| |
| /** |
| * Reloads the keyring file, optionally adds one new key to the file, |
| * removes all expired keys from the file iff a key was added, then |
| * resaves the file. Stores the keys from the file in keyring->keys. |
| * Note that the file is only resaved (written to) if a key is added, |
| * this means that only servers ever write to the file and need to |
| * lock it, which avoids a lot of lock contention at login time and |
| * such. |
| * |
| * @param keyring the keyring |
| * @param add_new #TRUE to add a new key to the file, expire keys, and resave |
| * @param error return location for errors |
| * @returns #FALSE on failure |
| */ |
| static dbus_bool_t |
| _dbus_keyring_reload (DBusKeyring *keyring, |
| dbus_bool_t add_new, |
| DBusError *error) |
| { |
| DBusString contents; |
| DBusString line; |
| dbus_bool_t retval; |
| dbus_bool_t have_lock; |
| DBusKey *keys; |
| int n_keys; |
| int i; |
| long now; |
| DBusError tmp_error; |
| |
| _DBUS_ASSERT_ERROR_IS_CLEAR (error); |
| |
| if (!_dbus_check_dir_is_private_to_user (&keyring->directory, error)) |
| return FALSE; |
| |
| if (!_dbus_string_init (&contents)) |
| { |
| dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); |
| return FALSE; |
| } |
| |
| if (!_dbus_string_init (&line)) |
| { |
| dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); |
| _dbus_string_free (&contents); |
| return FALSE; |
| } |
| |
| keys = NULL; |
| n_keys = 0; |
| retval = FALSE; |
| have_lock = FALSE; |
| |
| _dbus_get_current_time (&now, NULL); |
| |
| if (add_new) |
| { |
| if (!_dbus_keyring_lock (keyring)) |
| { |
| dbus_set_error (error, DBUS_ERROR_FAILED, |
| "Could not lock keyring file to add to it"); |
| goto out; |
| } |
| |
| have_lock = TRUE; |
| } |
| |
| dbus_error_init (&tmp_error); |
| if (!_dbus_file_get_contents (&contents, |
| &keyring->filename, |
| &tmp_error)) |
| { |
| _dbus_verbose ("Failed to load keyring file: %s\n", |
| tmp_error.message); |
| /* continue with empty keyring file, so we recreate it */ |
| dbus_error_free (&tmp_error); |
| } |
| |
| if (!_dbus_string_validate_ascii (&contents, 0, |
| _dbus_string_get_length (&contents))) |
| { |
| _dbus_warn ("Secret keyring file contains non-ASCII! Ignoring existing contents\n"); |
| _dbus_string_set_length (&contents, 0); |
| } |
| |
| /* FIXME this is badly inefficient for large keyring files |
| * (not that large keyring files exist outside of test suites) |
| */ |
| while (_dbus_string_pop_line (&contents, &line)) |
| { |
| int next; |
| long val; |
| int id; |
| long timestamp; |
| int len; |
| int end; |
| DBusKey *new; |
| |
| /* Don't load more than the max. */ |
| if (n_keys >= (add_new ? MAX_KEYS_IN_FILE - 1 : MAX_KEYS_IN_FILE)) |
| break; |
| |
| next = 0; |
| if (!_dbus_string_parse_int (&line, 0, &val, &next)) |
| { |
| _dbus_verbose ("could not parse secret key ID at start of line\n"); |
| continue; |
| } |
| |
| if (val > _DBUS_INT32_MAX || val < 0) |
| { |
| _dbus_verbose ("invalid secret key ID at start of line\n"); |
| continue; |
| } |
| |
| id = val; |
| |
| _dbus_string_skip_blank (&line, next, &next); |
| |
| if (!_dbus_string_parse_int (&line, next, ×tamp, &next)) |
| { |
| _dbus_verbose ("could not parse secret key timestamp\n"); |
| continue; |
| } |
| |
| if (timestamp < 0 || |
| (now + MAX_TIME_TRAVEL_SECONDS) < timestamp || |
| (now - EXPIRE_KEYS_TIMEOUT_SECONDS) > timestamp) |
| { |
| _dbus_verbose ("dropping/ignoring %ld-seconds old key with timestamp %ld as current time is %ld\n", |
| now - timestamp, timestamp, now); |
| continue; |
| } |
| |
| _dbus_string_skip_blank (&line, next, &next); |
| |
| len = _dbus_string_get_length (&line); |
| |
| if ((len - next) == 0) |
| { |
| _dbus_verbose ("no secret key after ID and timestamp\n"); |
| continue; |
| } |
| |
| /* We have all three parts */ |
| new = dbus_realloc (keys, sizeof (DBusKey) * (n_keys + 1)); |
| if (new == NULL) |
| { |
| dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); |
| goto out; |
| } |
| |
| keys = new; |
| n_keys += 1; |
| |
| if (!_dbus_string_init (&keys[n_keys-1].secret)) |
| { |
| n_keys -= 1; /* we don't want to free the one we didn't init */ |
| dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); |
| goto out; |
| } |
| |
| keys[n_keys-1].id = id; |
| keys[n_keys-1].creation_time = timestamp; |
| if (!_dbus_string_hex_decode (&line, next, &end, |
| &keys[n_keys-1].secret, 0)) |
| { |
| dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); |
| goto out; |
| } |
| |
| if (_dbus_string_get_length (&line) != end) |
| { |
| _dbus_verbose ("invalid hex encoding in keyring file\n"); |
| _dbus_string_free (&keys[n_keys - 1].secret); |
| n_keys -= 1; |
| continue; |
| } |
| } |
| |
| _dbus_verbose ("Successfully loaded %d existing keys\n", |
| n_keys); |
| |
| if (add_new) |
| { |
| if (!add_new_key (&keys, &n_keys, error)) |
| { |
| _dbus_verbose ("Failed to generate new key: %s\n", |
| error ? error->message : "(unknown)"); |
| goto out; |
| } |
| |
| _dbus_string_set_length (&contents, 0); |
| |
| i = 0; |
| while (i < n_keys) |
| { |
| if (!_dbus_string_append_int (&contents, |
| keys[i].id)) |
| goto nomem; |
| |
| if (!_dbus_string_append_byte (&contents, ' ')) |
| goto nomem; |
| |
| if (!_dbus_string_append_int (&contents, |
| keys[i].creation_time)) |
| goto nomem; |
| |
| if (!_dbus_string_append_byte (&contents, ' ')) |
| goto nomem; |
| |
| if (!_dbus_string_hex_encode (&keys[i].secret, 0, |
| &contents, |
| _dbus_string_get_length (&contents))) |
| goto nomem; |
| |
| if (!_dbus_string_append_byte (&contents, '\n')) |
| goto nomem; |
| |
| ++i; |
| continue; |
| |
| nomem: |
| dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); |
| goto out; |
| } |
| |
| if (!_dbus_string_save_to_file (&contents, &keyring->filename, |
| FALSE, error)) |
| goto out; |
| } |
| |
| if (keyring->keys) |
| free_keys (keyring->keys, keyring->n_keys); |
| keyring->keys = keys; |
| keyring->n_keys = n_keys; |
| keys = NULL; |
| n_keys = 0; |
| |
| retval = TRUE; |
| |
| out: |
| if (have_lock) |
| _dbus_keyring_unlock (keyring); |
| |
| if (! ((retval == TRUE && (error == NULL || error->name == NULL)) || |
| (retval == FALSE && (error == NULL || error->name != NULL)))) |
| { |
| if (error && error->name) |
| _dbus_verbose ("error is %s: %s\n", error->name, error->message); |
| _dbus_warn ("returning %d but error pointer %p name %s\n", |
| retval, error, error->name ? error->name : "(none)"); |
| _dbus_assert_not_reached ("didn't handle errors properly"); |
| } |
| |
| if (keys != NULL) |
| { |
| i = 0; |
| while (i < n_keys) |
| { |
| _dbus_string_zero (&keys[i].secret); |
| _dbus_string_free (&keys[i].secret); |
| ++i; |
| } |
| |
| dbus_free (keys); |
| } |
| |
| _dbus_string_free (&contents); |
| _dbus_string_free (&line); |
| |
| return retval; |
| } |
| |
| /** @} */ /* end of internals */ |
| |
| /** |
| * @addtogroup DBusKeyring |
| * |
| * @{ |
| */ |
| |
| /** |
| * Increments reference count of the keyring |
| * |
| * @param keyring the keyring |
| * @returns the keyring |
| */ |
| DBusKeyring * |
| _dbus_keyring_ref (DBusKeyring *keyring) |
| { |
| keyring->refcount += 1; |
| |
| return keyring; |
| } |
| |
| /** |
| * Decrements refcount and finalizes if it reaches |
| * zero. |
| * |
| * @param keyring the keyring |
| */ |
| void |
| _dbus_keyring_unref (DBusKeyring *keyring) |
| { |
| keyring->refcount -= 1; |
| |
| if (keyring->refcount == 0) |
| { |
| if (keyring->credentials) |
| _dbus_credentials_unref (keyring->credentials); |
| |
| _dbus_string_free (&keyring->filename); |
| _dbus_string_free (&keyring->filename_lock); |
| _dbus_string_free (&keyring->directory); |
| free_keys (keyring->keys, keyring->n_keys); |
| dbus_free (keyring); |
| } |
| } |
| |
| /** |
| * Creates a new keyring that lives in the ~/.dbus-keyrings directory |
| * of the given user credentials. If the credentials are #NULL or |
| * empty, uses those of the current process. |
| * |
| * @param username username to get keyring for, or #NULL |
| * @param context which keyring to get |
| * @param error return location for errors |
| * @returns the keyring or #NULL on error |
| */ |
| DBusKeyring* |
| _dbus_keyring_new_for_credentials (DBusCredentials *credentials, |
| const DBusString *context, |
| DBusError *error) |
| { |
| DBusString ringdir; |
| DBusKeyring *keyring; |
| dbus_bool_t error_set; |
| DBusError tmp_error; |
| DBusCredentials *our_credentials; |
| |
| _DBUS_ASSERT_ERROR_IS_CLEAR (error); |
| |
| keyring = NULL; |
| error_set = FALSE; |
| our_credentials = NULL; |
| |
| if (!_dbus_string_init (&ringdir)) |
| { |
| dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); |
| return NULL; |
| } |
| |
| if (credentials != NULL) |
| { |
| our_credentials = _dbus_credentials_copy (credentials); |
| } |
| else |
| { |
| our_credentials = _dbus_credentials_new_from_current_process (); |
| } |
| |
| if (our_credentials == NULL) |
| goto failed; |
| |
| if (_dbus_credentials_are_anonymous (our_credentials)) |
| { |
| if (!_dbus_credentials_add_from_current_process (our_credentials)) |
| goto failed; |
| } |
| |
| if (!_dbus_append_keyring_directory_for_credentials (&ringdir, |
| our_credentials)) |
| goto failed; |
| |
| keyring = _dbus_keyring_new (); |
| if (keyring == NULL) |
| goto failed; |
| |
| _dbus_assert (keyring->credentials == NULL); |
| keyring->credentials = our_credentials; |
| our_credentials = NULL; /* so we don't unref it again later */ |
| |
| /* should have been validated already, but paranoia check here */ |
| if (!_dbus_keyring_validate_context (context)) |
| { |
| error_set = TRUE; |
| dbus_set_error_const (error, |
| DBUS_ERROR_FAILED, |
| "Invalid context in keyring creation"); |
| goto failed; |
| } |
| |
| /* Save keyring dir in the keyring object */ |
| if (!_dbus_string_copy (&ringdir, 0, |
| &keyring->directory, 0)) |
| goto failed; |
| |
| /* Create keyring->filename based on keyring dir and context */ |
| if (!_dbus_string_copy (&keyring->directory, 0, |
| &keyring->filename, 0)) |
| goto failed; |
| |
| if (!_dbus_concat_dir_and_file (&keyring->filename, |
| context)) |
| goto failed; |
| |
| /* Create lockfile name */ |
| if (!_dbus_string_copy (&keyring->filename, 0, |
| &keyring->filename_lock, 0)) |
| goto failed; |
| |
| if (!_dbus_string_append (&keyring->filename_lock, ".lock")) |
| goto failed; |
| |
| /* Reload keyring */ |
| dbus_error_init (&tmp_error); |
| if (!_dbus_keyring_reload (keyring, FALSE, &tmp_error)) |
| { |
| _dbus_verbose ("didn't load an existing keyring: %s\n", |
| tmp_error.message); |
| dbus_error_free (&tmp_error); |
| } |
| |
| /* We don't fail fatally if we can't create the directory, |
| * but the keyring will probably always be empty |
| * unless someone else manages to create it |
| */ |
| dbus_error_init (&tmp_error); |
| if (!_dbus_create_directory (&keyring->directory, |
| &tmp_error)) |
| { |
| _dbus_verbose ("Creating keyring directory: %s\n", |
| tmp_error.message); |
| dbus_error_free (&tmp_error); |
| } |
| |
| _dbus_string_free (&ringdir); |
| |
| return keyring; |
| |
| failed: |
| if (!error_set) |
| dbus_set_error_const (error, |
| DBUS_ERROR_NO_MEMORY, |
| NULL); |
| if (our_credentials) |
| _dbus_credentials_unref (our_credentials); |
| if (keyring) |
| _dbus_keyring_unref (keyring); |
| _dbus_string_free (&ringdir); |
| return NULL; |
| |
| } |
| |
| /** |
| * Checks whether the context is a valid context. |
| * Contexts that might cause confusion when used |
| * in filenames are not allowed (contexts can't |
| * start with a dot or contain dir separators). |
| * |
| * @todo this is the most inefficient implementation |
| * imaginable. |
| * |
| * @param context the context |
| * @returns #TRUE if valid |
| */ |
| dbus_bool_t |
| _dbus_keyring_validate_context (const DBusString *context) |
| { |
| if (_dbus_string_get_length (context) == 0) |
| { |
| _dbus_verbose ("context is zero-length\n"); |
| return FALSE; |
| } |
| |
| if (!_dbus_string_validate_ascii (context, 0, |
| _dbus_string_get_length (context))) |
| { |
| _dbus_verbose ("context not valid ascii\n"); |
| return FALSE; |
| } |
| |
| /* no directory separators */ |
| if (_dbus_string_find (context, 0, "/", NULL)) |
| { |
| _dbus_verbose ("context contains a slash\n"); |
| return FALSE; |
| } |
| |
| if (_dbus_string_find (context, 0, "\\", NULL)) |
| { |
| _dbus_verbose ("context contains a backslash\n"); |
| return FALSE; |
| } |
| |
| /* prevent attempts to use dotfiles or ".." or ".lock" |
| * all of which might allow some kind of attack |
| */ |
| if (_dbus_string_find (context, 0, ".", NULL)) |
| { |
| _dbus_verbose ("context contains a dot\n"); |
| return FALSE; |
| } |
| |
| /* no spaces/tabs, those are used for separators in the protocol */ |
| if (_dbus_string_find_blank (context, 0, NULL)) |
| { |
| _dbus_verbose ("context contains a blank\n"); |
| return FALSE; |
| } |
| |
| if (_dbus_string_find (context, 0, "\n", NULL)) |
| { |
| _dbus_verbose ("context contains a newline\n"); |
| return FALSE; |
| } |
| |
| if (_dbus_string_find (context, 0, "\r", NULL)) |
| { |
| _dbus_verbose ("context contains a carriage return\n"); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static DBusKey* |
| find_recent_key (DBusKeyring *keyring) |
| { |
| int i; |
| long tv_sec, tv_usec; |
| |
| _dbus_get_current_time (&tv_sec, &tv_usec); |
| |
| i = 0; |
| while (i < keyring->n_keys) |
| { |
| DBusKey *key = &keyring->keys[i]; |
| |
| _dbus_verbose ("Key %d is %ld seconds old\n", |
| i, tv_sec - key->creation_time); |
| |
| if ((tv_sec - NEW_KEY_TIMEOUT_SECONDS) < key->creation_time) |
| return key; |
| |
| ++i; |
| } |
| |
| return NULL; |
| } |
| |
| /** |
| * Gets a recent key to use for authentication. |
| * If no recent key exists, creates one. Returns |
| * the key ID. If a key can't be written to the keyring |
| * file so no recent key can be created, returns -1. |
| * All valid keys are > 0. |
| * |
| * @param keyring the keyring |
| * @param error error on failure |
| * @returns key ID to use for auth, or -1 on failure |
| */ |
| int |
| _dbus_keyring_get_best_key (DBusKeyring *keyring, |
| DBusError *error) |
| { |
| DBusKey *key; |
| |
| _DBUS_ASSERT_ERROR_IS_CLEAR (error); |
| |
| key = find_recent_key (keyring); |
| if (key) |
| return key->id; |
| |
| /* All our keys are too old, or we've never loaded the |
| * keyring. Create a new one. |
| */ |
| if (!_dbus_keyring_reload (keyring, TRUE, |
| error)) |
| return -1; |
| |
| key = find_recent_key (keyring); |
| if (key) |
| return key->id; |
| else |
| { |
| dbus_set_error_const (error, |
| DBUS_ERROR_FAILED, |
| "No recent-enough key found in keyring, and unable to create a new key"); |
| return -1; |
| } |
| } |
| |
| /** |
| * Checks whether the keyring is for the same user as the given credentials. |
| * |
| * @param keyring the keyring |
| * @param credentials the credentials to check |
| * |
| * @returns #TRUE if the keyring belongs to the given user |
| */ |
| dbus_bool_t |
| _dbus_keyring_is_for_credentials (DBusKeyring *keyring, |
| DBusCredentials *credentials) |
| { |
| return _dbus_credentials_same_user (keyring->credentials, |
| credentials); |
| } |
| |
| /** |
| * Gets the hex-encoded secret key for the given ID. |
| * Returns #FALSE if not enough memory. Returns #TRUE |
| * but empty key on any other error such as unknown |
| * key ID. |
| * |
| * @param keyring the keyring |
| * @param key_id the key ID |
| * @param hex_key string to append hex-encoded key to |
| * @returns #TRUE if we had enough memory |
| */ |
| dbus_bool_t |
| _dbus_keyring_get_hex_key (DBusKeyring *keyring, |
| int key_id, |
| DBusString *hex_key) |
| { |
| DBusKey *key; |
| |
| key = find_key_by_id (keyring->keys, |
| keyring->n_keys, |
| key_id); |
| if (key == NULL) |
| return TRUE; /* had enough memory, so TRUE */ |
| |
| return _dbus_string_hex_encode (&key->secret, 0, |
| hex_key, |
| _dbus_string_get_length (hex_key)); |
| } |
| |
| /** @} */ /* end of exposed API */ |
| |
| #ifdef DBUS_BUILD_TESTS |
| #include "dbus-test.h" |
| #include <stdio.h> |
| |
| dbus_bool_t |
| _dbus_keyring_test (void) |
| { |
| DBusString context; |
| DBusKeyring *ring1; |
| DBusKeyring *ring2; |
| int id; |
| DBusError error; |
| int i; |
| |
| ring1 = NULL; |
| ring2 = NULL; |
| |
| /* Context validation */ |
| |
| _dbus_string_init_const (&context, "foo"); |
| _dbus_assert (_dbus_keyring_validate_context (&context)); |
| _dbus_string_init_const (&context, "org_freedesktop_blah"); |
| _dbus_assert (_dbus_keyring_validate_context (&context)); |
| |
| _dbus_string_init_const (&context, ""); |
| _dbus_assert (!_dbus_keyring_validate_context (&context)); |
| _dbus_string_init_const (&context, ".foo"); |
| _dbus_assert (!_dbus_keyring_validate_context (&context)); |
| _dbus_string_init_const (&context, "bar.foo"); |
| _dbus_assert (!_dbus_keyring_validate_context (&context)); |
| _dbus_string_init_const (&context, "bar/foo"); |
| _dbus_assert (!_dbus_keyring_validate_context (&context)); |
| _dbus_string_init_const (&context, "bar\\foo"); |
| _dbus_assert (!_dbus_keyring_validate_context (&context)); |
| _dbus_string_init_const (&context, "foo\xfa\xf0"); |
| _dbus_assert (!_dbus_keyring_validate_context (&context)); |
| _dbus_string_init_const (&context, "foo\x80"); |
| _dbus_assert (!_dbus_keyring_validate_context (&context)); |
| _dbus_string_init_const (&context, "foo\x7f"); |
| _dbus_assert (_dbus_keyring_validate_context (&context)); |
| _dbus_string_init_const (&context, "foo bar"); |
| _dbus_assert (!_dbus_keyring_validate_context (&context)); |
| |
| if (!_dbus_string_init (&context)) |
| _dbus_assert_not_reached ("no memory"); |
| if (!_dbus_string_append_byte (&context, '\0')) |
| _dbus_assert_not_reached ("no memory"); |
| _dbus_assert (!_dbus_keyring_validate_context (&context)); |
| _dbus_string_free (&context); |
| |
| /* Now verify that if we create a key in keyring 1, |
| * it is properly loaded in keyring 2 |
| */ |
| |
| _dbus_string_init_const (&context, "org_freedesktop_dbus_testsuite"); |
| dbus_error_init (&error); |
| ring1 = _dbus_keyring_new_for_credentials (NULL, &context, |
| &error); |
| _dbus_assert (ring1 != NULL); |
| _dbus_assert (error.name == NULL); |
| |
| id = _dbus_keyring_get_best_key (ring1, &error); |
| if (id < 0) |
| { |
| fprintf (stderr, "Could not load keyring: %s\n", error.message); |
| dbus_error_free (&error); |
| goto failure; |
| } |
| |
| ring2 = _dbus_keyring_new_for_credentials (NULL, &context, &error); |
| _dbus_assert (ring2 != NULL); |
| _dbus_assert (error.name == NULL); |
| |
| if (ring1->n_keys != ring2->n_keys) |
| { |
| fprintf (stderr, "Different number of keys in keyrings\n"); |
| goto failure; |
| } |
| |
| /* We guarantee we load and save keeping keys in a fixed |
| * order |
| */ |
| i = 0; |
| while (i < ring1->n_keys) |
| { |
| if (ring1->keys[i].id != ring2->keys[i].id) |
| { |
| fprintf (stderr, "Keyring 1 has first key ID %d and keyring 2 has %d\n", |
| ring1->keys[i].id, ring2->keys[i].id); |
| goto failure; |
| } |
| |
| if (ring1->keys[i].creation_time != ring2->keys[i].creation_time) |
| { |
| fprintf (stderr, "Keyring 1 has first key time %ld and keyring 2 has %ld\n", |
| ring1->keys[i].creation_time, ring2->keys[i].creation_time); |
| goto failure; |
| } |
| |
| if (!_dbus_string_equal (&ring1->keys[i].secret, |
| &ring2->keys[i].secret)) |
| { |
| fprintf (stderr, "Keyrings 1 and 2 have different secrets for same ID/timestamp\n"); |
| goto failure; |
| } |
| |
| ++i; |
| } |
| |
| printf (" %d keys in test\n", ring1->n_keys); |
| |
| /* Test ref/unref */ |
| _dbus_keyring_ref (ring1); |
| _dbus_keyring_ref (ring2); |
| _dbus_keyring_unref (ring1); |
| _dbus_keyring_unref (ring2); |
| |
| |
| /* really unref */ |
| _dbus_keyring_unref (ring1); |
| _dbus_keyring_unref (ring2); |
| |
| return TRUE; |
| |
| failure: |
| if (ring1) |
| _dbus_keyring_unref (ring1); |
| if (ring2) |
| _dbus_keyring_unref (ring2); |
| |
| return FALSE; |
| } |
| |
| #endif /* DBUS_BUILD_TESTS */ |
| |