diff options
Diffstat (limited to 'subversion/libsvn_ra_svn/cyrus_auth.c')
-rw-r--r-- | subversion/libsvn_ra_svn/cyrus_auth.c | 954 |
1 files changed, 954 insertions, 0 deletions
diff --git a/subversion/libsvn_ra_svn/cyrus_auth.c b/subversion/libsvn_ra_svn/cyrus_auth.c new file mode 100644 index 0000000..82e33d3 --- /dev/null +++ b/subversion/libsvn_ra_svn/cyrus_auth.c @@ -0,0 +1,954 @@ +/* + * cyrus_auth.c : functions for Cyrus SASL-based authentication + * + * ==================================================================== + * 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 "svn_private_config.h" +#ifdef SVN_HAVE_SASL + +#define APR_WANT_STRFUNC +#include <apr_want.h> +#include <apr_general.h> +#include <apr_strings.h> +#include <apr_version.h> + +#include "svn_types.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_ra.h" +#include "svn_ra_svn.h" +#include "svn_base64.h" + +#include "private/svn_atomic.h" +#include "private/ra_svn_sasl.h" +#include "private/svn_mutex.h" + +#include "ra_svn.h" + +/* Note: In addition to being used via svn_atomic__init_once to control + * initialization of the SASL code this will also be referenced in + * the various functions that work with sasl mutexes to determine + * if the sasl pool has been destroyed. This should be safe, since + * it is only set back to zero in the sasl pool's cleanups, which + * only happens during apr_terminate, which we assume is occurring + * in atexit processing, at which point we are already running in + * single threaded mode. + */ +volatile svn_atomic_t svn_ra_svn__sasl_status = 0; + +/* Initialized by svn_ra_svn__sasl_common_init(). */ +static volatile svn_atomic_t sasl_ctx_count; + +static apr_pool_t *sasl_pool = NULL; + + +/* Pool cleanup called when sasl_pool is destroyed. */ +static apr_status_t sasl_done_cb(void *data) +{ + /* Reset svn_ra_svn__sasl_status, in case the client calls + apr_initialize()/apr_terminate() more than once. */ + svn_ra_svn__sasl_status = 0; + if (svn_atomic_dec(&sasl_ctx_count) == 0) + sasl_done(); + return APR_SUCCESS; +} + +#if APR_HAS_THREADS +/* Cyrus SASL is thread-safe only if we supply it with mutex functions + * (with sasl_set_mutex()). To make this work with APR, we need to use the + * global sasl_pool for the mutex allocations. Freeing a mutex actually + * returns it to a global array. We allocate mutexes from this + * array if it is non-empty, or directly from the pool otherwise. + * We also need a mutex to serialize accesses to the array itself. + */ + +/* An array of allocated, but unused, apr_thread_mutex_t's. */ +static apr_array_header_t *free_mutexes = NULL; + +/* A mutex to serialize access to the array. */ +static svn_mutex__t *array_mutex = NULL; + +/* Callbacks we pass to sasl_set_mutex(). */ + +static svn_error_t * +sasl_mutex_alloc_cb_internal(svn_mutex__t **mutex) +{ + if (apr_is_empty_array(free_mutexes)) + return svn_mutex__init(mutex, TRUE, sasl_pool); + else + *mutex = *((svn_mutex__t**)apr_array_pop(free_mutexes)); + + return SVN_NO_ERROR; +} + +static void *sasl_mutex_alloc_cb(void) +{ + svn_mutex__t *mutex = NULL; + svn_error_t *err; + + if (!svn_ra_svn__sasl_status) + return NULL; + + err = svn_mutex__lock(array_mutex); + if (err) + svn_error_clear(err); + else + svn_error_clear(svn_mutex__unlock(array_mutex, + sasl_mutex_alloc_cb_internal(&mutex))); + + return mutex; +} + +static int check_result(svn_error_t *err) +{ + if (err) + { + svn_error_clear(err); + return -1; + } + + return 0; +} + +static int sasl_mutex_lock_cb(void *mutex) +{ + if (!svn_ra_svn__sasl_status) + return 0; + return check_result(svn_mutex__lock(mutex)); +} + +static int sasl_mutex_unlock_cb(void *mutex) +{ + if (!svn_ra_svn__sasl_status) + return 0; + return check_result(svn_mutex__unlock(mutex, SVN_NO_ERROR)); +} + +static svn_error_t * +sasl_mutex_free_cb_internal(void *mutex) +{ + APR_ARRAY_PUSH(free_mutexes, svn_mutex__t*) = mutex; + return SVN_NO_ERROR; +} + +static void sasl_mutex_free_cb(void *mutex) +{ + svn_error_t *err; + + if (!svn_ra_svn__sasl_status) + return; + + err = svn_mutex__lock(array_mutex); + if (err) + svn_error_clear(err); + else + svn_error_clear(svn_mutex__unlock(array_mutex, + sasl_mutex_free_cb_internal(mutex))); +} +#endif /* APR_HAS_THREADS */ + +svn_error_t * +svn_ra_svn__sasl_common_init(apr_pool_t *pool) +{ + sasl_pool = svn_pool_create(pool); + sasl_ctx_count = 1; + apr_pool_cleanup_register(sasl_pool, NULL, sasl_done_cb, + apr_pool_cleanup_null); +#if APR_HAS_THREADS + sasl_set_mutex(sasl_mutex_alloc_cb, + sasl_mutex_lock_cb, + sasl_mutex_unlock_cb, + sasl_mutex_free_cb); + free_mutexes = apr_array_make(sasl_pool, 0, sizeof(svn_mutex__t *)); + SVN_ERR(svn_mutex__init(&array_mutex, TRUE, sasl_pool)); + +#endif /* APR_HAS_THREADS */ + + return SVN_NO_ERROR; +} + +/* We are going to look at errno when we get SASL_FAIL but we don't + know for sure whether SASL always sets errno. Clearing errno + before calling SASL functions helps in cases where SASL does + nothing to set errno. */ +#ifdef apr_set_os_error +#define clear_sasl_errno() apr_set_os_error(APR_SUCCESS) +#else +#define clear_sasl_errno() (void)0 +#endif + +/* Sometimes SASL returns SASL_FAIL as RESULT and sets errno. + * SASL_FAIL translates to "generic error" which is quite unhelpful. + * Try to append a more informative error message based on errno so + * should be called before doing anything that may change errno. */ +static const char * +get_sasl_errno_msg(int result, apr_pool_t *result_pool) +{ +#ifdef apr_get_os_error + char buf[1024]; + + if (result == SASL_FAIL && apr_get_os_error() != 0) + return apr_psprintf(result_pool, ": %s", + svn_strerror(apr_get_os_error(), buf, sizeof(buf))); +#endif + return ""; +} + +/* Wrap an error message from SASL with a prefix that allows users + * to tell that the error message came from SASL. Queries errno and + * so should be called before doing anything that may change errno. */ +static const char * +get_sasl_error(sasl_conn_t *sasl_ctx, int result, apr_pool_t *result_pool) +{ + const char *sasl_errno_msg = get_sasl_errno_msg(result, result_pool); + + return apr_psprintf(result_pool, + _("SASL authentication error: %s%s"), + sasl_errdetail(sasl_ctx), sasl_errno_msg); +} + +static svn_error_t *sasl_init_cb(void *baton, apr_pool_t *pool) +{ + int result; + + SVN_ERR(svn_ra_svn__sasl_common_init(pool)); + clear_sasl_errno(); + result = sasl_client_init(NULL); + if (result != SASL_OK) + { + const char *sasl_errno_msg = get_sasl_errno_msg(result, pool); + + return svn_error_createf + (SVN_ERR_RA_NOT_AUTHORIZED, NULL, + _("Could not initialized the SASL library: %s%s"), + sasl_errstring(result, NULL, NULL), + sasl_errno_msg); + } + + return SVN_NO_ERROR; +} + +svn_error_t *svn_ra_svn__sasl_init(void) +{ + SVN_ERR(svn_atomic__init_once(&svn_ra_svn__sasl_status, + sasl_init_cb, NULL, NULL)); + return SVN_NO_ERROR; +} + +static apr_status_t sasl_dispose_cb(void *data) +{ + sasl_conn_t *sasl_ctx = data; + sasl_dispose(&sasl_ctx); + if (svn_atomic_dec(&sasl_ctx_count) == 0) + sasl_done(); + return APR_SUCCESS; +} + +void svn_ra_svn__default_secprops(sasl_security_properties_t *secprops) +{ + /* The minimum and maximum security strength factors that the chosen + SASL mechanism should provide. 0 means 'no encryption', 256 means + '256-bit encryption', which is about the best that any SASL + mechanism can provide. Using these values effectively means 'use + whatever encryption the other side wants'. Note that SASL will try + to use better encryption whenever possible, so if both the server and + the client use these values the highest possible encryption strength + will be used. */ + secprops->min_ssf = 0; + secprops->max_ssf = 256; + + /* Set maxbufsize to the maximum amount of data we can read at any one time. + This value needs to be commmunicated to the peer if a security layer + is negotiated. */ + secprops->maxbufsize = SVN_RA_SVN__READBUF_SIZE; + + secprops->security_flags = 0; + secprops->property_names = secprops->property_values = NULL; +} + +/* A baton type used by the SASL username and password callbacks. */ +typedef struct cred_baton { + svn_auth_baton_t *auth_baton; + svn_auth_iterstate_t *iterstate; + const char *realmstring; + + /* Unfortunately SASL uses two separate callbacks for the username and + password, but we must fetch both of them at the same time. So we cache + their values in the baton, set them to NULL individually when SASL + demands them, and fetch the next pair when both are NULL. */ + const char *username; + const char *password; + + /* Any errors we receive from svn_auth_{first,next}_credentials + are saved here. */ + svn_error_t *err; + + /* This flag is set when we run out of credential providers. */ + svn_boolean_t no_more_creds; + + /* Were the auth callbacks ever called? */ + svn_boolean_t was_used; + + apr_pool_t *pool; +} cred_baton_t; + +/* Call svn_auth_{first,next}_credentials. If successful, set BATON->username + and BATON->password to the new username and password and return TRUE, + otherwise return FALSE. If there are no more credentials, set + BATON->no_more_creds to TRUE. Any errors are saved in BATON->err. */ +static svn_boolean_t +get_credentials(cred_baton_t *baton) +{ + void *creds; + + if (baton->iterstate) + baton->err = svn_auth_next_credentials(&creds, baton->iterstate, + baton->pool); + else + baton->err = svn_auth_first_credentials(&creds, &baton->iterstate, + SVN_AUTH_CRED_SIMPLE, + baton->realmstring, + baton->auth_baton, baton->pool); + if (baton->err) + return FALSE; + + if (! creds) + { + baton->no_more_creds = TRUE; + return FALSE; + } + + baton->username = ((svn_auth_cred_simple_t *)creds)->username; + baton->password = ((svn_auth_cred_simple_t *)creds)->password; + baton->was_used = TRUE; + + return TRUE; +} + +/* The username callback. Implements the sasl_getsimple_t interface. */ +static int +get_username_cb(void *b, int id, const char **username, size_t *len) +{ + cred_baton_t *baton = b; + + if (baton->username || get_credentials(baton)) + { + *username = baton->username; + if (len) + *len = strlen(baton->username); + baton->username = NULL; + + return SASL_OK; + } + + return SASL_FAIL; +} + +/* The password callback. Implements the sasl_getsecret_t interface. */ +static int +get_password_cb(sasl_conn_t *conn, void *b, int id, sasl_secret_t **psecret) +{ + cred_baton_t *baton = b; + + if (baton->password || get_credentials(baton)) + { + sasl_secret_t *secret; + size_t len = strlen(baton->password); + + /* sasl_secret_t is a struct with a variable-sized array as a final + member, which means we need to allocate len-1 supplementary bytes + (one byte is part of sasl_secret_t, and we don't need a NULL + terminator). */ + secret = apr_palloc(baton->pool, sizeof(*secret) + len - 1); + secret->len = len; + memcpy(secret->data, baton->password, len); + baton->password = NULL; + *psecret = secret; + + return SASL_OK; + } + + return SASL_FAIL; +} + +/* Create a new SASL context. */ +static svn_error_t *new_sasl_ctx(sasl_conn_t **sasl_ctx, + svn_boolean_t is_tunneled, + const char *hostname, + const char *local_addrport, + const char *remote_addrport, + sasl_callback_t *callbacks, + apr_pool_t *pool) +{ + sasl_security_properties_t secprops; + int result; + + clear_sasl_errno(); + result = sasl_client_new(SVN_RA_SVN_SASL_NAME, + hostname, local_addrport, remote_addrport, + callbacks, SASL_SUCCESS_DATA, + sasl_ctx); + if (result != SASL_OK) + { + const char *sasl_errno_msg = get_sasl_errno_msg(result, pool); + + return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + _("Could not create SASL context: %s%s"), + sasl_errstring(result, NULL, NULL), + sasl_errno_msg); + } + svn_atomic_inc(&sasl_ctx_count); + apr_pool_cleanup_register(pool, *sasl_ctx, sasl_dispose_cb, + apr_pool_cleanup_null); + + if (is_tunneled) + { + /* We need to tell SASL that this connection is tunneled, + otherwise it will ignore EXTERNAL. The third parameter + should be the username, but since SASL doesn't seem + to use it on the client side, any non-empty string will do. */ + clear_sasl_errno(); + result = sasl_setprop(*sasl_ctx, + SASL_AUTH_EXTERNAL, " "); + if (result != SASL_OK) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + get_sasl_error(*sasl_ctx, result, pool)); + } + + /* Set security properties. */ + svn_ra_svn__default_secprops(&secprops); + sasl_setprop(*sasl_ctx, SASL_SEC_PROPS, &secprops); + + return SVN_NO_ERROR; +} + +/* Perform an authentication exchange */ +static svn_error_t *try_auth(svn_ra_svn__session_baton_t *sess, + sasl_conn_t *sasl_ctx, + svn_boolean_t *success, + const char **last_err, + const char *mechstring, + apr_pool_t *pool) +{ + sasl_interact_t *client_interact = NULL; + const char *out, *mech, *status = NULL; + const svn_string_t *arg = NULL, *in; + int result; + unsigned int outlen; + svn_boolean_t again; + + do + { + again = FALSE; + clear_sasl_errno(); + result = sasl_client_start(sasl_ctx, + mechstring, + &client_interact, + &out, + &outlen, + &mech); + switch (result) + { + case SASL_OK: + case SASL_CONTINUE: + /* Success. */ + break; + case SASL_NOMECH: + return svn_error_create(SVN_ERR_RA_SVN_NO_MECHANISMS, NULL, NULL); + case SASL_BADPARAM: + case SASL_NOMEM: + /* Fatal error. Fail the authentication. */ + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + get_sasl_error(sasl_ctx, result, pool)); + default: + /* For anything else, delete the mech from the list + and try again. */ + { + const char *pmech = strstr(mechstring, mech); + const char *head = apr_pstrndup(pool, mechstring, + pmech - mechstring); + const char *tail = pmech + strlen(mech); + + mechstring = apr_pstrcat(pool, head, tail, (char *)NULL); + again = TRUE; + } + } + } + while (again); + + /* Prepare the initial authentication token. */ + if (outlen > 0 || strcmp(mech, "EXTERNAL") == 0) + arg = svn_base64_encode_string2(svn_string_ncreate(out, outlen, pool), + TRUE, pool); + + /* Send the initial client response */ + SVN_ERR(svn_ra_svn__auth_response(sess->conn, pool, mech, + arg ? arg->data : NULL)); + + while (result == SASL_CONTINUE) + { + /* Read the server response */ + SVN_ERR(svn_ra_svn__read_tuple(sess->conn, pool, "w(?s)", + &status, &in)); + + if (strcmp(status, "failure") == 0) + { + /* Authentication failed. Use the next set of credentials */ + *success = FALSE; + /* Remember the message sent by the server because we'll want to + return a meaningful error if we run out of auth providers. */ + *last_err = in ? in->data : ""; + return SVN_NO_ERROR; + } + + if ((strcmp(status, "success") != 0 && strcmp(status, "step") != 0) + || in == NULL) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + _("Unexpected server response" + " to authentication")); + + /* If the mech is CRAM-MD5 we don't base64-decode the server response. */ + if (strcmp(mech, "CRAM-MD5") != 0) + in = svn_base64_decode_string(in, pool); + + clear_sasl_errno(); + result = sasl_client_step(sasl_ctx, + in->data, + (const unsigned int) in->len, + &client_interact, + &out, /* Filled in by SASL. */ + &outlen); + + if (result != SASL_OK && result != SASL_CONTINUE) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + get_sasl_error(sasl_ctx, result, pool)); + + /* If the server thinks we're done, then don't send any response. */ + if (strcmp(status, "success") == 0) + break; + + if (outlen > 0) + { + arg = svn_string_ncreate(out, outlen, pool); + /* Write our response. */ + /* For CRAM-MD5, we don't use base64-encoding. */ + if (strcmp(mech, "CRAM-MD5") != 0) + arg = svn_base64_encode_string2(arg, TRUE, pool); + SVN_ERR(svn_ra_svn__write_cstring(sess->conn, pool, arg->data)); + } + else + { + SVN_ERR(svn_ra_svn__write_cstring(sess->conn, pool, "")); + } + } + + if (!status || strcmp(status, "step") == 0) + { + /* This is a client-send-last mech. Read the last server response. */ + SVN_ERR(svn_ra_svn__read_tuple(sess->conn, pool, "w(?s)", + &status, &in)); + + if (strcmp(status, "failure") == 0) + { + *success = FALSE; + *last_err = in ? in->data : ""; + } + else if (strcmp(status, "success") == 0) + { + /* We're done */ + *success = TRUE; + } + else + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + _("Unexpected server response" + " to authentication")); + } + else + *success = TRUE; + return SVN_NO_ERROR; +} + +/* Baton for a SASL encrypted svn_ra_svn__stream_t. */ +typedef struct sasl_baton { + svn_ra_svn__stream_t *stream; /* Inherited stream. */ + sasl_conn_t *ctx; /* The SASL context for this connection. */ + unsigned int maxsize; /* The maximum amount of data we can encode. */ + const char *read_buf; /* The buffer returned by sasl_decode. */ + unsigned int read_len; /* Its current length. */ + const char *write_buf; /* The buffer returned by sasl_encode. */ + unsigned int write_len; /* Its length. */ + apr_pool_t *scratch_pool; +} sasl_baton_t; + +/* Functions to implement a SASL encrypted svn_ra_svn__stream_t. */ + +/* Implements svn_read_fn_t. */ +static svn_error_t *sasl_read_cb(void *baton, char *buffer, apr_size_t *len) +{ + sasl_baton_t *sasl_baton = baton; + int result; + /* A copy of *len, used by the wrapped stream. */ + apr_size_t len2 = *len; + + /* sasl_decode might need more data than a single read can provide, + hence the need to put a loop around the decoding. */ + while (! sasl_baton->read_buf || sasl_baton->read_len == 0) + { + SVN_ERR(svn_ra_svn__stream_read(sasl_baton->stream, buffer, &len2)); + if (len2 == 0) + { + *len = 0; + return SVN_NO_ERROR; + } + clear_sasl_errno(); + result = sasl_decode(sasl_baton->ctx, buffer, (unsigned int) len2, + &sasl_baton->read_buf, + &sasl_baton->read_len); + if (result != SASL_OK) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + get_sasl_error(sasl_baton->ctx, result, + sasl_baton->scratch_pool)); + } + + /* The buffer returned by sasl_decode might be larger than what the + caller wants. If this is the case, we only copy back *len bytes now + (the rest will be returned by subsequent calls to this function). + If not, we just copy back the whole thing. */ + if (*len >= sasl_baton->read_len) + { + memcpy(buffer, sasl_baton->read_buf, sasl_baton->read_len); + *len = sasl_baton->read_len; + sasl_baton->read_buf = NULL; + sasl_baton->read_len = 0; + } + else + { + memcpy(buffer, sasl_baton->read_buf, *len); + sasl_baton->read_len -= *len; + sasl_baton->read_buf += *len; + } + + return SVN_NO_ERROR; +} + +/* Implements svn_write_fn_t. */ +static svn_error_t * +sasl_write_cb(void *baton, const char *buffer, apr_size_t *len) +{ + sasl_baton_t *sasl_baton = baton; + int result; + + if (! sasl_baton->write_buf || sasl_baton->write_len == 0) + { + /* Make sure we don't write too much. */ + *len = (*len > sasl_baton->maxsize) ? sasl_baton->maxsize : *len; + clear_sasl_errno(); + result = sasl_encode(sasl_baton->ctx, buffer, (unsigned int) *len, + &sasl_baton->write_buf, + &sasl_baton->write_len); + + if (result != SASL_OK) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + get_sasl_error(sasl_baton->ctx, result, + sasl_baton->scratch_pool)); + } + + do + { + apr_size_t tmplen = sasl_baton->write_len; + SVN_ERR(svn_ra_svn__stream_write(sasl_baton->stream, + sasl_baton->write_buf, + &tmplen)); + if (tmplen == 0) + { + /* The output buffer and its length will be preserved in sasl_baton + and will be written out during the next call to this function + (which will have the same arguments). */ + *len = 0; + return SVN_NO_ERROR; + } + sasl_baton->write_len -= (unsigned int) tmplen; + sasl_baton->write_buf += tmplen; + } + while (sasl_baton->write_len > 0); + + sasl_baton->write_buf = NULL; + sasl_baton->write_len = 0; + + return SVN_NO_ERROR; +} + +/* Implements ra_svn_timeout_fn_t. */ +static void sasl_timeout_cb(void *baton, apr_interval_time_t interval) +{ + sasl_baton_t *sasl_baton = baton; + svn_ra_svn__stream_timeout(sasl_baton->stream, interval); +} + +/* Implements ra_svn_pending_fn_t. */ +static svn_boolean_t sasl_pending_cb(void *baton) +{ + sasl_baton_t *sasl_baton = baton; + return svn_ra_svn__stream_pending(sasl_baton->stream); +} + +svn_error_t *svn_ra_svn__enable_sasl_encryption(svn_ra_svn_conn_t *conn, + sasl_conn_t *sasl_ctx, + apr_pool_t *pool) +{ + const sasl_ssf_t *ssfp; + + if (! conn->encrypted) + { + int result; + + /* Get the strength of the security layer. */ + clear_sasl_errno(); + result = sasl_getprop(sasl_ctx, SASL_SSF, (void*) &ssfp); + if (result != SASL_OK) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + get_sasl_error(sasl_ctx, result, pool)); + + if (*ssfp > 0) + { + sasl_baton_t *sasl_baton; + const void *maxsize; + + /* Flush the connection, as we're about to replace its stream. */ + SVN_ERR(svn_ra_svn__flush(conn, pool)); + + /* Create and initialize the stream baton. */ + sasl_baton = apr_pcalloc(conn->pool, sizeof(*sasl_baton)); + sasl_baton->ctx = sasl_ctx; + sasl_baton->scratch_pool = conn->pool; + + /* Find out the maximum input size for sasl_encode. */ + clear_sasl_errno(); + result = sasl_getprop(sasl_ctx, SASL_MAXOUTBUF, &maxsize); + if (result != SASL_OK) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + get_sasl_error(sasl_ctx, result, pool)); + sasl_baton->maxsize = *((const unsigned int *) maxsize); + + /* If there is any data left in the read buffer at this point, + we need to decrypt it. */ + if (conn->read_end > conn->read_ptr) + { + clear_sasl_errno(); + result = sasl_decode(sasl_ctx, conn->read_ptr, + (unsigned int) (conn->read_end - conn->read_ptr), + &sasl_baton->read_buf, &sasl_baton->read_len); + if (result != SASL_OK) + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + get_sasl_error(sasl_ctx, result, pool)); + conn->read_end = conn->read_ptr; + } + + /* Wrap the existing stream. */ + sasl_baton->stream = conn->stream; + + conn->stream = svn_ra_svn__stream_create(sasl_baton, sasl_read_cb, + sasl_write_cb, + sasl_timeout_cb, + sasl_pending_cb, conn->pool); + /* Yay, we have a security layer! */ + conn->encrypted = TRUE; + } + } + return SVN_NO_ERROR; +} + +svn_error_t *svn_ra_svn__get_addresses(const char **local_addrport, + const char **remote_addrport, + svn_ra_svn_conn_t *conn, + apr_pool_t *pool) +{ + if (conn->sock) + { + apr_status_t apr_err; + apr_sockaddr_t *local_sa, *remote_sa; + char *local_addr, *remote_addr; + + apr_err = apr_socket_addr_get(&local_sa, APR_LOCAL, conn->sock); + if (apr_err) + return svn_error_wrap_apr(apr_err, NULL); + + apr_err = apr_socket_addr_get(&remote_sa, APR_REMOTE, conn->sock); + if (apr_err) + return svn_error_wrap_apr(apr_err, NULL); + + apr_err = apr_sockaddr_ip_get(&local_addr, local_sa); + if (apr_err) + return svn_error_wrap_apr(apr_err, NULL); + + apr_err = apr_sockaddr_ip_get(&remote_addr, remote_sa); + if (apr_err) + return svn_error_wrap_apr(apr_err, NULL); + + /* Format the IP address and port number like this: a.b.c.d;port */ + *local_addrport = apr_pstrcat(pool, local_addr, ";", + apr_itoa(pool, (int)local_sa->port), + (char *)NULL); + *remote_addrport = apr_pstrcat(pool, remote_addr, ";", + apr_itoa(pool, (int)remote_sa->port), + (char *)NULL); + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra_svn__do_cyrus_auth(svn_ra_svn__session_baton_t *sess, + const apr_array_header_t *mechlist, + const char *realm, apr_pool_t *pool) +{ + apr_pool_t *subpool; + sasl_conn_t *sasl_ctx; + const char *mechstring = "", *last_err = "", *realmstring; + const char *local_addrport = NULL, *remote_addrport = NULL; + svn_boolean_t success; + sasl_callback_t *callbacks; + cred_baton_t cred_baton = { 0 }; + int i; + + if (!sess->is_tunneled) + { + SVN_ERR(svn_ra_svn__get_addresses(&local_addrport, &remote_addrport, + sess->conn, pool)); + } + + /* Prefer EXTERNAL, then ANONYMOUS, then let SASL decide. */ + if (svn_ra_svn__find_mech(mechlist, "EXTERNAL")) + mechstring = "EXTERNAL"; + else if (svn_ra_svn__find_mech(mechlist, "ANONYMOUS")) + mechstring = "ANONYMOUS"; + else + { + /* Create a string containing the list of mechanisms, separated by spaces. */ + for (i = 0; i < mechlist->nelts; i++) + { + svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(mechlist, i, svn_ra_svn_item_t); + mechstring = apr_pstrcat(pool, + mechstring, + i == 0 ? "" : " ", + elt->u.word, (char *)NULL); + } + } + + realmstring = apr_psprintf(pool, "%s %s", sess->realm_prefix, realm); + + /* Initialize the credential baton. */ + cred_baton.auth_baton = sess->callbacks->auth_baton; + cred_baton.realmstring = realmstring; + cred_baton.pool = pool; + + /* Reserve space for 3 callbacks (for the username, password and the + array terminator). These structures must persist until the + disposal of the SASL context at pool cleanup, however the + callback functions will not be invoked outside this function so + other structures can have a shorter lifetime. */ + callbacks = apr_palloc(sess->conn->pool, sizeof(*callbacks) * 3); + + /* Initialize the callbacks array. */ + + /* The username callback. */ + callbacks[0].id = SASL_CB_AUTHNAME; + callbacks[0].proc = (int (*)(void))get_username_cb; + callbacks[0].context = &cred_baton; + + /* The password callback. */ + callbacks[1].id = SASL_CB_PASS; + callbacks[1].proc = (int (*)(void))get_password_cb; + callbacks[1].context = &cred_baton; + + /* Mark the end of the array. */ + callbacks[2].id = SASL_CB_LIST_END; + callbacks[2].proc = NULL; + callbacks[2].context = NULL; + + subpool = svn_pool_create(pool); + do + { + svn_error_t *err; + + /* If last_err was set to a non-empty string, it needs to be duplicated + to the parent pool before the subpool is cleared. */ + if (*last_err) + last_err = apr_pstrdup(pool, last_err); + svn_pool_clear(subpool); + + SVN_ERR(new_sasl_ctx(&sasl_ctx, sess->is_tunneled, + sess->hostname, local_addrport, remote_addrport, + callbacks, sess->conn->pool)); + err = try_auth(sess, sasl_ctx, &success, &last_err, mechstring, + subpool); + + /* If we encountered an error while fetching credentials, that error + has priority. */ + if (cred_baton.err) + { + svn_error_clear(err); + return cred_baton.err; + } + if (cred_baton.no_more_creds + || (! err && ! success && ! cred_baton.was_used)) + { + svn_error_clear(err); + /* If we ran out of authentication providers, or if we got a server + error and our callbacks were never called, there's no point in + retrying authentication. Return the last error sent by the + server. */ + if (*last_err) + return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + _("Authentication error from server: %s"), + last_err); + /* Hmm, we don't have a server error. Return a generic error. */ + return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, + _("Can't get username or password")); + } + if (err) + { + if (err->apr_err == SVN_ERR_RA_SVN_NO_MECHANISMS) + { + svn_error_clear(err); + + /* We could not find a supported mechanism in the list sent by the + server. In many cases this happens because the client is missing + the CRAM-MD5 or ANONYMOUS plugins, in which case we can simply use + the built-in implementation. In all other cases this call will be + useless, but hey, at least we'll get consistent error messages. */ + return svn_ra_svn__do_internal_auth(sess, mechlist, + realm, pool); + } + return err; + } + } + while (!success); + svn_pool_destroy(subpool); + + SVN_ERR(svn_ra_svn__enable_sasl_encryption(sess->conn, sasl_ctx, pool)); + + SVN_ERR(svn_auth_save_credentials(cred_baton.iterstate, pool)); + + return SVN_NO_ERROR; +} + +#endif /* SVN_HAVE_SASL */ |