diff options
Diffstat (limited to 'subversion/libsvn_subr/auth.c')
-rw-r--r-- | subversion/libsvn_subr/auth.c | 652 |
1 files changed, 652 insertions, 0 deletions
diff --git a/subversion/libsvn_subr/auth.c b/subversion/libsvn_subr/auth.c new file mode 100644 index 0000000..5406358 --- /dev/null +++ b/subversion/libsvn_subr/auth.c @@ -0,0 +1,652 @@ +/* + * auth.c: authentication support functions for Subversion + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + +#include <apr_pools.h> +#include <apr_tables.h> +#include <apr_strings.h> + +#include "svn_hash.h" +#include "svn_types.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_auth.h" +#include "svn_config.h" +#include "svn_private_config.h" +#include "svn_dso.h" +#include "svn_version.h" +#include "private/svn_dep_compat.h" + +#include "auth.h" + +/* AN OVERVIEW + =========== + + A good way to think of this machinery is as a set of tables. + + - Each type of credentials selects a single table. + + - In a given table, each row is a 'provider' capable of returning + the same type of credentials. Each column represents a + provider's repeated attempts to provide credentials. + + + Fetching Credentials from Providers + ----------------------------------- + + When the caller asks for a particular type of credentials, the + machinery in this file walks over the appropriate table. It starts + with the first provider (first row), and calls first_credentials() + to get the first set of credentials (first column). If the caller + is unhappy with the credentials, then each subsequent call to + next_credentials() traverses the row from left to right. If the + provider returns error at any point, then we go to the next provider + (row). We continue this way until every provider fails, or + until the client is happy with the returned credentials. + + Note that the caller cannot see the table traversal, and thus has + no idea when we switch providers. + + + Storing Credentials with Providers + ---------------------------------- + + When the server has validated a set of credentials, and when + credential caching is enabled, we have the chance to store those + credentials for later use. The provider which provided the working + credentials is the first one given the opportunity to (re)cache + those credentials. Its save_credentials() function is invoked with + the working credentials. If that provider reports that it + successfully stored the credentials, we're done. Otherwise, we + walk the providers (rows) for that type of credentials in order + from the top of the table, allowing each in turn the opportunity to + store the credentials. When one reports that it has done so + successfully -- or when we run out of providers (rows) to try -- + the table walk ends. +*/ + + + +/* This effectively defines a single table. Every provider in this + array returns the same kind of credentials. */ +typedef struct provider_set_t +{ + /* ordered array of svn_auth_provider_object_t */ + apr_array_header_t *providers; + +} provider_set_t; + + +/* The main auth baton. */ +struct svn_auth_baton_t +{ + /* a collection of tables. maps cred_kind -> provider_set */ + apr_hash_t *tables; + + /* the pool I'm allocated in. */ + apr_pool_t *pool; + + /* run-time parameters needed by providers. */ + apr_hash_t *parameters; + + /* run-time credentials cache. */ + apr_hash_t *creds_cache; + +}; + +/* Abstracted iteration baton */ +struct svn_auth_iterstate_t +{ + provider_set_t *table; /* the table being searched */ + int provider_idx; /* the current provider (row) */ + svn_boolean_t got_first; /* did we get the provider's first creds? */ + void *provider_iter_baton; /* the provider's own iteration context */ + const char *realmstring; /* The original realmstring passed in */ + const char *cache_key; /* key to use in auth_baton's creds_cache */ + svn_auth_baton_t *auth_baton; /* the original auth_baton. */ +}; + + + +void +svn_auth_open(svn_auth_baton_t **auth_baton, + const apr_array_header_t *providers, + apr_pool_t *pool) +{ + svn_auth_baton_t *ab; + svn_auth_provider_object_t *provider; + int i; + + /* Build the auth_baton. */ + ab = apr_pcalloc(pool, sizeof(*ab)); + ab->tables = apr_hash_make(pool); + ab->parameters = apr_hash_make(pool); + ab->creds_cache = apr_hash_make(pool); + ab->pool = pool; + + /* Register each provider in order. Providers of different + credentials will be automatically sorted into different tables by + register_provider(). */ + for (i = 0; i < providers->nelts; i++) + { + provider_set_t *table; + provider = APR_ARRAY_IDX(providers, i, svn_auth_provider_object_t *); + + /* Add it to the appropriate table in the auth_baton */ + table = svn_hash_gets(ab->tables, provider->vtable->cred_kind); + if (! table) + { + table = apr_pcalloc(pool, sizeof(*table)); + table->providers + = apr_array_make(pool, 1, sizeof(svn_auth_provider_object_t *)); + + svn_hash_sets(ab->tables, provider->vtable->cred_kind, table); + } + APR_ARRAY_PUSH(table->providers, svn_auth_provider_object_t *) + = provider; + } + + *auth_baton = ab; +} + + + +void +svn_auth_set_parameter(svn_auth_baton_t *auth_baton, + const char *name, + const void *value) +{ + svn_hash_sets(auth_baton->parameters, name, value); +} + +const void * +svn_auth_get_parameter(svn_auth_baton_t *auth_baton, + const char *name) +{ + return svn_hash_gets(auth_baton->parameters, name); +} + + +/* Return the key used to address the in-memory cache of auth + credentials of type CRED_KIND and associated with REALMSTRING. */ +static const char * +make_cache_key(const char *cred_kind, + const char *realmstring, + apr_pool_t *pool) +{ + return apr_pstrcat(pool, cred_kind, ":", realmstring, (char *)NULL); +} + +svn_error_t * +svn_auth_first_credentials(void **credentials, + svn_auth_iterstate_t **state, + const char *cred_kind, + const char *realmstring, + svn_auth_baton_t *auth_baton, + apr_pool_t *pool) +{ + int i = 0; + provider_set_t *table; + svn_auth_provider_object_t *provider = NULL; + void *creds = NULL; + void *iter_baton = NULL; + svn_boolean_t got_first = FALSE; + svn_auth_iterstate_t *iterstate; + const char *cache_key; + + /* Get the appropriate table of providers for CRED_KIND. */ + table = svn_hash_gets(auth_baton->tables, cred_kind); + if (! table) + return svn_error_createf(SVN_ERR_AUTHN_NO_PROVIDER, NULL, + _("No provider registered for '%s' credentials"), + cred_kind); + + /* First, see if we have cached creds in the auth_baton. */ + cache_key = make_cache_key(cred_kind, realmstring, pool); + creds = svn_hash_gets(auth_baton->creds_cache, cache_key); + if (creds) + { + got_first = FALSE; + } + else + /* If not, find a provider that can give "first" credentials. */ + { + /* Find a provider that can give "first" credentials. */ + for (i = 0; i < table->providers->nelts; i++) + { + provider = APR_ARRAY_IDX(table->providers, i, + svn_auth_provider_object_t *); + SVN_ERR(provider->vtable->first_credentials(&creds, &iter_baton, + provider->provider_baton, + auth_baton->parameters, + realmstring, + auth_baton->pool)); + + if (creds != NULL) + { + got_first = TRUE; + break; + } + } + } + + if (! creds) + *state = NULL; + else + { + /* Build an abstract iteration state. */ + iterstate = apr_pcalloc(pool, sizeof(*iterstate)); + iterstate->table = table; + iterstate->provider_idx = i; + iterstate->got_first = got_first; + iterstate->provider_iter_baton = iter_baton; + iterstate->realmstring = apr_pstrdup(pool, realmstring); + iterstate->cache_key = cache_key; + iterstate->auth_baton = auth_baton; + *state = iterstate; + + /* Put the creds in the cache */ + svn_hash_sets(auth_baton->creds_cache, + apr_pstrdup(auth_baton->pool, cache_key), + creds); + } + + *credentials = creds; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_auth_next_credentials(void **credentials, + svn_auth_iterstate_t *state, + apr_pool_t *pool) +{ + svn_auth_baton_t *auth_baton = state->auth_baton; + svn_auth_provider_object_t *provider; + provider_set_t *table = state->table; + void *creds = NULL; + + /* Continue traversing the table from where we left off. */ + for (/* no init */; + state->provider_idx < table->providers->nelts; + state->provider_idx++) + { + provider = APR_ARRAY_IDX(table->providers, + state->provider_idx, + svn_auth_provider_object_t *); + if (! state->got_first) + { + SVN_ERR(provider->vtable->first_credentials( + &creds, &(state->provider_iter_baton), + provider->provider_baton, auth_baton->parameters, + state->realmstring, auth_baton->pool)); + state->got_first = TRUE; + } + else if (provider->vtable->next_credentials) + { + SVN_ERR(provider->vtable->next_credentials( + &creds, state->provider_iter_baton, + provider->provider_baton, auth_baton->parameters, + state->realmstring, auth_baton->pool)); + } + + if (creds != NULL) + { + /* Put the creds in the cache */ + svn_hash_sets(auth_baton->creds_cache, state->cache_key, creds); + break; + } + + state->got_first = FALSE; + } + + *credentials = creds; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_auth_save_credentials(svn_auth_iterstate_t *state, + apr_pool_t *pool) +{ + int i; + svn_auth_provider_object_t *provider; + svn_boolean_t save_succeeded = FALSE; + const char *no_auth_cache; + svn_auth_baton_t *auth_baton; + void *creds; + + if (! state || state->table->providers->nelts <= state->provider_idx) + return SVN_NO_ERROR; + + auth_baton = state->auth_baton; + creds = svn_hash_gets(state->auth_baton->creds_cache, state->cache_key); + if (! creds) + return SVN_NO_ERROR; + + /* Do not save the creds if SVN_AUTH_PARAM_NO_AUTH_CACHE is set */ + no_auth_cache = svn_hash_gets(auth_baton->parameters, + SVN_AUTH_PARAM_NO_AUTH_CACHE); + if (no_auth_cache) + return SVN_NO_ERROR; + + /* First, try to save the creds using the provider that produced them. */ + provider = APR_ARRAY_IDX(state->table->providers, + state->provider_idx, + svn_auth_provider_object_t *); + if (provider->vtable->save_credentials) + SVN_ERR(provider->vtable->save_credentials(&save_succeeded, + creds, + provider->provider_baton, + auth_baton->parameters, + state->realmstring, + pool)); + if (save_succeeded) + return SVN_NO_ERROR; + + /* Otherwise, loop from the top of the list, asking every provider + to attempt a save. ### todo: someday optimize so we don't + necessarily start from the top of the list. */ + for (i = 0; i < state->table->providers->nelts; i++) + { + provider = APR_ARRAY_IDX(state->table->providers, i, + svn_auth_provider_object_t *); + if (provider->vtable->save_credentials) + SVN_ERR(provider->vtable->save_credentials + (&save_succeeded, creds, + provider->provider_baton, + auth_baton->parameters, + state->realmstring, + pool)); + + if (save_succeeded) + break; + } + + /* ### notice that at the moment, if no provider can save, there's + no way the caller will know. */ + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_auth_forget_credentials(svn_auth_baton_t *auth_baton, + const char *cred_kind, + const char *realmstring, + apr_pool_t *scratch_pool) +{ + SVN_ERR_ASSERT((cred_kind && realmstring) || (!cred_kind && !realmstring)); + + /* If we have a CRED_KIND and REALMSTRING, we clear out just the + cached item (if any). Otherwise, empty the whole hash. */ + if (cred_kind) + { + svn_hash_sets(auth_baton->creds_cache, + make_cache_key(cred_kind, realmstring, scratch_pool), + NULL); + } + else + { + apr_hash_clear(auth_baton->creds_cache); + } + + return SVN_NO_ERROR; +} + + +svn_auth_ssl_server_cert_info_t * +svn_auth_ssl_server_cert_info_dup + (const svn_auth_ssl_server_cert_info_t *info, apr_pool_t *pool) +{ + svn_auth_ssl_server_cert_info_t *new_info + = apr_palloc(pool, sizeof(*new_info)); + + *new_info = *info; + + new_info->hostname = apr_pstrdup(pool, new_info->hostname); + new_info->fingerprint = apr_pstrdup(pool, new_info->fingerprint); + new_info->valid_from = apr_pstrdup(pool, new_info->valid_from); + new_info->valid_until = apr_pstrdup(pool, new_info->valid_until); + new_info->issuer_dname = apr_pstrdup(pool, new_info->issuer_dname); + new_info->ascii_cert = apr_pstrdup(pool, new_info->ascii_cert); + + return new_info; +} + +svn_error_t * +svn_auth_get_platform_specific_provider(svn_auth_provider_object_t **provider, + const char *provider_name, + const char *provider_type, + apr_pool_t *pool) +{ + *provider = NULL; + + if (apr_strnatcmp(provider_name, "gnome_keyring") == 0 || + apr_strnatcmp(provider_name, "kwallet") == 0) + { +#if defined(SVN_HAVE_GNOME_KEYRING) || defined(SVN_HAVE_KWALLET) + apr_dso_handle_t *dso; + apr_dso_handle_sym_t provider_function_symbol, version_function_symbol; + const char *library_label, *library_name; + const char *provider_function_name, *version_function_name; + library_name = apr_psprintf(pool, + "libsvn_auth_%s-%d.so.%d", + provider_name, + SVN_VER_MAJOR, SVN_SOVERSION); + library_label = apr_psprintf(pool, "svn_%s", provider_name); + provider_function_name = apr_psprintf(pool, + "svn_auth_get_%s_%s_provider", + provider_name, provider_type); + version_function_name = apr_psprintf(pool, + "svn_auth_%s_version", + provider_name); + SVN_ERR(svn_dso_load(&dso, library_name)); + if (dso) + { + if (apr_dso_sym(&version_function_symbol, + dso, + version_function_name) == 0) + { + svn_version_func_t version_function + = version_function_symbol; + svn_version_checklist_t check_list[2]; + + check_list[0].label = library_label; + check_list[0].version_query = version_function; + check_list[1].label = NULL; + check_list[1].version_query = NULL; + SVN_ERR(svn_ver_check_list(svn_subr_version(), check_list)); + } + if (apr_dso_sym(&provider_function_symbol, + dso, + provider_function_name) == 0) + { + if (strcmp(provider_type, "simple") == 0) + { + svn_auth_simple_provider_func_t provider_function + = provider_function_symbol; + provider_function(provider, pool); + } + else if (strcmp(provider_type, "ssl_client_cert_pw") == 0) + { + svn_auth_ssl_client_cert_pw_provider_func_t provider_function + = provider_function_symbol; + provider_function(provider, pool); + } + } + } +#endif + } + else + { +#if defined(SVN_HAVE_GPG_AGENT) + if (strcmp(provider_name, "gpg_agent") == 0 && + strcmp(provider_type, "simple") == 0) + { + svn_auth_get_gpg_agent_simple_provider(provider, pool); + } +#endif +#ifdef SVN_HAVE_KEYCHAIN_SERVICES + if (strcmp(provider_name, "keychain") == 0 && + strcmp(provider_type, "simple") == 0) + { + svn_auth_get_keychain_simple_provider(provider, pool); + } + else if (strcmp(provider_name, "keychain") == 0 && + strcmp(provider_type, "ssl_client_cert_pw") == 0) + { + svn_auth_get_keychain_ssl_client_cert_pw_provider(provider, pool); + } +#endif + +#if defined(WIN32) && !defined(__MINGW32__) + if (strcmp(provider_name, "windows") == 0 && + strcmp(provider_type, "simple") == 0) + { + svn_auth_get_windows_simple_provider(provider, pool); + } + else if (strcmp(provider_name, "windows") == 0 && + strcmp(provider_type, "ssl_client_cert_pw") == 0) + { + svn_auth_get_windows_ssl_client_cert_pw_provider(provider, pool); + } + else if (strcmp(provider_name, "windows") == 0 && + strcmp(provider_type, "ssl_server_trust") == 0) + { + svn_auth_get_windows_ssl_server_trust_provider(provider, pool); + } +#endif + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_auth_get_platform_specific_client_providers(apr_array_header_t **providers, + svn_config_t *config, + apr_pool_t *pool) +{ + svn_auth_provider_object_t *provider; + const char *password_stores_config_option; + apr_array_header_t *password_stores; + int i; + +#define SVN__MAYBE_ADD_PROVIDER(list, p) \ + { if (p) APR_ARRAY_PUSH(list, svn_auth_provider_object_t *) = p; } + +#define SVN__DEFAULT_AUTH_PROVIDER_LIST \ + "gnome-keyring,kwallet,keychain,gpg-agent,windows-cryptoapi" + + *providers = apr_array_make(pool, 12, sizeof(svn_auth_provider_object_t *)); + + /* Fetch the configured list of password stores, and split them into + an array. */ + svn_config_get(config, + &password_stores_config_option, + SVN_CONFIG_SECTION_AUTH, + SVN_CONFIG_OPTION_PASSWORD_STORES, + SVN__DEFAULT_AUTH_PROVIDER_LIST); + password_stores = svn_cstring_split(password_stores_config_option, + " ,", TRUE, pool); + + for (i = 0; i < password_stores->nelts; i++) + { + const char *password_store = APR_ARRAY_IDX(password_stores, i, + const char *); + + /* GNOME Keyring */ + if (apr_strnatcmp(password_store, "gnome-keyring") == 0) + { + SVN_ERR(svn_auth_get_platform_specific_provider(&provider, + "gnome_keyring", + "simple", + pool)); + SVN__MAYBE_ADD_PROVIDER(*providers, provider); + + SVN_ERR(svn_auth_get_platform_specific_provider(&provider, + "gnome_keyring", + "ssl_client_cert_pw", + pool)); + SVN__MAYBE_ADD_PROVIDER(*providers, provider); + } + /* GPG-AGENT */ + else if (apr_strnatcmp(password_store, "gpg-agent") == 0) + { + SVN_ERR(svn_auth_get_platform_specific_provider(&provider, + "gpg_agent", + "simple", + pool)); + SVN__MAYBE_ADD_PROVIDER(*providers, provider); + } + /* KWallet */ + else if (apr_strnatcmp(password_store, "kwallet") == 0) + { + SVN_ERR(svn_auth_get_platform_specific_provider(&provider, + "kwallet", + "simple", + pool)); + SVN__MAYBE_ADD_PROVIDER(*providers, provider); + + SVN_ERR(svn_auth_get_platform_specific_provider(&provider, + "kwallet", + "ssl_client_cert_pw", + pool)); + SVN__MAYBE_ADD_PROVIDER(*providers, provider); + } + /* Keychain */ + else if (apr_strnatcmp(password_store, "keychain") == 0) + { + SVN_ERR(svn_auth_get_platform_specific_provider(&provider, + "keychain", + "simple", + pool)); + SVN__MAYBE_ADD_PROVIDER(*providers, provider); + + SVN_ERR(svn_auth_get_platform_specific_provider(&provider, + "keychain", + "ssl_client_cert_pw", + pool)); + SVN__MAYBE_ADD_PROVIDER(*providers, provider); + } + /* Windows */ + else if (apr_strnatcmp(password_store, "windows-cryptoapi") == 0) + { + SVN_ERR(svn_auth_get_platform_specific_provider(&provider, + "windows", + "simple", + pool)); + SVN__MAYBE_ADD_PROVIDER(*providers, provider); + + SVN_ERR(svn_auth_get_platform_specific_provider(&provider, + "windows", + "ssl_client_cert_pw", + pool)); + SVN__MAYBE_ADD_PROVIDER(*providers, provider); + } + } + + return SVN_NO_ERROR; +} |