1251881Speter/* 2251881Speter * cyrus_auth.c : functions for Cyrus SASL-based authentication 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter#include "svn_private_config.h" 25251881Speter#ifdef SVN_HAVE_SASL 26251881Speter 27251881Speter#define APR_WANT_STRFUNC 28251881Speter#include <apr_want.h> 29251881Speter#include <apr_general.h> 30251881Speter#include <apr_strings.h> 31251881Speter#include <apr_version.h> 32251881Speter 33251881Speter#include "svn_types.h" 34251881Speter#include "svn_string.h" 35251881Speter#include "svn_error.h" 36251881Speter#include "svn_pools.h" 37251881Speter#include "svn_ra.h" 38251881Speter#include "svn_ra_svn.h" 39251881Speter#include "svn_base64.h" 40251881Speter 41251881Speter#include "private/svn_atomic.h" 42251881Speter#include "private/ra_svn_sasl.h" 43251881Speter#include "private/svn_mutex.h" 44251881Speter 45251881Speter#include "ra_svn.h" 46251881Speter 47251881Speter/* Note: In addition to being used via svn_atomic__init_once to control 48251881Speter * initialization of the SASL code this will also be referenced in 49251881Speter * the various functions that work with sasl mutexes to determine 50251881Speter * if the sasl pool has been destroyed. This should be safe, since 51251881Speter * it is only set back to zero in the sasl pool's cleanups, which 52251881Speter * only happens during apr_terminate, which we assume is occurring 53251881Speter * in atexit processing, at which point we are already running in 54251881Speter * single threaded mode. 55251881Speter */ 56251881Spetervolatile svn_atomic_t svn_ra_svn__sasl_status = 0; 57251881Speter 58251881Speter/* Initialized by svn_ra_svn__sasl_common_init(). */ 59251881Speterstatic volatile svn_atomic_t sasl_ctx_count; 60251881Speter 61251881Speterstatic apr_pool_t *sasl_pool = NULL; 62251881Speter 63251881Speter 64251881Speter/* Pool cleanup called when sasl_pool is destroyed. */ 65251881Speterstatic apr_status_t sasl_done_cb(void *data) 66251881Speter{ 67251881Speter /* Reset svn_ra_svn__sasl_status, in case the client calls 68251881Speter apr_initialize()/apr_terminate() more than once. */ 69251881Speter svn_ra_svn__sasl_status = 0; 70251881Speter if (svn_atomic_dec(&sasl_ctx_count) == 0) 71362181Sdim svn_sasl__done(); 72251881Speter return APR_SUCCESS; 73251881Speter} 74251881Speter 75251881Speter#if APR_HAS_THREADS 76251881Speter/* Cyrus SASL is thread-safe only if we supply it with mutex functions 77251881Speter * (with sasl_set_mutex()). To make this work with APR, we need to use the 78251881Speter * global sasl_pool for the mutex allocations. Freeing a mutex actually 79251881Speter * returns it to a global array. We allocate mutexes from this 80251881Speter * array if it is non-empty, or directly from the pool otherwise. 81251881Speter * We also need a mutex to serialize accesses to the array itself. 82251881Speter */ 83251881Speter 84251881Speter/* An array of allocated, but unused, apr_thread_mutex_t's. */ 85251881Speterstatic apr_array_header_t *free_mutexes = NULL; 86251881Speter 87251881Speter/* A mutex to serialize access to the array. */ 88251881Speterstatic svn_mutex__t *array_mutex = NULL; 89251881Speter 90251881Speter/* Callbacks we pass to sasl_set_mutex(). */ 91251881Speter 92251881Speterstatic svn_error_t * 93251881Spetersasl_mutex_alloc_cb_internal(svn_mutex__t **mutex) 94251881Speter{ 95251881Speter if (apr_is_empty_array(free_mutexes)) 96251881Speter return svn_mutex__init(mutex, TRUE, sasl_pool); 97251881Speter else 98251881Speter *mutex = *((svn_mutex__t**)apr_array_pop(free_mutexes)); 99251881Speter 100251881Speter return SVN_NO_ERROR; 101251881Speter} 102251881Speter 103251881Speterstatic void *sasl_mutex_alloc_cb(void) 104251881Speter{ 105251881Speter svn_mutex__t *mutex = NULL; 106251881Speter svn_error_t *err; 107251881Speter 108251881Speter if (!svn_ra_svn__sasl_status) 109251881Speter return NULL; 110251881Speter 111251881Speter err = svn_mutex__lock(array_mutex); 112251881Speter if (err) 113251881Speter svn_error_clear(err); 114251881Speter else 115251881Speter svn_error_clear(svn_mutex__unlock(array_mutex, 116251881Speter sasl_mutex_alloc_cb_internal(&mutex))); 117251881Speter 118251881Speter return mutex; 119251881Speter} 120251881Speter 121251881Speterstatic int check_result(svn_error_t *err) 122251881Speter{ 123251881Speter if (err) 124251881Speter { 125251881Speter svn_error_clear(err); 126251881Speter return -1; 127251881Speter } 128251881Speter 129251881Speter return 0; 130251881Speter} 131251881Speter 132251881Speterstatic int sasl_mutex_lock_cb(void *mutex) 133251881Speter{ 134251881Speter if (!svn_ra_svn__sasl_status) 135251881Speter return 0; 136251881Speter return check_result(svn_mutex__lock(mutex)); 137251881Speter} 138251881Speter 139251881Speterstatic int sasl_mutex_unlock_cb(void *mutex) 140251881Speter{ 141251881Speter if (!svn_ra_svn__sasl_status) 142251881Speter return 0; 143251881Speter return check_result(svn_mutex__unlock(mutex, SVN_NO_ERROR)); 144251881Speter} 145251881Speter 146251881Speterstatic svn_error_t * 147251881Spetersasl_mutex_free_cb_internal(void *mutex) 148251881Speter{ 149251881Speter APR_ARRAY_PUSH(free_mutexes, svn_mutex__t*) = mutex; 150251881Speter return SVN_NO_ERROR; 151251881Speter} 152251881Speter 153251881Speterstatic void sasl_mutex_free_cb(void *mutex) 154251881Speter{ 155251881Speter svn_error_t *err; 156251881Speter 157251881Speter if (!svn_ra_svn__sasl_status) 158251881Speter return; 159251881Speter 160251881Speter err = svn_mutex__lock(array_mutex); 161251881Speter if (err) 162251881Speter svn_error_clear(err); 163251881Speter else 164251881Speter svn_error_clear(svn_mutex__unlock(array_mutex, 165251881Speter sasl_mutex_free_cb_internal(mutex))); 166251881Speter} 167251881Speter#endif /* APR_HAS_THREADS */ 168251881Speter 169251881Spetersvn_error_t * 170251881Spetersvn_ra_svn__sasl_common_init(apr_pool_t *pool) 171251881Speter{ 172251881Speter sasl_pool = svn_pool_create(pool); 173251881Speter sasl_ctx_count = 1; 174251881Speter apr_pool_cleanup_register(sasl_pool, NULL, sasl_done_cb, 175251881Speter apr_pool_cleanup_null); 176251881Speter#if APR_HAS_THREADS 177362181Sdim svn_sasl__set_mutex(sasl_mutex_alloc_cb, 178362181Sdim sasl_mutex_lock_cb, 179362181Sdim sasl_mutex_unlock_cb, 180362181Sdim sasl_mutex_free_cb); 181251881Speter free_mutexes = apr_array_make(sasl_pool, 0, sizeof(svn_mutex__t *)); 182251881Speter SVN_ERR(svn_mutex__init(&array_mutex, TRUE, sasl_pool)); 183251881Speter 184251881Speter#endif /* APR_HAS_THREADS */ 185251881Speter 186251881Speter return SVN_NO_ERROR; 187251881Speter} 188251881Speter 189251881Speter/* We are going to look at errno when we get SASL_FAIL but we don't 190251881Speter know for sure whether SASL always sets errno. Clearing errno 191251881Speter before calling SASL functions helps in cases where SASL does 192251881Speter nothing to set errno. */ 193251881Speter#ifdef apr_set_os_error 194251881Speter#define clear_sasl_errno() apr_set_os_error(APR_SUCCESS) 195251881Speter#else 196251881Speter#define clear_sasl_errno() (void)0 197251881Speter#endif 198251881Speter 199251881Speter/* Sometimes SASL returns SASL_FAIL as RESULT and sets errno. 200251881Speter * SASL_FAIL translates to "generic error" which is quite unhelpful. 201251881Speter * Try to append a more informative error message based on errno so 202251881Speter * should be called before doing anything that may change errno. */ 203251881Speterstatic const char * 204251881Speterget_sasl_errno_msg(int result, apr_pool_t *result_pool) 205251881Speter{ 206251881Speter#ifdef apr_get_os_error 207251881Speter char buf[1024]; 208251881Speter 209251881Speter if (result == SASL_FAIL && apr_get_os_error() != 0) 210251881Speter return apr_psprintf(result_pool, ": %s", 211251881Speter svn_strerror(apr_get_os_error(), buf, sizeof(buf))); 212251881Speter#endif 213251881Speter return ""; 214251881Speter} 215251881Speter 216251881Speter/* Wrap an error message from SASL with a prefix that allows users 217251881Speter * to tell that the error message came from SASL. Queries errno and 218251881Speter * so should be called before doing anything that may change errno. */ 219251881Speterstatic const char * 220251881Speterget_sasl_error(sasl_conn_t *sasl_ctx, int result, apr_pool_t *result_pool) 221251881Speter{ 222251881Speter const char *sasl_errno_msg = get_sasl_errno_msg(result, result_pool); 223251881Speter 224251881Speter return apr_psprintf(result_pool, 225251881Speter _("SASL authentication error: %s%s"), 226362181Sdim svn_sasl__errdetail(sasl_ctx), sasl_errno_msg); 227251881Speter} 228251881Speter 229251881Speterstatic svn_error_t *sasl_init_cb(void *baton, apr_pool_t *pool) 230251881Speter{ 231251881Speter int result; 232251881Speter 233251881Speter SVN_ERR(svn_ra_svn__sasl_common_init(pool)); 234251881Speter clear_sasl_errno(); 235362181Sdim result = svn_sasl__client_init(NULL); 236251881Speter if (result != SASL_OK) 237251881Speter { 238251881Speter const char *sasl_errno_msg = get_sasl_errno_msg(result, pool); 239251881Speter 240251881Speter return svn_error_createf 241251881Speter (SVN_ERR_RA_NOT_AUTHORIZED, NULL, 242251881Speter _("Could not initialized the SASL library: %s%s"), 243362181Sdim svn_sasl__errstring(result, NULL, NULL), 244251881Speter sasl_errno_msg); 245251881Speter } 246251881Speter 247251881Speter return SVN_NO_ERROR; 248251881Speter} 249251881Speter 250251881Spetersvn_error_t *svn_ra_svn__sasl_init(void) 251251881Speter{ 252251881Speter SVN_ERR(svn_atomic__init_once(&svn_ra_svn__sasl_status, 253251881Speter sasl_init_cb, NULL, NULL)); 254251881Speter return SVN_NO_ERROR; 255251881Speter} 256251881Speter 257251881Speterstatic apr_status_t sasl_dispose_cb(void *data) 258251881Speter{ 259251881Speter sasl_conn_t *sasl_ctx = data; 260362181Sdim svn_sasl__dispose(&sasl_ctx); 261251881Speter if (svn_atomic_dec(&sasl_ctx_count) == 0) 262362181Sdim svn_sasl__done(); 263251881Speter return APR_SUCCESS; 264251881Speter} 265251881Speter 266251881Spetervoid svn_ra_svn__default_secprops(sasl_security_properties_t *secprops) 267251881Speter{ 268251881Speter /* The minimum and maximum security strength factors that the chosen 269251881Speter SASL mechanism should provide. 0 means 'no encryption', 256 means 270251881Speter '256-bit encryption', which is about the best that any SASL 271251881Speter mechanism can provide. Using these values effectively means 'use 272251881Speter whatever encryption the other side wants'. Note that SASL will try 273251881Speter to use better encryption whenever possible, so if both the server and 274251881Speter the client use these values the highest possible encryption strength 275251881Speter will be used. */ 276251881Speter secprops->min_ssf = 0; 277251881Speter secprops->max_ssf = 256; 278251881Speter 279251881Speter /* Set maxbufsize to the maximum amount of data we can read at any one time. 280251881Speter This value needs to be commmunicated to the peer if a security layer 281251881Speter is negotiated. */ 282251881Speter secprops->maxbufsize = SVN_RA_SVN__READBUF_SIZE; 283251881Speter 284251881Speter secprops->security_flags = 0; 285251881Speter secprops->property_names = secprops->property_values = NULL; 286251881Speter} 287251881Speter 288251881Speter/* A baton type used by the SASL username and password callbacks. */ 289251881Spetertypedef struct cred_baton { 290251881Speter svn_auth_baton_t *auth_baton; 291251881Speter svn_auth_iterstate_t *iterstate; 292251881Speter const char *realmstring; 293251881Speter 294251881Speter /* Unfortunately SASL uses two separate callbacks for the username and 295251881Speter password, but we must fetch both of them at the same time. So we cache 296251881Speter their values in the baton, set them to NULL individually when SASL 297251881Speter demands them, and fetch the next pair when both are NULL. */ 298251881Speter const char *username; 299251881Speter const char *password; 300251881Speter 301251881Speter /* Any errors we receive from svn_auth_{first,next}_credentials 302251881Speter are saved here. */ 303251881Speter svn_error_t *err; 304251881Speter 305251881Speter /* This flag is set when we run out of credential providers. */ 306251881Speter svn_boolean_t no_more_creds; 307251881Speter 308251881Speter /* Were the auth callbacks ever called? */ 309251881Speter svn_boolean_t was_used; 310251881Speter 311251881Speter apr_pool_t *pool; 312251881Speter} cred_baton_t; 313251881Speter 314251881Speter/* Call svn_auth_{first,next}_credentials. If successful, set BATON->username 315251881Speter and BATON->password to the new username and password and return TRUE, 316251881Speter otherwise return FALSE. If there are no more credentials, set 317251881Speter BATON->no_more_creds to TRUE. Any errors are saved in BATON->err. */ 318251881Speterstatic svn_boolean_t 319251881Speterget_credentials(cred_baton_t *baton) 320251881Speter{ 321251881Speter void *creds; 322251881Speter 323251881Speter if (baton->iterstate) 324251881Speter baton->err = svn_auth_next_credentials(&creds, baton->iterstate, 325251881Speter baton->pool); 326251881Speter else 327251881Speter baton->err = svn_auth_first_credentials(&creds, &baton->iterstate, 328251881Speter SVN_AUTH_CRED_SIMPLE, 329251881Speter baton->realmstring, 330251881Speter baton->auth_baton, baton->pool); 331251881Speter if (baton->err) 332251881Speter return FALSE; 333251881Speter 334251881Speter if (! creds) 335251881Speter { 336251881Speter baton->no_more_creds = TRUE; 337251881Speter return FALSE; 338251881Speter } 339251881Speter 340251881Speter baton->username = ((svn_auth_cred_simple_t *)creds)->username; 341251881Speter baton->password = ((svn_auth_cred_simple_t *)creds)->password; 342251881Speter baton->was_used = TRUE; 343251881Speter 344251881Speter return TRUE; 345251881Speter} 346251881Speter 347251881Speter/* The username callback. Implements the sasl_getsimple_t interface. */ 348251881Speterstatic int 349251881Speterget_username_cb(void *b, int id, const char **username, size_t *len) 350251881Speter{ 351251881Speter cred_baton_t *baton = b; 352251881Speter 353251881Speter if (baton->username || get_credentials(baton)) 354251881Speter { 355251881Speter *username = baton->username; 356251881Speter if (len) 357251881Speter *len = strlen(baton->username); 358251881Speter baton->username = NULL; 359251881Speter 360251881Speter return SASL_OK; 361251881Speter } 362251881Speter 363251881Speter return SASL_FAIL; 364251881Speter} 365251881Speter 366251881Speter/* The password callback. Implements the sasl_getsecret_t interface. */ 367251881Speterstatic int 368251881Speterget_password_cb(sasl_conn_t *conn, void *b, int id, sasl_secret_t **psecret) 369251881Speter{ 370251881Speter cred_baton_t *baton = b; 371251881Speter 372251881Speter if (baton->password || get_credentials(baton)) 373251881Speter { 374251881Speter sasl_secret_t *secret; 375251881Speter size_t len = strlen(baton->password); 376251881Speter 377251881Speter /* sasl_secret_t is a struct with a variable-sized array as a final 378251881Speter member, which means we need to allocate len-1 supplementary bytes 379251881Speter (one byte is part of sasl_secret_t, and we don't need a NULL 380251881Speter terminator). */ 381251881Speter secret = apr_palloc(baton->pool, sizeof(*secret) + len - 1); 382251881Speter secret->len = len; 383251881Speter memcpy(secret->data, baton->password, len); 384251881Speter baton->password = NULL; 385251881Speter *psecret = secret; 386251881Speter 387251881Speter return SASL_OK; 388251881Speter } 389251881Speter 390251881Speter return SASL_FAIL; 391251881Speter} 392251881Speter 393251881Speter/* Create a new SASL context. */ 394251881Speterstatic svn_error_t *new_sasl_ctx(sasl_conn_t **sasl_ctx, 395251881Speter svn_boolean_t is_tunneled, 396251881Speter const char *hostname, 397251881Speter const char *local_addrport, 398251881Speter const char *remote_addrport, 399251881Speter sasl_callback_t *callbacks, 400251881Speter apr_pool_t *pool) 401251881Speter{ 402251881Speter sasl_security_properties_t secprops; 403251881Speter int result; 404251881Speter 405251881Speter clear_sasl_errno(); 406362181Sdim result = svn_sasl__client_new(SVN_RA_SVN_SASL_NAME, 407362181Sdim hostname, local_addrport, remote_addrport, 408362181Sdim callbacks, SASL_SUCCESS_DATA, 409362181Sdim sasl_ctx); 410251881Speter if (result != SASL_OK) 411251881Speter { 412251881Speter const char *sasl_errno_msg = get_sasl_errno_msg(result, pool); 413251881Speter 414251881Speter return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 415251881Speter _("Could not create SASL context: %s%s"), 416362181Sdim svn_sasl__errstring(result, NULL, NULL), 417251881Speter sasl_errno_msg); 418251881Speter } 419251881Speter svn_atomic_inc(&sasl_ctx_count); 420251881Speter apr_pool_cleanup_register(pool, *sasl_ctx, sasl_dispose_cb, 421251881Speter apr_pool_cleanup_null); 422251881Speter 423251881Speter if (is_tunneled) 424251881Speter { 425251881Speter /* We need to tell SASL that this connection is tunneled, 426251881Speter otherwise it will ignore EXTERNAL. The third parameter 427251881Speter should be the username, but since SASL doesn't seem 428251881Speter to use it on the client side, any non-empty string will do. */ 429251881Speter clear_sasl_errno(); 430362181Sdim result = svn_sasl__setprop(*sasl_ctx, 431362181Sdim SASL_AUTH_EXTERNAL, " "); 432251881Speter if (result != SASL_OK) 433251881Speter return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 434251881Speter get_sasl_error(*sasl_ctx, result, pool)); 435251881Speter } 436251881Speter 437251881Speter /* Set security properties. */ 438251881Speter svn_ra_svn__default_secprops(&secprops); 439362181Sdim svn_sasl__setprop(*sasl_ctx, SASL_SEC_PROPS, &secprops); 440251881Speter 441251881Speter return SVN_NO_ERROR; 442251881Speter} 443251881Speter 444251881Speter/* Perform an authentication exchange */ 445251881Speterstatic svn_error_t *try_auth(svn_ra_svn__session_baton_t *sess, 446251881Speter sasl_conn_t *sasl_ctx, 447251881Speter svn_boolean_t *success, 448251881Speter const char **last_err, 449251881Speter const char *mechstring, 450251881Speter apr_pool_t *pool) 451251881Speter{ 452251881Speter sasl_interact_t *client_interact = NULL; 453251881Speter const char *out, *mech, *status = NULL; 454251881Speter const svn_string_t *arg = NULL, *in; 455251881Speter int result; 456251881Speter unsigned int outlen; 457251881Speter svn_boolean_t again; 458251881Speter 459251881Speter do 460251881Speter { 461251881Speter again = FALSE; 462251881Speter clear_sasl_errno(); 463362181Sdim result = svn_sasl__client_start(sasl_ctx, 464362181Sdim mechstring, 465362181Sdim &client_interact, 466362181Sdim &out, 467362181Sdim &outlen, 468362181Sdim &mech); 469251881Speter switch (result) 470251881Speter { 471251881Speter case SASL_OK: 472251881Speter case SASL_CONTINUE: 473251881Speter /* Success. */ 474251881Speter break; 475251881Speter case SASL_NOMECH: 476251881Speter return svn_error_create(SVN_ERR_RA_SVN_NO_MECHANISMS, NULL, NULL); 477251881Speter case SASL_BADPARAM: 478251881Speter case SASL_NOMEM: 479251881Speter /* Fatal error. Fail the authentication. */ 480251881Speter return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 481251881Speter get_sasl_error(sasl_ctx, result, pool)); 482251881Speter default: 483251881Speter /* For anything else, delete the mech from the list 484251881Speter and try again. */ 485251881Speter { 486251881Speter const char *pmech = strstr(mechstring, mech); 487251881Speter const char *head = apr_pstrndup(pool, mechstring, 488251881Speter pmech - mechstring); 489251881Speter const char *tail = pmech + strlen(mech); 490251881Speter 491289180Speter mechstring = apr_pstrcat(pool, head, tail, SVN_VA_NULL); 492251881Speter again = TRUE; 493251881Speter } 494251881Speter } 495251881Speter } 496251881Speter while (again); 497251881Speter 498251881Speter /* Prepare the initial authentication token. */ 499251881Speter if (outlen > 0 || strcmp(mech, "EXTERNAL") == 0) 500251881Speter arg = svn_base64_encode_string2(svn_string_ncreate(out, outlen, pool), 501251881Speter TRUE, pool); 502251881Speter 503251881Speter /* Send the initial client response */ 504251881Speter SVN_ERR(svn_ra_svn__auth_response(sess->conn, pool, mech, 505251881Speter arg ? arg->data : NULL)); 506251881Speter 507251881Speter while (result == SASL_CONTINUE) 508251881Speter { 509251881Speter /* Read the server response */ 510251881Speter SVN_ERR(svn_ra_svn__read_tuple(sess->conn, pool, "w(?s)", 511251881Speter &status, &in)); 512251881Speter 513251881Speter if (strcmp(status, "failure") == 0) 514251881Speter { 515251881Speter /* Authentication failed. Use the next set of credentials */ 516251881Speter *success = FALSE; 517251881Speter /* Remember the message sent by the server because we'll want to 518251881Speter return a meaningful error if we run out of auth providers. */ 519251881Speter *last_err = in ? in->data : ""; 520251881Speter return SVN_NO_ERROR; 521251881Speter } 522251881Speter 523251881Speter if ((strcmp(status, "success") != 0 && strcmp(status, "step") != 0) 524251881Speter || in == NULL) 525251881Speter return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 526251881Speter _("Unexpected server response" 527251881Speter " to authentication")); 528251881Speter 529251881Speter /* If the mech is CRAM-MD5 we don't base64-decode the server response. */ 530251881Speter if (strcmp(mech, "CRAM-MD5") != 0) 531251881Speter in = svn_base64_decode_string(in, pool); 532251881Speter 533251881Speter clear_sasl_errno(); 534362181Sdim result = svn_sasl__client_step(sasl_ctx, 535362181Sdim in->data, 536362181Sdim (const unsigned int) in->len, 537362181Sdim &client_interact, 538362181Sdim &out, /* Filled in by SASL. */ 539362181Sdim &outlen); 540251881Speter 541251881Speter if (result != SASL_OK && result != SASL_CONTINUE) 542251881Speter return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 543251881Speter get_sasl_error(sasl_ctx, result, pool)); 544251881Speter 545251881Speter /* If the server thinks we're done, then don't send any response. */ 546251881Speter if (strcmp(status, "success") == 0) 547251881Speter break; 548251881Speter 549251881Speter if (outlen > 0) 550251881Speter { 551251881Speter arg = svn_string_ncreate(out, outlen, pool); 552251881Speter /* Write our response. */ 553251881Speter /* For CRAM-MD5, we don't use base64-encoding. */ 554251881Speter if (strcmp(mech, "CRAM-MD5") != 0) 555251881Speter arg = svn_base64_encode_string2(arg, TRUE, pool); 556251881Speter SVN_ERR(svn_ra_svn__write_cstring(sess->conn, pool, arg->data)); 557251881Speter } 558251881Speter else 559251881Speter { 560251881Speter SVN_ERR(svn_ra_svn__write_cstring(sess->conn, pool, "")); 561251881Speter } 562251881Speter } 563251881Speter 564251881Speter if (!status || strcmp(status, "step") == 0) 565251881Speter { 566251881Speter /* This is a client-send-last mech. Read the last server response. */ 567251881Speter SVN_ERR(svn_ra_svn__read_tuple(sess->conn, pool, "w(?s)", 568251881Speter &status, &in)); 569251881Speter 570251881Speter if (strcmp(status, "failure") == 0) 571251881Speter { 572251881Speter *success = FALSE; 573251881Speter *last_err = in ? in->data : ""; 574251881Speter } 575251881Speter else if (strcmp(status, "success") == 0) 576251881Speter { 577251881Speter /* We're done */ 578251881Speter *success = TRUE; 579251881Speter } 580251881Speter else 581251881Speter return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 582251881Speter _("Unexpected server response" 583251881Speter " to authentication")); 584251881Speter } 585251881Speter else 586251881Speter *success = TRUE; 587251881Speter return SVN_NO_ERROR; 588251881Speter} 589251881Speter 590251881Speter/* Baton for a SASL encrypted svn_ra_svn__stream_t. */ 591251881Spetertypedef struct sasl_baton { 592251881Speter svn_ra_svn__stream_t *stream; /* Inherited stream. */ 593251881Speter sasl_conn_t *ctx; /* The SASL context for this connection. */ 594251881Speter unsigned int maxsize; /* The maximum amount of data we can encode. */ 595251881Speter const char *read_buf; /* The buffer returned by sasl_decode. */ 596251881Speter unsigned int read_len; /* Its current length. */ 597251881Speter const char *write_buf; /* The buffer returned by sasl_encode. */ 598251881Speter unsigned int write_len; /* Its length. */ 599251881Speter apr_pool_t *scratch_pool; 600251881Speter} sasl_baton_t; 601251881Speter 602251881Speter/* Functions to implement a SASL encrypted svn_ra_svn__stream_t. */ 603251881Speter 604251881Speter/* Implements svn_read_fn_t. */ 605251881Speterstatic svn_error_t *sasl_read_cb(void *baton, char *buffer, apr_size_t *len) 606251881Speter{ 607251881Speter sasl_baton_t *sasl_baton = baton; 608251881Speter int result; 609251881Speter /* A copy of *len, used by the wrapped stream. */ 610251881Speter apr_size_t len2 = *len; 611251881Speter 612251881Speter /* sasl_decode might need more data than a single read can provide, 613251881Speter hence the need to put a loop around the decoding. */ 614251881Speter while (! sasl_baton->read_buf || sasl_baton->read_len == 0) 615251881Speter { 616251881Speter SVN_ERR(svn_ra_svn__stream_read(sasl_baton->stream, buffer, &len2)); 617251881Speter if (len2 == 0) 618251881Speter { 619251881Speter *len = 0; 620251881Speter return SVN_NO_ERROR; 621251881Speter } 622251881Speter clear_sasl_errno(); 623362181Sdim result = svn_sasl__decode(sasl_baton->ctx, buffer, (unsigned int) len2, 624362181Sdim &sasl_baton->read_buf, 625362181Sdim &sasl_baton->read_len); 626251881Speter if (result != SASL_OK) 627251881Speter return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 628251881Speter get_sasl_error(sasl_baton->ctx, result, 629251881Speter sasl_baton->scratch_pool)); 630251881Speter } 631251881Speter 632251881Speter /* The buffer returned by sasl_decode might be larger than what the 633251881Speter caller wants. If this is the case, we only copy back *len bytes now 634251881Speter (the rest will be returned by subsequent calls to this function). 635251881Speter If not, we just copy back the whole thing. */ 636251881Speter if (*len >= sasl_baton->read_len) 637251881Speter { 638251881Speter memcpy(buffer, sasl_baton->read_buf, sasl_baton->read_len); 639251881Speter *len = sasl_baton->read_len; 640251881Speter sasl_baton->read_buf = NULL; 641251881Speter sasl_baton->read_len = 0; 642251881Speter } 643251881Speter else 644251881Speter { 645251881Speter memcpy(buffer, sasl_baton->read_buf, *len); 646251881Speter sasl_baton->read_len -= *len; 647251881Speter sasl_baton->read_buf += *len; 648251881Speter } 649251881Speter 650251881Speter return SVN_NO_ERROR; 651251881Speter} 652251881Speter 653251881Speter/* Implements svn_write_fn_t. */ 654251881Speterstatic svn_error_t * 655251881Spetersasl_write_cb(void *baton, const char *buffer, apr_size_t *len) 656251881Speter{ 657251881Speter sasl_baton_t *sasl_baton = baton; 658251881Speter int result; 659251881Speter 660251881Speter if (! sasl_baton->write_buf || sasl_baton->write_len == 0) 661251881Speter { 662251881Speter /* Make sure we don't write too much. */ 663251881Speter *len = (*len > sasl_baton->maxsize) ? sasl_baton->maxsize : *len; 664251881Speter clear_sasl_errno(); 665362181Sdim result = svn_sasl__encode(sasl_baton->ctx, buffer, (unsigned int) *len, 666362181Sdim &sasl_baton->write_buf, 667362181Sdim &sasl_baton->write_len); 668251881Speter 669251881Speter if (result != SASL_OK) 670251881Speter return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 671251881Speter get_sasl_error(sasl_baton->ctx, result, 672251881Speter sasl_baton->scratch_pool)); 673251881Speter } 674251881Speter 675251881Speter do 676251881Speter { 677251881Speter apr_size_t tmplen = sasl_baton->write_len; 678251881Speter SVN_ERR(svn_ra_svn__stream_write(sasl_baton->stream, 679251881Speter sasl_baton->write_buf, 680251881Speter &tmplen)); 681251881Speter if (tmplen == 0) 682251881Speter { 683251881Speter /* The output buffer and its length will be preserved in sasl_baton 684251881Speter and will be written out during the next call to this function 685251881Speter (which will have the same arguments). */ 686251881Speter *len = 0; 687251881Speter return SVN_NO_ERROR; 688251881Speter } 689251881Speter sasl_baton->write_len -= (unsigned int) tmplen; 690251881Speter sasl_baton->write_buf += tmplen; 691251881Speter } 692251881Speter while (sasl_baton->write_len > 0); 693251881Speter 694251881Speter sasl_baton->write_buf = NULL; 695251881Speter sasl_baton->write_len = 0; 696251881Speter 697251881Speter return SVN_NO_ERROR; 698251881Speter} 699251881Speter 700251881Speter/* Implements ra_svn_timeout_fn_t. */ 701251881Speterstatic void sasl_timeout_cb(void *baton, apr_interval_time_t interval) 702251881Speter{ 703251881Speter sasl_baton_t *sasl_baton = baton; 704251881Speter svn_ra_svn__stream_timeout(sasl_baton->stream, interval); 705251881Speter} 706251881Speter 707289180Speter/* Implements svn_stream_data_available_fn_t. */ 708289180Speterstatic svn_error_t * 709289180Spetersasl_data_available_cb(void *baton, svn_boolean_t *data_available) 710251881Speter{ 711251881Speter sasl_baton_t *sasl_baton = baton; 712289180Speter return svn_error_trace(svn_ra_svn__stream_data_available(sasl_baton->stream, 713289180Speter data_available)); 714251881Speter} 715251881Speter 716251881Spetersvn_error_t *svn_ra_svn__enable_sasl_encryption(svn_ra_svn_conn_t *conn, 717251881Speter sasl_conn_t *sasl_ctx, 718251881Speter apr_pool_t *pool) 719251881Speter{ 720251881Speter const sasl_ssf_t *ssfp; 721251881Speter 722251881Speter if (! conn->encrypted) 723251881Speter { 724251881Speter int result; 725251881Speter 726251881Speter /* Get the strength of the security layer. */ 727251881Speter clear_sasl_errno(); 728362181Sdim result = svn_sasl__getprop(sasl_ctx, SASL_SSF, (void*) &ssfp); 729251881Speter if (result != SASL_OK) 730251881Speter return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 731251881Speter get_sasl_error(sasl_ctx, result, pool)); 732251881Speter 733251881Speter if (*ssfp > 0) 734251881Speter { 735251881Speter sasl_baton_t *sasl_baton; 736251881Speter const void *maxsize; 737251881Speter 738251881Speter /* Flush the connection, as we're about to replace its stream. */ 739251881Speter SVN_ERR(svn_ra_svn__flush(conn, pool)); 740251881Speter 741251881Speter /* Create and initialize the stream baton. */ 742251881Speter sasl_baton = apr_pcalloc(conn->pool, sizeof(*sasl_baton)); 743251881Speter sasl_baton->ctx = sasl_ctx; 744251881Speter sasl_baton->scratch_pool = conn->pool; 745251881Speter 746251881Speter /* Find out the maximum input size for sasl_encode. */ 747251881Speter clear_sasl_errno(); 748362181Sdim result = svn_sasl__getprop(sasl_ctx, SASL_MAXOUTBUF, &maxsize); 749251881Speter if (result != SASL_OK) 750251881Speter return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 751251881Speter get_sasl_error(sasl_ctx, result, pool)); 752251881Speter sasl_baton->maxsize = *((const unsigned int *) maxsize); 753251881Speter 754251881Speter /* If there is any data left in the read buffer at this point, 755251881Speter we need to decrypt it. */ 756251881Speter if (conn->read_end > conn->read_ptr) 757251881Speter { 758251881Speter clear_sasl_errno(); 759362181Sdim result = svn_sasl__decode( 760362181Sdim sasl_ctx, conn->read_ptr, 761362181Sdim (unsigned int) (conn->read_end - conn->read_ptr), 762362181Sdim &sasl_baton->read_buf, &sasl_baton->read_len); 763251881Speter if (result != SASL_OK) 764251881Speter return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 765251881Speter get_sasl_error(sasl_ctx, result, pool)); 766251881Speter conn->read_end = conn->read_ptr; 767251881Speter } 768251881Speter 769251881Speter /* Wrap the existing stream. */ 770251881Speter sasl_baton->stream = conn->stream; 771251881Speter 772289180Speter { 773289180Speter svn_stream_t *sasl_in = svn_stream_create(sasl_baton, conn->pool); 774289180Speter svn_stream_t *sasl_out = svn_stream_create(sasl_baton, conn->pool); 775289180Speter 776289180Speter svn_stream_set_read2(sasl_in, sasl_read_cb, NULL /* use default */); 777289180Speter svn_stream_set_data_available(sasl_in, sasl_data_available_cb); 778289180Speter svn_stream_set_write(sasl_out, sasl_write_cb); 779289180Speter 780289180Speter conn->stream = svn_ra_svn__stream_create(sasl_in, sasl_out, 781289180Speter sasl_baton, 782289180Speter sasl_timeout_cb, 783289180Speter conn->pool); 784289180Speter } 785251881Speter /* Yay, we have a security layer! */ 786251881Speter conn->encrypted = TRUE; 787251881Speter } 788251881Speter } 789251881Speter return SVN_NO_ERROR; 790251881Speter} 791251881Speter 792251881Spetersvn_error_t *svn_ra_svn__get_addresses(const char **local_addrport, 793251881Speter const char **remote_addrport, 794251881Speter svn_ra_svn_conn_t *conn, 795251881Speter apr_pool_t *pool) 796251881Speter{ 797251881Speter if (conn->sock) 798251881Speter { 799251881Speter apr_status_t apr_err; 800251881Speter apr_sockaddr_t *local_sa, *remote_sa; 801251881Speter char *local_addr, *remote_addr; 802251881Speter 803251881Speter apr_err = apr_socket_addr_get(&local_sa, APR_LOCAL, conn->sock); 804251881Speter if (apr_err) 805251881Speter return svn_error_wrap_apr(apr_err, NULL); 806251881Speter 807251881Speter apr_err = apr_socket_addr_get(&remote_sa, APR_REMOTE, conn->sock); 808251881Speter if (apr_err) 809251881Speter return svn_error_wrap_apr(apr_err, NULL); 810251881Speter 811251881Speter apr_err = apr_sockaddr_ip_get(&local_addr, local_sa); 812251881Speter if (apr_err) 813251881Speter return svn_error_wrap_apr(apr_err, NULL); 814251881Speter 815251881Speter apr_err = apr_sockaddr_ip_get(&remote_addr, remote_sa); 816251881Speter if (apr_err) 817251881Speter return svn_error_wrap_apr(apr_err, NULL); 818251881Speter 819251881Speter /* Format the IP address and port number like this: a.b.c.d;port */ 820251881Speter *local_addrport = apr_pstrcat(pool, local_addr, ";", 821251881Speter apr_itoa(pool, (int)local_sa->port), 822289180Speter SVN_VA_NULL); 823251881Speter *remote_addrport = apr_pstrcat(pool, remote_addr, ";", 824251881Speter apr_itoa(pool, (int)remote_sa->port), 825289180Speter SVN_VA_NULL); 826251881Speter } 827251881Speter return SVN_NO_ERROR; 828251881Speter} 829251881Speter 830251881Spetersvn_error_t * 831251881Spetersvn_ra_svn__do_cyrus_auth(svn_ra_svn__session_baton_t *sess, 832362181Sdim const svn_ra_svn__list_t *mechlist, 833251881Speter const char *realm, apr_pool_t *pool) 834251881Speter{ 835251881Speter apr_pool_t *subpool; 836251881Speter sasl_conn_t *sasl_ctx; 837251881Speter const char *mechstring = "", *last_err = "", *realmstring; 838251881Speter const char *local_addrport = NULL, *remote_addrport = NULL; 839251881Speter svn_boolean_t success; 840251881Speter sasl_callback_t *callbacks; 841251881Speter cred_baton_t cred_baton = { 0 }; 842251881Speter int i; 843251881Speter 844251881Speter if (!sess->is_tunneled) 845251881Speter { 846251881Speter SVN_ERR(svn_ra_svn__get_addresses(&local_addrport, &remote_addrport, 847251881Speter sess->conn, pool)); 848251881Speter } 849251881Speter 850251881Speter /* Prefer EXTERNAL, then ANONYMOUS, then let SASL decide. */ 851251881Speter if (svn_ra_svn__find_mech(mechlist, "EXTERNAL")) 852251881Speter mechstring = "EXTERNAL"; 853251881Speter else if (svn_ra_svn__find_mech(mechlist, "ANONYMOUS")) 854251881Speter mechstring = "ANONYMOUS"; 855251881Speter else 856251881Speter { 857251881Speter /* Create a string containing the list of mechanisms, separated by spaces. */ 858251881Speter for (i = 0; i < mechlist->nelts; i++) 859251881Speter { 860362181Sdim svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(mechlist, i); 861251881Speter mechstring = apr_pstrcat(pool, 862251881Speter mechstring, 863251881Speter i == 0 ? "" : " ", 864362181Sdim elt->u.word.data, SVN_VA_NULL); 865251881Speter } 866251881Speter } 867251881Speter 868251881Speter realmstring = apr_psprintf(pool, "%s %s", sess->realm_prefix, realm); 869251881Speter 870251881Speter /* Initialize the credential baton. */ 871289180Speter cred_baton.auth_baton = sess->auth_baton; 872251881Speter cred_baton.realmstring = realmstring; 873251881Speter cred_baton.pool = pool; 874251881Speter 875251881Speter /* Reserve space for 3 callbacks (for the username, password and the 876251881Speter array terminator). These structures must persist until the 877251881Speter disposal of the SASL context at pool cleanup, however the 878251881Speter callback functions will not be invoked outside this function so 879251881Speter other structures can have a shorter lifetime. */ 880251881Speter callbacks = apr_palloc(sess->conn->pool, sizeof(*callbacks) * 3); 881251881Speter 882251881Speter /* Initialize the callbacks array. */ 883251881Speter 884251881Speter /* The username callback. */ 885251881Speter callbacks[0].id = SASL_CB_AUTHNAME; 886251881Speter callbacks[0].proc = (int (*)(void))get_username_cb; 887251881Speter callbacks[0].context = &cred_baton; 888251881Speter 889251881Speter /* The password callback. */ 890251881Speter callbacks[1].id = SASL_CB_PASS; 891251881Speter callbacks[1].proc = (int (*)(void))get_password_cb; 892251881Speter callbacks[1].context = &cred_baton; 893251881Speter 894251881Speter /* Mark the end of the array. */ 895251881Speter callbacks[2].id = SASL_CB_LIST_END; 896251881Speter callbacks[2].proc = NULL; 897251881Speter callbacks[2].context = NULL; 898251881Speter 899251881Speter subpool = svn_pool_create(pool); 900251881Speter do 901251881Speter { 902251881Speter svn_error_t *err; 903251881Speter 904251881Speter /* If last_err was set to a non-empty string, it needs to be duplicated 905251881Speter to the parent pool before the subpool is cleared. */ 906251881Speter if (*last_err) 907251881Speter last_err = apr_pstrdup(pool, last_err); 908251881Speter svn_pool_clear(subpool); 909251881Speter 910251881Speter SVN_ERR(new_sasl_ctx(&sasl_ctx, sess->is_tunneled, 911251881Speter sess->hostname, local_addrport, remote_addrport, 912251881Speter callbacks, sess->conn->pool)); 913251881Speter err = try_auth(sess, sasl_ctx, &success, &last_err, mechstring, 914251881Speter subpool); 915251881Speter 916251881Speter /* If we encountered an error while fetching credentials, that error 917251881Speter has priority. */ 918251881Speter if (cred_baton.err) 919251881Speter { 920251881Speter svn_error_clear(err); 921251881Speter return cred_baton.err; 922251881Speter } 923251881Speter if (cred_baton.no_more_creds 924251881Speter || (! err && ! success && ! cred_baton.was_used)) 925251881Speter { 926251881Speter svn_error_clear(err); 927251881Speter /* If we ran out of authentication providers, or if we got a server 928251881Speter error and our callbacks were never called, there's no point in 929251881Speter retrying authentication. Return the last error sent by the 930251881Speter server. */ 931251881Speter if (*last_err) 932251881Speter return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 933251881Speter _("Authentication error from server: %s"), 934251881Speter last_err); 935251881Speter /* Hmm, we don't have a server error. Return a generic error. */ 936251881Speter return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 937251881Speter _("Can't get username or password")); 938251881Speter } 939251881Speter if (err) 940251881Speter { 941251881Speter if (err->apr_err == SVN_ERR_RA_SVN_NO_MECHANISMS) 942251881Speter { 943251881Speter svn_error_clear(err); 944251881Speter 945251881Speter /* We could not find a supported mechanism in the list sent by the 946251881Speter server. In many cases this happens because the client is missing 947251881Speter the CRAM-MD5 or ANONYMOUS plugins, in which case we can simply use 948251881Speter the built-in implementation. In all other cases this call will be 949251881Speter useless, but hey, at least we'll get consistent error messages. */ 950289180Speter return svn_error_trace(svn_ra_svn__do_internal_auth(sess, mechlist, 951289180Speter realm, pool)); 952251881Speter } 953251881Speter return err; 954251881Speter } 955251881Speter } 956251881Speter while (!success); 957251881Speter svn_pool_destroy(subpool); 958251881Speter 959251881Speter SVN_ERR(svn_ra_svn__enable_sasl_encryption(sess->conn, sasl_ctx, pool)); 960251881Speter 961251881Speter SVN_ERR(svn_auth_save_credentials(cred_baton.iterstate, pool)); 962251881Speter 963251881Speter return SVN_NO_ERROR; 964251881Speter} 965251881Speter 966251881Speter#endif /* SVN_HAVE_SASL */ 967