1/* 2 * cyrus_auth.c : functions for Cyrus SASL-based authentication 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24#include "svn_private_config.h" 25#ifdef SVN_HAVE_SASL 26 27#define APR_WANT_STRFUNC 28#include <apr_want.h> 29#include <apr_general.h> 30#include <apr_strings.h> 31#include <apr_version.h> 32 33#include "svn_types.h" 34#include "svn_string.h" 35#include "svn_error.h" 36#include "svn_pools.h" 37#include "svn_ra.h" 38#include "svn_ra_svn.h" 39#include "svn_base64.h" 40 41#include "private/svn_atomic.h" 42#include "private/ra_svn_sasl.h" 43#include "private/svn_mutex.h" 44 45#include "ra_svn.h" 46 47/* Note: In addition to being used via svn_atomic__init_once to control 48 * initialization of the SASL code this will also be referenced in 49 * the various functions that work with sasl mutexes to determine 50 * if the sasl pool has been destroyed. This should be safe, since 51 * it is only set back to zero in the sasl pool's cleanups, which 52 * only happens during apr_terminate, which we assume is occurring 53 * in atexit processing, at which point we are already running in 54 * single threaded mode. 55 */ 56volatile svn_atomic_t svn_ra_svn__sasl_status = 0; 57 58/* Initialized by svn_ra_svn__sasl_common_init(). */ 59static volatile svn_atomic_t sasl_ctx_count; 60 61static apr_pool_t *sasl_pool = NULL; 62 63 64/* Pool cleanup called when sasl_pool is destroyed. */ 65static apr_status_t sasl_done_cb(void *data) 66{ 67 /* Reset svn_ra_svn__sasl_status, in case the client calls 68 apr_initialize()/apr_terminate() more than once. */ 69 svn_ra_svn__sasl_status = 0; 70 if (svn_atomic_dec(&sasl_ctx_count) == 0) 71 svn_sasl__done(); 72 return APR_SUCCESS; 73} 74 75#if APR_HAS_THREADS 76/* Cyrus SASL is thread-safe only if we supply it with mutex functions 77 * (with sasl_set_mutex()). To make this work with APR, we need to use the 78 * global sasl_pool for the mutex allocations. Freeing a mutex actually 79 * returns it to a global array. We allocate mutexes from this 80 * array if it is non-empty, or directly from the pool otherwise. 81 * We also need a mutex to serialize accesses to the array itself. 82 */ 83 84/* An array of allocated, but unused, apr_thread_mutex_t's. */ 85static apr_array_header_t *free_mutexes = NULL; 86 87/* A mutex to serialize access to the array. */ 88static svn_mutex__t *array_mutex = NULL; 89 90/* Callbacks we pass to sasl_set_mutex(). */ 91 92static svn_error_t * 93sasl_mutex_alloc_cb_internal(svn_mutex__t **mutex) 94{ 95 if (apr_is_empty_array(free_mutexes)) 96 return svn_mutex__init(mutex, TRUE, sasl_pool); 97 else 98 *mutex = *((svn_mutex__t**)apr_array_pop(free_mutexes)); 99 100 return SVN_NO_ERROR; 101} 102 103static void *sasl_mutex_alloc_cb(void) 104{ 105 svn_mutex__t *mutex = NULL; 106 svn_error_t *err; 107 108 if (!svn_ra_svn__sasl_status) 109 return NULL; 110 111 err = svn_mutex__lock(array_mutex); 112 if (err) 113 svn_error_clear(err); 114 else 115 svn_error_clear(svn_mutex__unlock(array_mutex, 116 sasl_mutex_alloc_cb_internal(&mutex))); 117 118 return mutex; 119} 120 121static int check_result(svn_error_t *err) 122{ 123 if (err) 124 { 125 svn_error_clear(err); 126 return -1; 127 } 128 129 return 0; 130} 131 132static int sasl_mutex_lock_cb(void *mutex) 133{ 134 if (!svn_ra_svn__sasl_status) 135 return 0; 136 return check_result(svn_mutex__lock(mutex)); 137} 138 139static int sasl_mutex_unlock_cb(void *mutex) 140{ 141 if (!svn_ra_svn__sasl_status) 142 return 0; 143 return check_result(svn_mutex__unlock(mutex, SVN_NO_ERROR)); 144} 145 146static svn_error_t * 147sasl_mutex_free_cb_internal(void *mutex) 148{ 149 APR_ARRAY_PUSH(free_mutexes, svn_mutex__t*) = mutex; 150 return SVN_NO_ERROR; 151} 152 153static void sasl_mutex_free_cb(void *mutex) 154{ 155 svn_error_t *err; 156 157 if (!svn_ra_svn__sasl_status) 158 return; 159 160 err = svn_mutex__lock(array_mutex); 161 if (err) 162 svn_error_clear(err); 163 else 164 svn_error_clear(svn_mutex__unlock(array_mutex, 165 sasl_mutex_free_cb_internal(mutex))); 166} 167#endif /* APR_HAS_THREADS */ 168 169svn_error_t * 170svn_ra_svn__sasl_common_init(apr_pool_t *pool) 171{ 172 sasl_pool = svn_pool_create(pool); 173 sasl_ctx_count = 1; 174 apr_pool_cleanup_register(sasl_pool, NULL, sasl_done_cb, 175 apr_pool_cleanup_null); 176#if APR_HAS_THREADS 177 svn_sasl__set_mutex(sasl_mutex_alloc_cb, 178 sasl_mutex_lock_cb, 179 sasl_mutex_unlock_cb, 180 sasl_mutex_free_cb); 181 free_mutexes = apr_array_make(sasl_pool, 0, sizeof(svn_mutex__t *)); 182 SVN_ERR(svn_mutex__init(&array_mutex, TRUE, sasl_pool)); 183 184#endif /* APR_HAS_THREADS */ 185 186 return SVN_NO_ERROR; 187} 188 189/* We are going to look at errno when we get SASL_FAIL but we don't 190 know for sure whether SASL always sets errno. Clearing errno 191 before calling SASL functions helps in cases where SASL does 192 nothing to set errno. */ 193#ifdef apr_set_os_error 194#define clear_sasl_errno() apr_set_os_error(APR_SUCCESS) 195#else 196#define clear_sasl_errno() (void)0 197#endif 198 199/* Sometimes SASL returns SASL_FAIL as RESULT and sets errno. 200 * SASL_FAIL translates to "generic error" which is quite unhelpful. 201 * Try to append a more informative error message based on errno so 202 * should be called before doing anything that may change errno. */ 203static const char * 204get_sasl_errno_msg(int result, apr_pool_t *result_pool) 205{ 206#ifdef apr_get_os_error 207 char buf[1024]; 208 209 if (result == SASL_FAIL && apr_get_os_error() != 0) 210 return apr_psprintf(result_pool, ": %s", 211 svn_strerror(apr_get_os_error(), buf, sizeof(buf))); 212#endif 213 return ""; 214} 215 216/* Wrap an error message from SASL with a prefix that allows users 217 * to tell that the error message came from SASL. Queries errno and 218 * so should be called before doing anything that may change errno. */ 219static const char * 220get_sasl_error(sasl_conn_t *sasl_ctx, int result, apr_pool_t *result_pool) 221{ 222 const char *sasl_errno_msg = get_sasl_errno_msg(result, result_pool); 223 224 return apr_psprintf(result_pool, 225 _("SASL authentication error: %s%s"), 226 svn_sasl__errdetail(sasl_ctx), sasl_errno_msg); 227} 228 229static svn_error_t *sasl_init_cb(void *baton, apr_pool_t *pool) 230{ 231 int result; 232 233 SVN_ERR(svn_ra_svn__sasl_common_init(pool)); 234 clear_sasl_errno(); 235 result = svn_sasl__client_init(NULL); 236 if (result != SASL_OK) 237 { 238 const char *sasl_errno_msg = get_sasl_errno_msg(result, pool); 239 240 return svn_error_createf 241 (SVN_ERR_RA_NOT_AUTHORIZED, NULL, 242 _("Could not initialized the SASL library: %s%s"), 243 svn_sasl__errstring(result, NULL, NULL), 244 sasl_errno_msg); 245 } 246 247 return SVN_NO_ERROR; 248} 249 250svn_error_t *svn_ra_svn__sasl_init(void) 251{ 252 SVN_ERR(svn_atomic__init_once(&svn_ra_svn__sasl_status, 253 sasl_init_cb, NULL, NULL)); 254 return SVN_NO_ERROR; 255} 256 257static apr_status_t sasl_dispose_cb(void *data) 258{ 259 sasl_conn_t *sasl_ctx = data; 260 svn_sasl__dispose(&sasl_ctx); 261 if (svn_atomic_dec(&sasl_ctx_count) == 0) 262 svn_sasl__done(); 263 return APR_SUCCESS; 264} 265 266void svn_ra_svn__default_secprops(sasl_security_properties_t *secprops) 267{ 268 /* The minimum and maximum security strength factors that the chosen 269 SASL mechanism should provide. 0 means 'no encryption', 256 means 270 '256-bit encryption', which is about the best that any SASL 271 mechanism can provide. Using these values effectively means 'use 272 whatever encryption the other side wants'. Note that SASL will try 273 to use better encryption whenever possible, so if both the server and 274 the client use these values the highest possible encryption strength 275 will be used. */ 276 secprops->min_ssf = 0; 277 secprops->max_ssf = 256; 278 279 /* Set maxbufsize to the maximum amount of data we can read at any one time. 280 This value needs to be commmunicated to the peer if a security layer 281 is negotiated. */ 282 secprops->maxbufsize = SVN_RA_SVN__READBUF_SIZE; 283 284 secprops->security_flags = 0; 285 secprops->property_names = secprops->property_values = NULL; 286} 287 288/* A baton type used by the SASL username and password callbacks. */ 289typedef struct cred_baton { 290 svn_auth_baton_t *auth_baton; 291 svn_auth_iterstate_t *iterstate; 292 const char *realmstring; 293 294 /* Unfortunately SASL uses two separate callbacks for the username and 295 password, but we must fetch both of them at the same time. So we cache 296 their values in the baton, set them to NULL individually when SASL 297 demands them, and fetch the next pair when both are NULL. */ 298 const char *username; 299 const char *password; 300 301 /* Any errors we receive from svn_auth_{first,next}_credentials 302 are saved here. */ 303 svn_error_t *err; 304 305 /* This flag is set when we run out of credential providers. */ 306 svn_boolean_t no_more_creds; 307 308 /* Were the auth callbacks ever called? */ 309 svn_boolean_t was_used; 310 311 apr_pool_t *pool; 312} cred_baton_t; 313 314/* Call svn_auth_{first,next}_credentials. If successful, set BATON->username 315 and BATON->password to the new username and password and return TRUE, 316 otherwise return FALSE. If there are no more credentials, set 317 BATON->no_more_creds to TRUE. Any errors are saved in BATON->err. */ 318static svn_boolean_t 319get_credentials(cred_baton_t *baton) 320{ 321 void *creds; 322 323 if (baton->iterstate) 324 baton->err = svn_auth_next_credentials(&creds, baton->iterstate, 325 baton->pool); 326 else 327 baton->err = svn_auth_first_credentials(&creds, &baton->iterstate, 328 SVN_AUTH_CRED_SIMPLE, 329 baton->realmstring, 330 baton->auth_baton, baton->pool); 331 if (baton->err) 332 return FALSE; 333 334 if (! creds) 335 { 336 baton->no_more_creds = TRUE; 337 return FALSE; 338 } 339 340 baton->username = ((svn_auth_cred_simple_t *)creds)->username; 341 baton->password = ((svn_auth_cred_simple_t *)creds)->password; 342 baton->was_used = TRUE; 343 344 return TRUE; 345} 346 347/* The username callback. Implements the sasl_getsimple_t interface. */ 348static int 349get_username_cb(void *b, int id, const char **username, size_t *len) 350{ 351 cred_baton_t *baton = b; 352 353 if (baton->username || get_credentials(baton)) 354 { 355 *username = baton->username; 356 if (len) 357 *len = strlen(baton->username); 358 baton->username = NULL; 359 360 return SASL_OK; 361 } 362 363 return SASL_FAIL; 364} 365 366/* The password callback. Implements the sasl_getsecret_t interface. */ 367static int 368get_password_cb(sasl_conn_t *conn, void *b, int id, sasl_secret_t **psecret) 369{ 370 cred_baton_t *baton = b; 371 372 if (baton->password || get_credentials(baton)) 373 { 374 sasl_secret_t *secret; 375 size_t len = strlen(baton->password); 376 377 /* sasl_secret_t is a struct with a variable-sized array as a final 378 member, which means we need to allocate len-1 supplementary bytes 379 (one byte is part of sasl_secret_t, and we don't need a NULL 380 terminator). */ 381 secret = apr_palloc(baton->pool, sizeof(*secret) + len - 1); 382 secret->len = len; 383 memcpy(secret->data, baton->password, len); 384 baton->password = NULL; 385 *psecret = secret; 386 387 return SASL_OK; 388 } 389 390 return SASL_FAIL; 391} 392 393/* Create a new SASL context. */ 394static svn_error_t *new_sasl_ctx(sasl_conn_t **sasl_ctx, 395 svn_boolean_t is_tunneled, 396 const char *hostname, 397 const char *local_addrport, 398 const char *remote_addrport, 399 sasl_callback_t *callbacks, 400 apr_pool_t *pool) 401{ 402 sasl_security_properties_t secprops; 403 int result; 404 405 clear_sasl_errno(); 406 result = svn_sasl__client_new(SVN_RA_SVN_SASL_NAME, 407 hostname, local_addrport, remote_addrport, 408 callbacks, SASL_SUCCESS_DATA, 409 sasl_ctx); 410 if (result != SASL_OK) 411 { 412 const char *sasl_errno_msg = get_sasl_errno_msg(result, pool); 413 414 return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 415 _("Could not create SASL context: %s%s"), 416 svn_sasl__errstring(result, NULL, NULL), 417 sasl_errno_msg); 418 } 419 svn_atomic_inc(&sasl_ctx_count); 420 apr_pool_cleanup_register(pool, *sasl_ctx, sasl_dispose_cb, 421 apr_pool_cleanup_null); 422 423 if (is_tunneled) 424 { 425 /* We need to tell SASL that this connection is tunneled, 426 otherwise it will ignore EXTERNAL. The third parameter 427 should be the username, but since SASL doesn't seem 428 to use it on the client side, any non-empty string will do. */ 429 clear_sasl_errno(); 430 result = svn_sasl__setprop(*sasl_ctx, 431 SASL_AUTH_EXTERNAL, " "); 432 if (result != SASL_OK) 433 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 434 get_sasl_error(*sasl_ctx, result, pool)); 435 } 436 437 /* Set security properties. */ 438 svn_ra_svn__default_secprops(&secprops); 439 svn_sasl__setprop(*sasl_ctx, SASL_SEC_PROPS, &secprops); 440 441 return SVN_NO_ERROR; 442} 443 444/* Perform an authentication exchange */ 445static svn_error_t *try_auth(svn_ra_svn__session_baton_t *sess, 446 sasl_conn_t *sasl_ctx, 447 svn_boolean_t *success, 448 const char **last_err, 449 const char *mechstring, 450 apr_pool_t *pool) 451{ 452 sasl_interact_t *client_interact = NULL; 453 const char *out, *mech, *status = NULL; 454 const svn_string_t *arg = NULL, *in; 455 int result; 456 unsigned int outlen; 457 svn_boolean_t again; 458 459 do 460 { 461 again = FALSE; 462 clear_sasl_errno(); 463 result = svn_sasl__client_start(sasl_ctx, 464 mechstring, 465 &client_interact, 466 &out, 467 &outlen, 468 &mech); 469 switch (result) 470 { 471 case SASL_OK: 472 case SASL_CONTINUE: 473 /* Success. */ 474 break; 475 case SASL_NOMECH: 476 return svn_error_create(SVN_ERR_RA_SVN_NO_MECHANISMS, NULL, NULL); 477 case SASL_BADPARAM: 478 case SASL_NOMEM: 479 /* Fatal error. Fail the authentication. */ 480 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 481 get_sasl_error(sasl_ctx, result, pool)); 482 default: 483 /* For anything else, delete the mech from the list 484 and try again. */ 485 { 486 const char *pmech = strstr(mechstring, mech); 487 const char *head = apr_pstrndup(pool, mechstring, 488 pmech - mechstring); 489 const char *tail = pmech + strlen(mech); 490 491 mechstring = apr_pstrcat(pool, head, tail, SVN_VA_NULL); 492 again = TRUE; 493 } 494 } 495 } 496 while (again); 497 498 /* Prepare the initial authentication token. */ 499 if (outlen > 0 || strcmp(mech, "EXTERNAL") == 0) 500 arg = svn_base64_encode_string2(svn_string_ncreate(out, outlen, pool), 501 TRUE, pool); 502 503 /* Send the initial client response */ 504 SVN_ERR(svn_ra_svn__auth_response(sess->conn, pool, mech, 505 arg ? arg->data : NULL)); 506 507 while (result == SASL_CONTINUE) 508 { 509 /* Read the server response */ 510 SVN_ERR(svn_ra_svn__read_tuple(sess->conn, pool, "w(?s)", 511 &status, &in)); 512 513 if (strcmp(status, "failure") == 0) 514 { 515 /* Authentication failed. Use the next set of credentials */ 516 *success = FALSE; 517 /* Remember the message sent by the server because we'll want to 518 return a meaningful error if we run out of auth providers. */ 519 *last_err = in ? in->data : ""; 520 return SVN_NO_ERROR; 521 } 522 523 if ((strcmp(status, "success") != 0 && strcmp(status, "step") != 0) 524 || in == NULL) 525 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 526 _("Unexpected server response" 527 " to authentication")); 528 529 /* If the mech is CRAM-MD5 we don't base64-decode the server response. */ 530 if (strcmp(mech, "CRAM-MD5") != 0) 531 in = svn_base64_decode_string(in, pool); 532 533 clear_sasl_errno(); 534 result = svn_sasl__client_step(sasl_ctx, 535 in->data, 536 (const unsigned int) in->len, 537 &client_interact, 538 &out, /* Filled in by SASL. */ 539 &outlen); 540 541 if (result != SASL_OK && result != SASL_CONTINUE) 542 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 543 get_sasl_error(sasl_ctx, result, pool)); 544 545 /* If the server thinks we're done, then don't send any response. */ 546 if (strcmp(status, "success") == 0) 547 break; 548 549 if (outlen > 0) 550 { 551 arg = svn_string_ncreate(out, outlen, pool); 552 /* Write our response. */ 553 /* For CRAM-MD5, we don't use base64-encoding. */ 554 if (strcmp(mech, "CRAM-MD5") != 0) 555 arg = svn_base64_encode_string2(arg, TRUE, pool); 556 SVN_ERR(svn_ra_svn__write_cstring(sess->conn, pool, arg->data)); 557 } 558 else 559 { 560 SVN_ERR(svn_ra_svn__write_cstring(sess->conn, pool, "")); 561 } 562 } 563 564 if (!status || strcmp(status, "step") == 0) 565 { 566 /* This is a client-send-last mech. Read the last server response. */ 567 SVN_ERR(svn_ra_svn__read_tuple(sess->conn, pool, "w(?s)", 568 &status, &in)); 569 570 if (strcmp(status, "failure") == 0) 571 { 572 *success = FALSE; 573 *last_err = in ? in->data : ""; 574 } 575 else if (strcmp(status, "success") == 0) 576 { 577 /* We're done */ 578 *success = TRUE; 579 } 580 else 581 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 582 _("Unexpected server response" 583 " to authentication")); 584 } 585 else 586 *success = TRUE; 587 return SVN_NO_ERROR; 588} 589 590/* Baton for a SASL encrypted svn_ra_svn__stream_t. */ 591typedef struct sasl_baton { 592 svn_ra_svn__stream_t *stream; /* Inherited stream. */ 593 sasl_conn_t *ctx; /* The SASL context for this connection. */ 594 unsigned int maxsize; /* The maximum amount of data we can encode. */ 595 const char *read_buf; /* The buffer returned by sasl_decode. */ 596 unsigned int read_len; /* Its current length. */ 597 const char *write_buf; /* The buffer returned by sasl_encode. */ 598 unsigned int write_len; /* Its length. */ 599 apr_pool_t *scratch_pool; 600} sasl_baton_t; 601 602/* Functions to implement a SASL encrypted svn_ra_svn__stream_t. */ 603 604/* Implements svn_read_fn_t. */ 605static svn_error_t *sasl_read_cb(void *baton, char *buffer, apr_size_t *len) 606{ 607 sasl_baton_t *sasl_baton = baton; 608 int result; 609 /* A copy of *len, used by the wrapped stream. */ 610 apr_size_t len2 = *len; 611 612 /* sasl_decode might need more data than a single read can provide, 613 hence the need to put a loop around the decoding. */ 614 while (! sasl_baton->read_buf || sasl_baton->read_len == 0) 615 { 616 SVN_ERR(svn_ra_svn__stream_read(sasl_baton->stream, buffer, &len2)); 617 if (len2 == 0) 618 { 619 *len = 0; 620 return SVN_NO_ERROR; 621 } 622 clear_sasl_errno(); 623 result = svn_sasl__decode(sasl_baton->ctx, buffer, (unsigned int) len2, 624 &sasl_baton->read_buf, 625 &sasl_baton->read_len); 626 if (result != SASL_OK) 627 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 628 get_sasl_error(sasl_baton->ctx, result, 629 sasl_baton->scratch_pool)); 630 } 631 632 /* The buffer returned by sasl_decode might be larger than what the 633 caller wants. If this is the case, we only copy back *len bytes now 634 (the rest will be returned by subsequent calls to this function). 635 If not, we just copy back the whole thing. */ 636 if (*len >= sasl_baton->read_len) 637 { 638 memcpy(buffer, sasl_baton->read_buf, sasl_baton->read_len); 639 *len = sasl_baton->read_len; 640 sasl_baton->read_buf = NULL; 641 sasl_baton->read_len = 0; 642 } 643 else 644 { 645 memcpy(buffer, sasl_baton->read_buf, *len); 646 sasl_baton->read_len -= *len; 647 sasl_baton->read_buf += *len; 648 } 649 650 return SVN_NO_ERROR; 651} 652 653/* Implements svn_write_fn_t. */ 654static svn_error_t * 655sasl_write_cb(void *baton, const char *buffer, apr_size_t *len) 656{ 657 sasl_baton_t *sasl_baton = baton; 658 int result; 659 660 if (! sasl_baton->write_buf || sasl_baton->write_len == 0) 661 { 662 /* Make sure we don't write too much. */ 663 *len = (*len > sasl_baton->maxsize) ? sasl_baton->maxsize : *len; 664 clear_sasl_errno(); 665 result = svn_sasl__encode(sasl_baton->ctx, buffer, (unsigned int) *len, 666 &sasl_baton->write_buf, 667 &sasl_baton->write_len); 668 669 if (result != SASL_OK) 670 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 671 get_sasl_error(sasl_baton->ctx, result, 672 sasl_baton->scratch_pool)); 673 } 674 675 do 676 { 677 apr_size_t tmplen = sasl_baton->write_len; 678 SVN_ERR(svn_ra_svn__stream_write(sasl_baton->stream, 679 sasl_baton->write_buf, 680 &tmplen)); 681 if (tmplen == 0) 682 { 683 /* The output buffer and its length will be preserved in sasl_baton 684 and will be written out during the next call to this function 685 (which will have the same arguments). */ 686 *len = 0; 687 return SVN_NO_ERROR; 688 } 689 sasl_baton->write_len -= (unsigned int) tmplen; 690 sasl_baton->write_buf += tmplen; 691 } 692 while (sasl_baton->write_len > 0); 693 694 sasl_baton->write_buf = NULL; 695 sasl_baton->write_len = 0; 696 697 return SVN_NO_ERROR; 698} 699 700/* Implements ra_svn_timeout_fn_t. */ 701static void sasl_timeout_cb(void *baton, apr_interval_time_t interval) 702{ 703 sasl_baton_t *sasl_baton = baton; 704 svn_ra_svn__stream_timeout(sasl_baton->stream, interval); 705} 706 707/* Implements svn_stream_data_available_fn_t. */ 708static svn_error_t * 709sasl_data_available_cb(void *baton, svn_boolean_t *data_available) 710{ 711 sasl_baton_t *sasl_baton = baton; 712 return svn_error_trace(svn_ra_svn__stream_data_available(sasl_baton->stream, 713 data_available)); 714} 715 716svn_error_t *svn_ra_svn__enable_sasl_encryption(svn_ra_svn_conn_t *conn, 717 sasl_conn_t *sasl_ctx, 718 apr_pool_t *pool) 719{ 720 const sasl_ssf_t *ssfp; 721 722 if (! conn->encrypted) 723 { 724 int result; 725 726 /* Get the strength of the security layer. */ 727 clear_sasl_errno(); 728 result = svn_sasl__getprop(sasl_ctx, SASL_SSF, (void*) &ssfp); 729 if (result != SASL_OK) 730 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 731 get_sasl_error(sasl_ctx, result, pool)); 732 733 if (*ssfp > 0) 734 { 735 sasl_baton_t *sasl_baton; 736 const void *maxsize; 737 738 /* Flush the connection, as we're about to replace its stream. */ 739 SVN_ERR(svn_ra_svn__flush(conn, pool)); 740 741 /* Create and initialize the stream baton. */ 742 sasl_baton = apr_pcalloc(conn->pool, sizeof(*sasl_baton)); 743 sasl_baton->ctx = sasl_ctx; 744 sasl_baton->scratch_pool = conn->pool; 745 746 /* Find out the maximum input size for sasl_encode. */ 747 clear_sasl_errno(); 748 result = svn_sasl__getprop(sasl_ctx, SASL_MAXOUTBUF, &maxsize); 749 if (result != SASL_OK) 750 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 751 get_sasl_error(sasl_ctx, result, pool)); 752 sasl_baton->maxsize = *((const unsigned int *) maxsize); 753 754 /* If there is any data left in the read buffer at this point, 755 we need to decrypt it. */ 756 if (conn->read_end > conn->read_ptr) 757 { 758 clear_sasl_errno(); 759 result = svn_sasl__decode( 760 sasl_ctx, conn->read_ptr, 761 (unsigned int) (conn->read_end - conn->read_ptr), 762 &sasl_baton->read_buf, &sasl_baton->read_len); 763 if (result != SASL_OK) 764 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 765 get_sasl_error(sasl_ctx, result, pool)); 766 conn->read_end = conn->read_ptr; 767 } 768 769 /* Wrap the existing stream. */ 770 sasl_baton->stream = conn->stream; 771 772 { 773 svn_stream_t *sasl_in = svn_stream_create(sasl_baton, conn->pool); 774 svn_stream_t *sasl_out = svn_stream_create(sasl_baton, conn->pool); 775 776 svn_stream_set_read2(sasl_in, sasl_read_cb, NULL /* use default */); 777 svn_stream_set_data_available(sasl_in, sasl_data_available_cb); 778 svn_stream_set_write(sasl_out, sasl_write_cb); 779 780 conn->stream = svn_ra_svn__stream_create(sasl_in, sasl_out, 781 sasl_baton, 782 sasl_timeout_cb, 783 conn->pool); 784 } 785 /* Yay, we have a security layer! */ 786 conn->encrypted = TRUE; 787 } 788 } 789 return SVN_NO_ERROR; 790} 791 792svn_error_t *svn_ra_svn__get_addresses(const char **local_addrport, 793 const char **remote_addrport, 794 svn_ra_svn_conn_t *conn, 795 apr_pool_t *pool) 796{ 797 if (conn->sock) 798 { 799 apr_status_t apr_err; 800 apr_sockaddr_t *local_sa, *remote_sa; 801 char *local_addr, *remote_addr; 802 803 apr_err = apr_socket_addr_get(&local_sa, APR_LOCAL, conn->sock); 804 if (apr_err) 805 return svn_error_wrap_apr(apr_err, NULL); 806 807 apr_err = apr_socket_addr_get(&remote_sa, APR_REMOTE, conn->sock); 808 if (apr_err) 809 return svn_error_wrap_apr(apr_err, NULL); 810 811 apr_err = apr_sockaddr_ip_get(&local_addr, local_sa); 812 if (apr_err) 813 return svn_error_wrap_apr(apr_err, NULL); 814 815 apr_err = apr_sockaddr_ip_get(&remote_addr, remote_sa); 816 if (apr_err) 817 return svn_error_wrap_apr(apr_err, NULL); 818 819 /* Format the IP address and port number like this: a.b.c.d;port */ 820 *local_addrport = apr_pstrcat(pool, local_addr, ";", 821 apr_itoa(pool, (int)local_sa->port), 822 SVN_VA_NULL); 823 *remote_addrport = apr_pstrcat(pool, remote_addr, ";", 824 apr_itoa(pool, (int)remote_sa->port), 825 SVN_VA_NULL); 826 } 827 return SVN_NO_ERROR; 828} 829 830svn_error_t * 831svn_ra_svn__do_cyrus_auth(svn_ra_svn__session_baton_t *sess, 832 const svn_ra_svn__list_t *mechlist, 833 const char *realm, apr_pool_t *pool) 834{ 835 apr_pool_t *subpool; 836 sasl_conn_t *sasl_ctx; 837 const char *mechstring = "", *last_err = "", *realmstring; 838 const char *local_addrport = NULL, *remote_addrport = NULL; 839 svn_boolean_t success; 840 sasl_callback_t *callbacks; 841 cred_baton_t cred_baton = { 0 }; 842 int i; 843 844 if (!sess->is_tunneled) 845 { 846 SVN_ERR(svn_ra_svn__get_addresses(&local_addrport, &remote_addrport, 847 sess->conn, pool)); 848 } 849 850 /* Prefer EXTERNAL, then ANONYMOUS, then let SASL decide. */ 851 if (svn_ra_svn__find_mech(mechlist, "EXTERNAL")) 852 mechstring = "EXTERNAL"; 853 else if (svn_ra_svn__find_mech(mechlist, "ANONYMOUS")) 854 mechstring = "ANONYMOUS"; 855 else 856 { 857 /* Create a string containing the list of mechanisms, separated by spaces. */ 858 for (i = 0; i < mechlist->nelts; i++) 859 { 860 svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(mechlist, i); 861 mechstring = apr_pstrcat(pool, 862 mechstring, 863 i == 0 ? "" : " ", 864 elt->u.word.data, SVN_VA_NULL); 865 } 866 } 867 868 realmstring = apr_psprintf(pool, "%s %s", sess->realm_prefix, realm); 869 870 /* Initialize the credential baton. */ 871 cred_baton.auth_baton = sess->auth_baton; 872 cred_baton.realmstring = realmstring; 873 cred_baton.pool = pool; 874 875 /* Reserve space for 3 callbacks (for the username, password and the 876 array terminator). These structures must persist until the 877 disposal of the SASL context at pool cleanup, however the 878 callback functions will not be invoked outside this function so 879 other structures can have a shorter lifetime. */ 880 callbacks = apr_palloc(sess->conn->pool, sizeof(*callbacks) * 3); 881 882 /* Initialize the callbacks array. */ 883 884 /* The username callback. */ 885 callbacks[0].id = SASL_CB_AUTHNAME; 886 callbacks[0].proc = (int (*)(void))get_username_cb; 887 callbacks[0].context = &cred_baton; 888 889 /* The password callback. */ 890 callbacks[1].id = SASL_CB_PASS; 891 callbacks[1].proc = (int (*)(void))get_password_cb; 892 callbacks[1].context = &cred_baton; 893 894 /* Mark the end of the array. */ 895 callbacks[2].id = SASL_CB_LIST_END; 896 callbacks[2].proc = NULL; 897 callbacks[2].context = NULL; 898 899 subpool = svn_pool_create(pool); 900 do 901 { 902 svn_error_t *err; 903 904 /* If last_err was set to a non-empty string, it needs to be duplicated 905 to the parent pool before the subpool is cleared. */ 906 if (*last_err) 907 last_err = apr_pstrdup(pool, last_err); 908 svn_pool_clear(subpool); 909 910 SVN_ERR(new_sasl_ctx(&sasl_ctx, sess->is_tunneled, 911 sess->hostname, local_addrport, remote_addrport, 912 callbacks, sess->conn->pool)); 913 err = try_auth(sess, sasl_ctx, &success, &last_err, mechstring, 914 subpool); 915 916 /* If we encountered an error while fetching credentials, that error 917 has priority. */ 918 if (cred_baton.err) 919 { 920 svn_error_clear(err); 921 return cred_baton.err; 922 } 923 if (cred_baton.no_more_creds 924 || (! err && ! success && ! cred_baton.was_used)) 925 { 926 svn_error_clear(err); 927 /* If we ran out of authentication providers, or if we got a server 928 error and our callbacks were never called, there's no point in 929 retrying authentication. Return the last error sent by the 930 server. */ 931 if (*last_err) 932 return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 933 _("Authentication error from server: %s"), 934 last_err); 935 /* Hmm, we don't have a server error. Return a generic error. */ 936 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, 937 _("Can't get username or password")); 938 } 939 if (err) 940 { 941 if (err->apr_err == SVN_ERR_RA_SVN_NO_MECHANISMS) 942 { 943 svn_error_clear(err); 944 945 /* We could not find a supported mechanism in the list sent by the 946 server. In many cases this happens because the client is missing 947 the CRAM-MD5 or ANONYMOUS plugins, in which case we can simply use 948 the built-in implementation. In all other cases this call will be 949 useless, but hey, at least we'll get consistent error messages. */ 950 return svn_error_trace(svn_ra_svn__do_internal_auth(sess, mechlist, 951 realm, pool)); 952 } 953 return err; 954 } 955 } 956 while (!success); 957 svn_pool_destroy(subpool); 958 959 SVN_ERR(svn_ra_svn__enable_sasl_encryption(sess->conn, sasl_ctx, pool)); 960 961 SVN_ERR(svn_auth_save_credentials(cred_baton.iterstate, pool)); 962 963 return SVN_NO_ERROR; 964} 965 966#endif /* SVN_HAVE_SASL */ 967