summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_ra_svn/cyrus_auth.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_ra_svn/cyrus_auth.c')
-rw-r--r--subversion/libsvn_ra_svn/cyrus_auth.c954
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 */
OpenPOWER on IntegriCloud