diff options
Diffstat (limited to 'subversion/libsvn_subr/simple_providers.c')
-rw-r--r-- | subversion/libsvn_subr/simple_providers.c | 734 |
1 files changed, 734 insertions, 0 deletions
diff --git a/subversion/libsvn_subr/simple_providers.c b/subversion/libsvn_subr/simple_providers.c new file mode 100644 index 0000000..e70770a --- /dev/null +++ b/subversion/libsvn_subr/simple_providers.c @@ -0,0 +1,734 @@ +/* + * simple_providers.c: providers for SVN_AUTH_CRED_SIMPLE + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include <apr_pools.h> +#include "svn_auth.h" +#include "svn_dirent_uri.h" +#include "svn_hash.h" +#include "svn_pools.h" +#include "svn_error.h" +#include "svn_utf.h" +#include "svn_config.h" +#include "svn_user.h" + +#include "private/svn_auth_private.h" + +#include "svn_private_config.h" + +#include "auth.h" + +/*-----------------------------------------------------------------------*/ +/* File provider */ +/*-----------------------------------------------------------------------*/ + +/* The keys that will be stored on disk. These serve the same role as + similar constants in other providers. */ +#define AUTHN_USERNAME_KEY "username" +#define AUTHN_PASSWORD_KEY "password" +#define AUTHN_PASSTYPE_KEY "passtype" + +/* Baton type for the simple provider. */ +typedef struct simple_provider_baton_t +{ + svn_auth_plaintext_prompt_func_t plaintext_prompt_func; + void *prompt_baton; + /* We cache the user's answer to the plaintext prompt, keyed + * by realm, in case we'll be called multiple times for the + * same realm. */ + apr_hash_t *plaintext_answers; +} simple_provider_baton_t; + + +/* Implementation of svn_auth__password_get_t that retrieves + the plaintext password from CREDS. */ +svn_error_t * +svn_auth__simple_password_get(svn_boolean_t *done, + const char **password, + apr_hash_t *creds, + const char *realmstring, + const char *username, + apr_hash_t *parameters, + svn_boolean_t non_interactive, + apr_pool_t *pool) +{ + svn_string_t *str; + + *done = FALSE; + + str = svn_hash_gets(creds, AUTHN_USERNAME_KEY); + if (str && username && strcmp(str->data, username) == 0) + { + str = svn_hash_gets(creds, AUTHN_PASSWORD_KEY); + if (str && str->data) + { + *password = str->data; + *done = TRUE; + } + } + + return SVN_NO_ERROR; +} + +/* Implementation of svn_auth__password_set_t that stores + the plaintext password in CREDS. */ +svn_error_t * +svn_auth__simple_password_set(svn_boolean_t *done, + apr_hash_t *creds, + const char *realmstring, + const char *username, + const char *password, + apr_hash_t *parameters, + svn_boolean_t non_interactive, + apr_pool_t *pool) +{ + svn_hash_sets(creds, AUTHN_PASSWORD_KEY, svn_string_create(password, pool)); + *done = TRUE; + + return SVN_NO_ERROR; +} + +/* Set **USERNAME to the username retrieved from CREDS; ignore + other parameters. *USERNAME will have the same lifetime as CREDS. */ +static svn_boolean_t +simple_username_get(const char **username, + apr_hash_t *creds, + const char *realmstring, + svn_boolean_t non_interactive) +{ + svn_string_t *str; + str = svn_hash_gets(creds, AUTHN_USERNAME_KEY); + if (str && str->data) + { + *username = str->data; + return TRUE; + } + return FALSE; +} + + +svn_error_t * +svn_auth__simple_creds_cache_get(void **credentials, + void **iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + svn_auth__password_get_t password_get, + const char *passtype, + apr_pool_t *pool) +{ + const char *config_dir = svn_hash_gets(parameters, SVN_AUTH_PARAM_CONFIG_DIR); + svn_config_t *cfg = svn_hash_gets(parameters, + SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS); + const char *server_group = svn_hash_gets(parameters, + SVN_AUTH_PARAM_SERVER_GROUP); + const char *username = svn_hash_gets(parameters, + SVN_AUTH_PARAM_DEFAULT_USERNAME); + const char *password = svn_hash_gets(parameters, + SVN_AUTH_PARAM_DEFAULT_PASSWORD); + svn_boolean_t non_interactive = svn_hash_gets(parameters, + SVN_AUTH_PARAM_NON_INTERACTIVE) + != NULL; + const char *default_username = NULL; /* Default username from cache. */ + const char *default_password = NULL; /* Default password from cache. */ + + /* This checks if we should save the CREDS, iff saving the credentials is + allowed by the run-time configuration. */ + svn_boolean_t need_to_save = FALSE; + apr_hash_t *creds_hash = NULL; + svn_error_t *err; + svn_string_t *str; + + /* Try to load credentials from a file on disk, based on the + realmstring. Don't throw an error, though: if something went + wrong reading the file, no big deal. What really matters is that + we failed to get the creds, so allow the auth system to try the + next provider. */ + err = svn_config_read_auth_data(&creds_hash, SVN_AUTH_CRED_SIMPLE, + realmstring, config_dir, pool); + if (err) + { + svn_error_clear(err); + err = NULL; + } + else if (creds_hash) + { + /* We have something in the auth cache for this realm. */ + svn_boolean_t have_passtype = FALSE; + + /* The password type in the auth data must match the + mangler's type, otherwise the password must be + interpreted by another provider. */ + str = svn_hash_gets(creds_hash, AUTHN_PASSTYPE_KEY); + if (str && str->data) + if (passtype && (0 == strcmp(str->data, passtype))) + have_passtype = TRUE; + + /* See if we need to save this username if it is not present in + auth cache. */ + if (username) + { + if (!simple_username_get(&default_username, creds_hash, realmstring, + non_interactive)) + { + need_to_save = TRUE; + } + else + { + if (strcmp(default_username, username) != 0) + need_to_save = TRUE; + } + } + + /* See if we need to save this password if it is not present in + auth cache. */ + if (password) + { + if (have_passtype) + { + svn_boolean_t done; + + SVN_ERR(password_get(&done, &default_password, creds_hash, + realmstring, username, parameters, + non_interactive, pool)); + if (!done) + { + need_to_save = TRUE; + } + else + { + if (strcmp(default_password, password) != 0) + need_to_save = TRUE; + } + } + } + + /* If we don't have a username and a password yet, we try the + auth cache */ + if (! (username && password)) + { + if (! username) + if (!simple_username_get(&username, creds_hash, realmstring, + non_interactive)) + username = NULL; + + if (username && ! password) + { + if (! have_passtype) + password = NULL; + else + { + svn_boolean_t done; + + SVN_ERR(password_get(&done, &password, creds_hash, + realmstring, username, parameters, + non_interactive, pool)); + if (!done) + password = NULL; + + /* If the auth data didn't contain a password type, + force a write to upgrade the format of the auth + data file. */ + if (password && ! have_passtype) + need_to_save = TRUE; + } + } + } + } + else + { + /* Nothing was present in the auth cache, so indicate that these + credentials should be saved. */ + need_to_save = TRUE; + } + + /* If we don't have a username yet, check the 'servers' file */ + if (! username) + { + username = svn_config_get_server_setting(cfg, server_group, + SVN_CONFIG_OPTION_USERNAME, + NULL); + } + + /* Ask the OS for the username if we have a password but no + username. */ + if (password && ! username) + username = svn_user_get_name(pool); + + if (username && password) + { + svn_auth_cred_simple_t *creds = apr_pcalloc(pool, sizeof(*creds)); + creds->username = username; + creds->password = password; + creds->may_save = need_to_save; + *credentials = creds; + } + else + *credentials = NULL; + + *iter_baton = NULL; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_auth__simple_creds_cache_set(svn_boolean_t *saved, + void *credentials, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + svn_auth__password_set_t password_set, + const char *passtype, + apr_pool_t *pool) +{ + svn_auth_cred_simple_t *creds = credentials; + apr_hash_t *creds_hash = NULL; + const char *config_dir; + svn_error_t *err; + svn_boolean_t dont_store_passwords = + svn_hash_gets(parameters, SVN_AUTH_PARAM_DONT_STORE_PASSWORDS) != NULL; + svn_boolean_t non_interactive = svn_hash_gets(parameters, + SVN_AUTH_PARAM_NON_INTERACTIVE) + != NULL; + svn_boolean_t no_auth_cache = + (! creds->may_save) || (svn_hash_gets(parameters, + SVN_AUTH_PARAM_NO_AUTH_CACHE) + != NULL); + + /* Make sure we've been passed a passtype. */ + SVN_ERR_ASSERT(passtype != NULL); + + *saved = FALSE; + + if (no_auth_cache) + return SVN_NO_ERROR; + + config_dir = svn_hash_gets(parameters, SVN_AUTH_PARAM_CONFIG_DIR); + + /* Put the username into the credentials hash. */ + creds_hash = apr_hash_make(pool); + svn_hash_sets(creds_hash, AUTHN_USERNAME_KEY, + svn_string_create(creds->username, pool)); + + /* Don't store passwords in any form if the user has told + * us not to do so. */ + if (! dont_store_passwords) + { + svn_boolean_t may_save_password = FALSE; + + /* If the password is going to be stored encrypted, go right + * ahead and store it to disk. Else determine whether saving + * in plaintext is OK. */ + if (passtype && + (strcmp(passtype, SVN_AUTH__WINCRYPT_PASSWORD_TYPE) == 0 + || strcmp(passtype, SVN_AUTH__KEYCHAIN_PASSWORD_TYPE) == 0 + || strcmp(passtype, SVN_AUTH__KWALLET_PASSWORD_TYPE) == 0 + || strcmp(passtype, SVN_AUTH__GNOME_KEYRING_PASSWORD_TYPE) == 0 + || strcmp(passtype, SVN_AUTH__GPG_AGENT_PASSWORD_TYPE) == 0)) + { + may_save_password = TRUE; + } + else + { +#ifdef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE + may_save_password = FALSE; +#else + const char *store_plaintext_passwords = + svn_hash_gets(parameters, SVN_AUTH_PARAM_STORE_PLAINTEXT_PASSWORDS); + simple_provider_baton_t *b = + (simple_provider_baton_t *)provider_baton; + + if (store_plaintext_passwords + && svn_cstring_casecmp(store_plaintext_passwords, + SVN_CONFIG_ASK) == 0) + { + if (non_interactive) + /* In non-interactive mode, the default behaviour is + * to not store the password, because it is usually + * passed on the command line. */ + may_save_password = FALSE; + else if (b->plaintext_prompt_func) + { + /* We're interactive, and the client provided a + * prompt callback. So we can ask the user. + * + * Check for a cached answer before prompting. */ + svn_boolean_t *cached_answer; + cached_answer = svn_hash_gets(b->plaintext_answers, + realmstring); + if (cached_answer != NULL) + may_save_password = *cached_answer; + else + { + apr_pool_t *cached_answer_pool; + + /* Nothing cached for this realm, prompt the user. */ + SVN_ERR((*b->plaintext_prompt_func)(&may_save_password, + realmstring, + b->prompt_baton, + pool)); + + /* Cache the user's answer in case we're called again + * for the same realm. + * + * We allocate the answer cache in the hash table's pool + * to make sure that is has the same life time as the + * hash table itself. This means that the answer will + * survive across RA sessions -- which is important, + * because otherwise we'd prompt users once per RA session. + */ + cached_answer_pool = apr_hash_pool_get(b->plaintext_answers); + cached_answer = apr_palloc(cached_answer_pool, + sizeof(svn_boolean_t)); + *cached_answer = may_save_password; + svn_hash_sets(b->plaintext_answers, realmstring, + cached_answer); + } + } + else + { + /* TODO: We might want to default to not storing if the + * prompt callback is NULL, i.e. have may_save_password + * default to FALSE here, in order to force clients to + * implement the callback. + * + * This would change the semantics of old API though. + * + * So for now, clients that don't implement the callback + * and provide no explicit value for + * SVN_AUTH_PARAM_STORE_PLAINTEXT_PASSWORDS + * cause unencrypted passwords to be stored by default. + * Needless to say, our own client is sane, but who knows + * what other clients are doing. + */ + may_save_password = TRUE; + } + } + else if (store_plaintext_passwords + && svn_cstring_casecmp(store_plaintext_passwords, + SVN_CONFIG_FALSE) == 0) + { + may_save_password = FALSE; + } + else if (!store_plaintext_passwords + || svn_cstring_casecmp(store_plaintext_passwords, + SVN_CONFIG_TRUE) == 0) + { + may_save_password = TRUE; + } + else + { + return svn_error_createf + (SVN_ERR_BAD_CONFIG_VALUE, NULL, + _("Config error: invalid value '%s' for option '%s'"), + store_plaintext_passwords, + SVN_AUTH_PARAM_STORE_PLAINTEXT_PASSWORDS); + } +#endif + } + + if (may_save_password) + { + SVN_ERR(password_set(saved, creds_hash, realmstring, + creds->username, creds->password, + parameters, non_interactive, pool)); + if (*saved && passtype) + /* Store the password type with the auth data, so that we + know which provider owns the password. */ + svn_hash_sets(creds_hash, AUTHN_PASSTYPE_KEY, + svn_string_create(passtype, pool)); + } + } + + /* Save credentials to disk. */ + err = svn_config_write_auth_data(creds_hash, SVN_AUTH_CRED_SIMPLE, + realmstring, config_dir, pool); + if (err) + *saved = FALSE; + + /* ### return error? */ + svn_error_clear(err); + + return SVN_NO_ERROR; +} + +/* Get cached (unencrypted) credentials from the simple provider's cache. */ +static svn_error_t * +simple_first_creds(void **credentials, + void **iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + return svn_auth__simple_creds_cache_get(credentials, iter_baton, + provider_baton, parameters, + realmstring, + svn_auth__simple_password_get, + SVN_AUTH__SIMPLE_PASSWORD_TYPE, + pool); +} + +/* Save (unencrypted) credentials to the simple provider's cache. */ +static svn_error_t * +simple_save_creds(svn_boolean_t *saved, + void *credentials, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + return svn_auth__simple_creds_cache_set(saved, credentials, provider_baton, + parameters, realmstring, + svn_auth__simple_password_set, + SVN_AUTH__SIMPLE_PASSWORD_TYPE, + pool); +} + +static const svn_auth_provider_t simple_provider = { + SVN_AUTH_CRED_SIMPLE, + simple_first_creds, + NULL, + simple_save_creds +}; + + +/* Public API */ +void +svn_auth_get_simple_provider2 + (svn_auth_provider_object_t **provider, + svn_auth_plaintext_prompt_func_t plaintext_prompt_func, + void* prompt_baton, + apr_pool_t *pool) +{ + svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po)); + simple_provider_baton_t *pb = apr_pcalloc(pool, sizeof(*pb)); + + pb->plaintext_prompt_func = plaintext_prompt_func; + pb->prompt_baton = prompt_baton; + pb->plaintext_answers = apr_hash_make(pool); + + po->vtable = &simple_provider; + po->provider_baton = pb; + *provider = po; +} + + +/*-----------------------------------------------------------------------*/ +/* Prompt provider */ +/*-----------------------------------------------------------------------*/ + +/* Baton type for username/password prompting. */ +typedef struct simple_prompt_provider_baton_t +{ + svn_auth_simple_prompt_func_t prompt_func; + void *prompt_baton; + + /* how many times to re-prompt after the first one fails */ + int retry_limit; +} simple_prompt_provider_baton_t; + + +/* Iteration baton type for username/password prompting. */ +typedef struct simple_prompt_iter_baton_t +{ + /* how many times we've reprompted */ + int retries; +} simple_prompt_iter_baton_t; + + + +/*** Helper Functions ***/ +static svn_error_t * +prompt_for_simple_creds(svn_auth_cred_simple_t **cred_p, + simple_prompt_provider_baton_t *pb, + apr_hash_t *parameters, + const char *realmstring, + svn_boolean_t first_time, + svn_boolean_t may_save, + apr_pool_t *pool) +{ + const char *default_username = NULL; + const char *default_password = NULL; + + *cred_p = NULL; + + /* If we're allowed to check for default usernames and passwords, do + so. */ + if (first_time) + { + default_username = svn_hash_gets(parameters, + SVN_AUTH_PARAM_DEFAULT_USERNAME); + + /* No default username? Try the auth cache. */ + if (! default_username) + { + const char *config_dir = svn_hash_gets(parameters, + SVN_AUTH_PARAM_CONFIG_DIR); + apr_hash_t *creds_hash = NULL; + svn_string_t *str; + svn_error_t *err; + + err = svn_config_read_auth_data(&creds_hash, SVN_AUTH_CRED_SIMPLE, + realmstring, config_dir, pool); + svn_error_clear(err); + if (! err && creds_hash) + { + str = svn_hash_gets(creds_hash, AUTHN_USERNAME_KEY); + if (str && str->data) + default_username = str->data; + } + } + + /* Still no default username? Try the 'servers' file. */ + if (! default_username) + { + svn_config_t *cfg = svn_hash_gets(parameters, + SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS); + const char *server_group = svn_hash_gets(parameters, + SVN_AUTH_PARAM_SERVER_GROUP); + default_username = + svn_config_get_server_setting(cfg, server_group, + SVN_CONFIG_OPTION_USERNAME, + NULL); + } + + /* Still no default username? Try the UID. */ + if (! default_username) + default_username = svn_user_get_name(pool); + + default_password = svn_hash_gets(parameters, + SVN_AUTH_PARAM_DEFAULT_PASSWORD); + } + + /* If we have defaults, just build the cred here and return it. + * + * ### I do wonder why this is here instead of in a separate + * ### 'defaults' provider that would run before the prompt + * ### provider... Hmmm. + */ + if (default_username && default_password) + { + *cred_p = apr_palloc(pool, sizeof(**cred_p)); + (*cred_p)->username = apr_pstrdup(pool, default_username); + (*cred_p)->password = apr_pstrdup(pool, default_password); + (*cred_p)->may_save = TRUE; + } + else + { + SVN_ERR(pb->prompt_func(cred_p, pb->prompt_baton, realmstring, + default_username, may_save, pool)); + } + + return SVN_NO_ERROR; +} + + +/* Our first attempt will use any default username/password passed + in, and prompt for the remaining stuff. */ +static svn_error_t * +simple_prompt_first_creds(void **credentials_p, + void **iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + simple_prompt_provider_baton_t *pb = provider_baton; + simple_prompt_iter_baton_t *ibaton = apr_pcalloc(pool, sizeof(*ibaton)); + const char *no_auth_cache = svn_hash_gets(parameters, + SVN_AUTH_PARAM_NO_AUTH_CACHE); + + SVN_ERR(prompt_for_simple_creds((svn_auth_cred_simple_t **) credentials_p, + pb, parameters, realmstring, TRUE, + ! no_auth_cache, pool)); + + ibaton->retries = 0; + *iter_baton = ibaton; + + return SVN_NO_ERROR; +} + + +/* Subsequent attempts to fetch will ignore the default values, and + simply re-prompt for both, up to a maximum of ib->pb->retry_limit. */ +static svn_error_t * +simple_prompt_next_creds(void **credentials_p, + void *iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + simple_prompt_iter_baton_t *ib = iter_baton; + simple_prompt_provider_baton_t *pb = provider_baton; + const char *no_auth_cache = svn_hash_gets(parameters, + SVN_AUTH_PARAM_NO_AUTH_CACHE); + + if ((pb->retry_limit >= 0) && (ib->retries >= pb->retry_limit)) + { + /* give up, go on to next provider. */ + *credentials_p = NULL; + return SVN_NO_ERROR; + } + ib->retries++; + + return prompt_for_simple_creds((svn_auth_cred_simple_t **) credentials_p, + pb, parameters, realmstring, FALSE, + ! no_auth_cache, pool); +} + +static const svn_auth_provider_t simple_prompt_provider = { + SVN_AUTH_CRED_SIMPLE, + simple_prompt_first_creds, + simple_prompt_next_creds, + NULL, +}; + + +/* Public API */ +void +svn_auth_get_simple_prompt_provider + (svn_auth_provider_object_t **provider, + svn_auth_simple_prompt_func_t prompt_func, + void *prompt_baton, + int retry_limit, + apr_pool_t *pool) +{ + svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po)); + simple_prompt_provider_baton_t *pb = apr_pcalloc(pool, sizeof(*pb)); + + pb->prompt_func = prompt_func; + pb->prompt_baton = prompt_baton; + pb->retry_limit = retry_limit; + + po->vtable = &simple_prompt_provider; + po->provider_baton = pb; + *provider = po; +} |