From 6b40feaff4114ab3498ad06e13063fceff4d48e9 Mon Sep 17 00:00:00 2001 From: Havoc Pennington Date: Thu, 20 Feb 2003 03:43:18 +0000 Subject: 2003-02-19 Havoc Pennington * Doxyfile.in (PREDEFINED): put DOXYGEN_SHOULD_SKIP_THIS in Doxyfile.in, not Doxyfile * dbus/dbus-keyring.c: do some hacking on this * dbus/dbus-sysdeps.c (_dbus_delete_file): new * dbus/dbus-errors.c (dbus_set_error_const): do not call dbus_error_init (dbus_set_error): remove dbus_error_init, check for message == NULL *before* we sprintf into it, and add @todo about including system headers in this file * dbus/dbus-sysdeps.c (_dbus_create_file_exclusively): new * dbus/dbus-errors.h (DBUS_ERROR_FAILED): add * dbus/dbus-sysdeps.c (get_user_info): break this function out to get various bits of user information based on either username or user ID (_dbus_homedir_from_username): new function --- dbus/dbus-keyring.c | 404 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 397 insertions(+), 7 deletions(-) (limited to 'dbus/dbus-keyring.c') diff --git a/dbus/dbus-keyring.c b/dbus/dbus-keyring.c index da66da98..0f1dd1c9 100644 --- a/dbus/dbus-keyring.c +++ b/dbus/dbus-keyring.c @@ -53,6 +53,13 @@ * @{ */ +/** The maximum time a key can be alive before we switch to a + * new one. 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 MAX_KEY_LIFETIME_SECONDS (60*5) + typedef struct { dbus_int32_t id; /**< identifier used to refer to the key */ @@ -60,10 +67,6 @@ typedef struct unsigned long creation_time; /**< when the key was generated, * as unix timestamp */ - - DBusString context; /**< Name of kind of server using this - * key, for example "login_session_bus" - */ DBusString secret; /**< the actual key */ @@ -77,11 +80,153 @@ typedef struct */ struct DBusKeyring { - DBusString filename; /**< File containing keyring */ - DBusString lock_filename; /**< Lock file for changing keyring */ + 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 */ +}; + +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 6 +/** Length of each timeout while waiting for a lock */ +#define LOCK_TIMEOUT 500 + +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 (&error); + if (_dbus_create_file_exclusively (&keyring->filename_lock, + &error)) + break; + + _dbus_verbose ("Did not get lock file: %s\n", + error.message); + dbus_error_free (&error); + + _dbus_sleep_milliseconds (LOCK_TIMEOUT); + + ++n_timeouts; + } + + if (n_timeouts == MAX_LOCK_TIMEOUTS) + { + _dbus_verbose ("Lock file timed out, assuming stale\n"); + + _dbus_delete_file (&keyring->filename_lock); + + if (!_dbus_create_file_exclusively (&keyring->filename_lock, + NULL)) + { + _dbus_verbose ("Couldn't create lock file after trying to delete the stale one, giving up\n"); + return FALSE; + } + } + + return TRUE; +} + +static void +_dbus_keyring_unlock (DBusKeyring *keyring) +{ + if (!_dbus_delete_file (&keyring->filename_lock)) + _dbus_warn ("Failed to delete lock file\n"); +} + +/** + * Reloads the keyring file, optionally adds one new key to the file, + * removes all expired keys from the file, then resaves the file. + * Stores the keys from the file in keyring->keys. + * + * @param keyring the keyring + * @param add_new #TRUE to add a new key to the file before 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) +{ + /* FIXME */ + +} /** @} */ /* end of internals */ @@ -91,5 +236,250 @@ struct DBusKeyring * @{ */ +/** + * Increments reference count of the keyring + * + * @param keyring the keyring + */ +void +_dbus_keyring_ref (DBusKeyring *keyring) +{ + keyring->refcount += 1; +} + +/** + * 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) + { + _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. If the username is #NULL, + * uses the user owning 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_homedir (const DBusString *username, + const DBusString *context, + DBusError *error) +{ + DBusString homedir; + DBusKeyring *keyring; + dbus_bool_t error_set; + DBusString dotdir; + DBusString lock_extension; + + keyring = NULL; + error_set = FALSE; + + if (!_dbus_string_init (&homedir, _DBUS_INT_MAX)) + return FALSE; + + _dbus_string_init_const (&dotdir, ".dbus-keyrings"); + _dbus_string_init_const (&lock_extension, ".lock"); + + if (username == NULL) + { + const DBusString *const_homedir; + + if (!_dbus_user_info_from_current_process (&username, + &const_homedir, + NULL)) + goto failed; + + if (!_dbus_string_copy (const_homedir, 0, + &homedir, 0)) + goto failed; + } + else + { + if (!_dbus_homedir_from_username (username, &homedir)) + goto failed; + } + + keyring = _dbus_keyring_new (); + if (keyring == NULL) + goto failed; + + /* 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; + } + + if (!_dbus_string_copy (&homedir, 0, + &keyring->directory, 0)) + goto failed; + + if (!_dbus_concat_dir_and_file (&keyring->directory, + &dotdir)) + goto failed; + + if (!_dbus_string_copy (&keyring->directory, 0, + &keyring->filename, 0)) + goto failed; + + if (!_dbus_concat_dir_and_file (&keyring->filename, + context)) + goto failed; + + if (!_dbus_string_copy (&keyring->filename, 0, + &keyring->filename_lock, 0)) + goto failed; + + if (!_dbus_concat_dir_and_file (&keyring->filename_lock, + &lock_extension)) + goto failed; + + return keyring; + + failed: + if (!error_set) + dbus_set_error_const (error, + DBUS_ERROR_NO_MEMORY, + "No memory to create keyring"); + if (keyring) + _dbus_keyring_unref (keyring); + _dbus_string_free (&homedir); + return FALSE; + +} + +/** + * 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). + * + * @param context the context + * @returns #TRUE if valid + */ +dbus_bool_t +_dbus_keyring_validate_context (const DBusString *context) +{ + if (_dbus_string_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; + } + + 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]; + + if (tv_sec - MAX_KEY_LIFETIME_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; + + 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; + } +} /** @} */ /* end of exposed API */ + -- cgit