In the command-line client, forbid
[svn.git] / subversion / libsvn_ra_svn / cyrus_auth.c
blob82d72974862a14d3379024bda76843f76943984e
1 /*
2 * cyrus_auth.c : functions for Cyrus SASL-based authentication
4 * ====================================================================
5 * Copyright (c) 2006-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 #include "svn_private_config.h"
20 #ifdef SVN_HAVE_SASL
22 #define APR_WANT_STRFUNC
23 #include <apr_want.h>
24 #include <apr_general.h>
25 #include <apr_strings.h>
26 #include <apr_atomic.h>
27 #include <apr_thread_mutex.h>
28 #include <apr_version.h>
30 #include "svn_types.h"
31 #include "svn_string.h"
32 #include "svn_error.h"
33 #include "svn_pools.h"
34 #include "svn_ra.h"
35 #include "svn_ra_svn.h"
36 #include "svn_base64.h"
38 #include "private/svn_atomic.h"
39 #include "private/ra_svn_sasl.h"
41 #include "ra_svn.h"
43 /* Note: In addition to being used via svn_atomic__init_once to control
44 * initialization of the SASL code this will also be referenced in
45 * the various functions that work with sasl mutexes to determine
46 * if the sasl pool has been destroyed. This should be safe, since
47 * it is only set back to zero in the sasl pool's cleanups, which
48 * only happens during apr_terminate, which we assume is occurring
49 * in atexit processing, at which point we are already running in
50 * single threaded mode.
52 volatile svn_atomic_t svn_ra_svn__sasl_status;
54 static volatile svn_atomic_t sasl_ctx_count;
56 static apr_pool_t *sasl_pool = NULL;
59 /* Pool cleanup called when sasl_pool is destroyed. */
60 static apr_status_t sasl_done_cb(void *data)
62 /* Reset svn_ra_svn__sasl_status, in case the client calls
63 apr_initialize()/apr_terminate() more than once. */
64 svn_ra_svn__sasl_status = 0;
65 if (svn_atomic_dec(&sasl_ctx_count) == 0)
66 sasl_done();
67 return APR_SUCCESS;
70 #if APR_HAS_THREADS
71 /* Cyrus SASL is thread-safe only if we supply it with mutex functions
72 * (with sasl_set_mutex()). To make this work with APR, we need to use the
73 * global sasl_pool for the mutex allocations. Freeing a mutex actually
74 * returns it to a global array. We allocate mutexes from this
75 * array if it is non-empty, or directly from the pool otherwise.
76 * We also need a mutex to serialize accesses to the array itself.
79 /* An array of allocated, but unused, apr_thread_mutex_t's. */
80 static apr_array_header_t *free_mutexes = NULL;
82 /* A mutex to serialize access to the array. */
83 static apr_thread_mutex_t *array_mutex = NULL;
85 /* Callbacks we pass to sasl_set_mutex(). */
87 static void *sasl_mutex_alloc_cb(void)
89 apr_thread_mutex_t *mutex;
90 apr_status_t apr_err;
92 if (!svn_ra_svn__sasl_status)
93 return NULL;
95 apr_err = apr_thread_mutex_lock(array_mutex);
96 if (apr_err != APR_SUCCESS)
97 return NULL;
99 if (apr_is_empty_array(free_mutexes))
101 apr_err = apr_thread_mutex_create(&mutex,
102 APR_THREAD_MUTEX_DEFAULT,
103 sasl_pool);
104 if (apr_err != APR_SUCCESS)
105 mutex = NULL;
107 else
108 mutex = *((apr_thread_mutex_t**)apr_array_pop(free_mutexes));
110 apr_err = apr_thread_mutex_unlock(array_mutex);
111 if (apr_err != APR_SUCCESS)
112 return NULL;
114 return mutex;
117 static int sasl_mutex_lock_cb(void *mutex)
119 if (!svn_ra_svn__sasl_status)
120 return 0;
121 return (apr_thread_mutex_lock(mutex) == APR_SUCCESS) ? 0 : -1;
124 static int sasl_mutex_unlock_cb(void *mutex)
126 if (!svn_ra_svn__sasl_status)
127 return 0;
128 return (apr_thread_mutex_unlock(mutex) == APR_SUCCESS) ? 0 : -1;
131 static void sasl_mutex_free_cb(void *mutex)
133 if (svn_ra_svn__sasl_status)
135 apr_status_t apr_err = apr_thread_mutex_lock(array_mutex);
136 if (apr_err == APR_SUCCESS)
138 APR_ARRAY_PUSH(free_mutexes, apr_thread_mutex_t*) = mutex;
139 apr_thread_mutex_unlock(array_mutex);
143 #endif /* APR_HAS_THREADS */
145 apr_status_t svn_ra_svn__sasl_common_init(apr_pool_t *pool)
147 apr_status_t apr_err = APR_SUCCESS;
149 sasl_pool = svn_pool_create(pool);
150 sasl_ctx_count = 1;
151 apr_pool_cleanup_register(sasl_pool, NULL, sasl_done_cb,
152 apr_pool_cleanup_null);
153 #if APR_HAS_THREADS
154 sasl_set_mutex(sasl_mutex_alloc_cb,
155 sasl_mutex_lock_cb,
156 sasl_mutex_unlock_cb,
157 sasl_mutex_free_cb);
158 free_mutexes = apr_array_make(sasl_pool, 0, sizeof(apr_thread_mutex_t *));
159 apr_err = apr_thread_mutex_create(&array_mutex,
160 APR_THREAD_MUTEX_DEFAULT,
161 sasl_pool);
162 #endif /* APR_HAS_THREADS */
163 return apr_err;
166 static svn_error_t *sasl_init_cb(apr_pool_t *pool)
168 if (svn_ra_svn__sasl_common_init(pool) != APR_SUCCESS
169 || sasl_client_init(NULL) != SASL_OK)
170 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
171 _("Could not initialize the SASL library"));
172 return SVN_NO_ERROR;
175 svn_error_t *svn_ra_svn__sasl_init(void)
177 SVN_ERR(svn_atomic__init_once(&svn_ra_svn__sasl_status, sasl_init_cb, NULL));
178 return SVN_NO_ERROR;
181 static apr_status_t sasl_dispose_cb(void *data)
183 sasl_conn_t *sasl_ctx = data;
184 sasl_dispose(&sasl_ctx);
185 if (svn_atomic_dec(&sasl_ctx_count) == 0)
186 sasl_done();
187 return APR_SUCCESS;
190 void svn_ra_svn__default_secprops(sasl_security_properties_t *secprops)
192 /* The minimum and maximum security strength factors that the chosen
193 SASL mechanism should provide. 0 means 'no encryption', 256 means
194 '256-bit encryption', which is about the best that any SASL
195 mechanism can provide. Using these values effectively means 'use
196 whatever encryption the other side wants'. Note that SASL will try
197 to use better encryption whenever possible, so if both the server and
198 the client use these values the highest possible encryption strength
199 will be used. */
200 secprops->min_ssf = 0;
201 secprops->max_ssf = 256;
203 /* Set maxbufsize to the maximum amount of data we can read at any one time.
204 This value needs to be commmunicated to the peer if a security layer
205 is negotiated. */
206 secprops->maxbufsize = SVN_RA_SVN__READBUF_SIZE;
208 secprops->security_flags = 0;
209 secprops->property_names = secprops->property_values = NULL;
212 /* A baton type used by the SASL username and password callbacks. */
213 typedef struct cred_baton {
214 svn_auth_baton_t *auth_baton;
215 svn_auth_iterstate_t *iterstate;
216 const char *realmstring;
218 /* Unfortunately SASL uses two separate callbacks for the username and
219 password, but we must fetch both of them at the same time. So we cache
220 their values in the baton, set them to NULL individually when SASL
221 demands them, and fetch the next pair when both are NULL. */
222 const char *username;
223 const char *password;
225 /* Any errors we receive from svn_auth_{first,next}_credentials
226 are saved here. */
227 svn_error_t *err;
229 /* This flag is set when we run out of credential providers. */
230 svn_boolean_t no_more_creds;
232 /* Were the auth callbacks ever called? */
233 svn_boolean_t was_used;
235 apr_pool_t *pool;
236 } cred_baton_t;
238 /* Call svn_auth_{first,next}_credentials. If successful, set BATON->username
239 and BATON->password to the new username and password and return TRUE,
240 otherwise return FALSE. If there are no more credentials, set
241 BATON->no_more_creds to TRUE. Any errors are saved in BATON->err. */
242 static svn_boolean_t
243 get_credentials(cred_baton_t *baton)
245 void *creds;
247 if (baton->iterstate)
248 baton->err = svn_auth_next_credentials(&creds, baton->iterstate,
249 baton->pool);
250 else
251 baton->err = svn_auth_first_credentials(&creds, &baton->iterstate,
252 SVN_AUTH_CRED_SIMPLE,
253 baton->realmstring,
254 baton->auth_baton, baton->pool);
255 if (baton->err)
256 return FALSE;
258 if (! creds)
260 baton->no_more_creds = TRUE;
261 return FALSE;
264 baton->username = ((svn_auth_cred_simple_t *)creds)->username;
265 baton->password = ((svn_auth_cred_simple_t *)creds)->password;
266 baton->was_used = TRUE;
268 return TRUE;
271 /* The username callback. Implements the sasl_getsimple_t interface. */
272 static int
273 get_username_cb(void *b, int id, const char **username, unsigned *len)
275 cred_baton_t *baton = b;
277 if (baton->username || get_credentials(baton))
279 *username = baton->username;
280 if (len)
281 *len = strlen(baton->username);
282 baton->username = NULL;
284 return SASL_OK;
287 return SASL_FAIL;
290 /* The password callback. Implements the sasl_getsecret_t interface. */
291 static int
292 get_password_cb(sasl_conn_t *conn, void *b, int id, sasl_secret_t **psecret)
294 cred_baton_t *baton = b;
296 if (baton->password || get_credentials(baton))
298 sasl_secret_t *secret;
299 int len = strlen(baton->password);
301 /* sasl_secret_t is a struct with a variable-sized array as a final
302 member, which means we need to allocate len-1 supplementary bytes
303 (one byte is part of sasl_secret_t, and we don't need a NULL
304 terminator). */
305 secret = apr_palloc(baton->pool, sizeof(*secret) + len - 1);
306 secret->len = len;
307 memcpy(secret->data, baton->password, len);
308 baton->password = NULL;
309 *psecret = secret;
311 return SASL_OK;
314 return SASL_FAIL;
317 /* Create a new SASL context. */
318 static svn_error_t *new_sasl_ctx(sasl_conn_t **sasl_ctx,
319 svn_boolean_t is_tunneled,
320 const char *hostname,
321 const char *local_addrport,
322 const char *remote_addrport,
323 sasl_callback_t *callbacks,
324 apr_pool_t *pool)
326 sasl_security_properties_t secprops;
327 int result;
329 result = sasl_client_new("svn", hostname, local_addrport, remote_addrport,
330 callbacks, SASL_SUCCESS_DATA,
331 sasl_ctx);
332 if (result != SASL_OK)
333 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
334 sasl_errstring(result, NULL, NULL));
336 svn_atomic_inc(&sasl_ctx_count);
337 apr_pool_cleanup_register(pool, *sasl_ctx, sasl_dispose_cb,
338 apr_pool_cleanup_null);
340 if (is_tunneled)
342 /* We need to tell SASL that this connection is tunneled,
343 otherwise it will ignore EXTERNAL. The third paramater
344 should be the username, but since SASL doesn't seem
345 to use it on the client side, any non-empty string will do. */
346 result = sasl_setprop(*sasl_ctx,
347 SASL_AUTH_EXTERNAL, " ");
348 if (result != SASL_OK)
349 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
350 sasl_errdetail(*sasl_ctx));
353 /* Set security properties. Don't allow PLAIN or LOGIN, since we
354 don't support TLS yet. */
355 svn_ra_svn__default_secprops(&secprops);
356 secprops.security_flags = SASL_SEC_NOPLAINTEXT;
357 sasl_setprop(*sasl_ctx, SASL_SEC_PROPS, &secprops);
359 return SVN_NO_ERROR;
362 /* Perform an authentication exchange */
363 static svn_error_t *try_auth(svn_ra_svn__session_baton_t *sess,
364 sasl_conn_t *sasl_ctx,
365 svn_boolean_t *success,
366 const char **last_err,
367 const char *mechstring,
368 apr_pool_t *pool)
370 sasl_interact_t *client_interact = NULL;
371 const char *out, *mech, *status = NULL;
372 const svn_string_t *arg = NULL, *in;
373 int result;
374 unsigned int outlen;
375 svn_boolean_t again;
379 again = FALSE;
380 result = sasl_client_start(sasl_ctx,
381 mechstring,
382 &client_interact,
383 &out,
384 &outlen,
385 &mech);
386 switch (result)
388 case SASL_OK:
389 case SASL_CONTINUE:
390 /* Success. */
391 break;
392 case SASL_NOMECH:
393 return svn_error_create(SVN_ERR_RA_SVN_NO_MECHANISMS, NULL, NULL);
394 case SASL_BADPARAM:
395 case SASL_NOMEM:
396 /* Fatal error. Fail the authentication. */
397 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
398 sasl_errdetail(sasl_ctx));
399 default:
400 /* For anything else, delete the mech from the list
401 and try again. */
403 char *dst = strstr(mechstring, mech);
404 char *src = dst + strlen(mech);
405 while ((*dst++ = *src++) != '\0')
407 again = TRUE;
411 while (again);
413 /* Prepare the initial authentication token. */
414 if (outlen > 0 || strcmp(mech, "EXTERNAL") == 0)
415 arg = svn_base64_encode_string(svn_string_ncreate(out, outlen, pool),
416 pool);
418 /* Send the initial client response */
419 SVN_ERR(svn_ra_svn__auth_response(sess->conn, pool, mech,
420 arg ? arg->data : NULL));
422 while (result == SASL_CONTINUE)
424 /* Read the server response */
425 SVN_ERR(svn_ra_svn_read_tuple(sess->conn, pool, "w(?s)",
426 &status, &in));
428 if (strcmp(status, "failure") == 0)
430 /* Authentication failed. Use the next set of credentials */
431 *success = FALSE;
432 /* Remember the message sent by the server because we'll want to
433 return a meaningful error if we run out of auth providers. */
434 *last_err = in ? in->data : "";
435 return SVN_NO_ERROR;
438 if ((strcmp(status, "success") != 0 && strcmp(status, "step") != 0)
439 || in == NULL)
440 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
441 _("Unexpected server response"
442 " to authentication"));
444 /* If the mech is CRAM-MD5 we don't base64-decode the server response. */
445 if (strcmp(mech, "CRAM-MD5") != 0)
446 in = svn_base64_decode_string(in, pool);
448 result = sasl_client_step(sasl_ctx,
449 in->data,
450 in->len,
451 &client_interact,
452 &out, /* Filled in by SASL. */
453 &outlen);
455 if (result != SASL_OK && result != SASL_CONTINUE)
456 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
457 sasl_errdetail(sasl_ctx));
459 if (outlen > 0)
461 arg = svn_string_ncreate(out, outlen, pool);
462 /* Write our response. */
463 /* For CRAM-MD5, we don't use base64-encoding. */
464 if (strcmp(mech, "CRAM-MD5") != 0)
465 arg = svn_base64_encode_string(arg, pool);
466 SVN_ERR(svn_ra_svn_write_cstring(sess->conn, pool, arg->data));
470 if (!status || strcmp(status, "step") == 0)
472 /* This is a client-send-last mech. Read the last server response. */
473 SVN_ERR(svn_ra_svn_read_tuple(sess->conn, pool, "w(?s)",
474 &status, &in));
476 if (strcmp(status, "failure") == 0)
478 *success = FALSE;
479 *last_err = in ? in->data : "";
481 else if (strcmp(status, "success") == 0)
483 /* We're done */
484 *success = TRUE;
486 else
487 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
488 _("Unexpected server response"
489 " to authentication"));
491 else
492 *success = TRUE;
493 return SVN_NO_ERROR;
496 /* Baton for a SASL encrypted svn_ra_svn__stream_t. */
497 typedef struct sasl_baton {
498 svn_ra_svn__stream_t *stream; /* Inherited stream. */
499 sasl_conn_t *ctx; /* The SASL context for this connection. */
500 unsigned int maxsize; /* The maximum amount of data we can encode. */
501 const char *read_buf; /* The buffer returned by sasl_decode. */
502 unsigned int read_len; /* Its current length. */
503 const char *write_buf; /* The buffer returned by sasl_encode. */
504 unsigned int write_len; /* Its length. */
505 } sasl_baton_t;
507 /* Functions to implement a SASL encrypted svn_ra_svn__stream_t. */
509 /* Implements svn_read_fn_t. */
510 static svn_error_t *sasl_read_cb(void *baton, char *buffer, apr_size_t *len)
512 sasl_baton_t *sasl_baton = baton;
513 int result;
514 /* A copy of *len, used by the wrapped stream. */
515 apr_size_t len2 = *len;
517 /* sasl_decode might need more data than a single read can provide,
518 hence the need to put a loop around the decoding. */
519 while (! sasl_baton->read_buf || sasl_baton->read_len == 0)
521 SVN_ERR(svn_ra_svn__stream_read(sasl_baton->stream, buffer, &len2));
522 if (len2 == 0)
524 *len = 0;
525 return SVN_NO_ERROR;
527 result = sasl_decode(sasl_baton->ctx, buffer, len2,
528 &sasl_baton->read_buf,
529 &sasl_baton->read_len);
530 if (result != SASL_OK)
531 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
532 sasl_errdetail(sasl_baton->ctx));
535 /* The buffer returned by sasl_decode might be larger than what the
536 caller wants. If this is the case, we only copy back *len bytes now
537 (the rest will be returned by subsequent calls to this function).
538 If not, we just copy back the whole thing. */
539 if (*len >= sasl_baton->read_len)
541 memcpy(buffer, sasl_baton->read_buf, sasl_baton->read_len);
542 *len = sasl_baton->read_len;
543 sasl_baton->read_buf = NULL;
544 sasl_baton->read_len = 0;
546 else
548 memcpy(buffer, sasl_baton->read_buf, *len);
549 sasl_baton->read_len -= *len;
550 sasl_baton->read_buf += *len;
553 return SVN_NO_ERROR;
556 /* Implements svn_write_fn_t. */
557 static svn_error_t *
558 sasl_write_cb(void *baton, const char *buffer, apr_size_t *len)
560 sasl_baton_t *sasl_baton = baton;
561 int result;
563 if (! sasl_baton->write_buf || sasl_baton->write_len == 0)
565 /* Make sure we don't write too much. */
566 *len = (*len > sasl_baton->maxsize) ? sasl_baton->maxsize : *len;
567 result = sasl_encode(sasl_baton->ctx, buffer, *len,
568 &sasl_baton->write_buf,
569 &sasl_baton->write_len);
571 if (result != SASL_OK)
572 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
573 sasl_errdetail(sasl_baton->ctx));
578 apr_size_t tmplen = sasl_baton->write_len;
579 SVN_ERR(svn_ra_svn__stream_write(sasl_baton->stream,
580 sasl_baton->write_buf,
581 &tmplen));
582 if (tmplen == 0)
584 /* The output buffer and its length will be preserved in sasl_baton
585 and will be written out during the next call to this function
586 (which will have the same arguments). */
587 *len = 0;
588 return SVN_NO_ERROR;
590 sasl_baton->write_len -= tmplen;
591 sasl_baton->write_buf += tmplen;
593 while (sasl_baton->write_len > 0);
595 sasl_baton->write_buf = NULL;
596 sasl_baton->write_len = 0;
598 return SVN_NO_ERROR;
601 /* Implements ra_svn_timeout_fn_t. */
602 static void sasl_timeout_cb(void *baton, apr_interval_time_t interval)
604 sasl_baton_t *sasl_baton = baton;
605 svn_ra_svn__stream_timeout(sasl_baton->stream, interval);
608 /* Implements ra_svn_pending_fn_t. */
609 static svn_boolean_t sasl_pending_cb(void *baton)
611 sasl_baton_t *sasl_baton = baton;
612 return svn_ra_svn__stream_pending(sasl_baton->stream);
615 svn_error_t *svn_ra_svn__enable_sasl_encryption(svn_ra_svn_conn_t *conn,
616 sasl_conn_t *sasl_ctx,
617 apr_pool_t *pool)
619 sasl_baton_t *sasl_baton;
620 const sasl_ssf_t *ssfp;
621 int result;
622 const void *maxsize;
624 if (! conn->encrypted)
626 /* Get the strength of the security layer. */
627 result = sasl_getprop(sasl_ctx, SASL_SSF, (void*) &ssfp);
628 if (result != SASL_OK)
629 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
630 sasl_errdetail(sasl_ctx));
632 if (*ssfp > 0)
634 /* Flush the connection, as we're about to replace its stream. */
635 SVN_ERR(svn_ra_svn_flush(conn, pool));
637 /* Create and initialize the stream baton. */
638 sasl_baton = apr_pcalloc(conn->pool, sizeof(*sasl_baton));
639 sasl_baton->ctx = sasl_ctx;
641 /* Find out the maximum input size for sasl_encode. */
642 result = sasl_getprop(sasl_ctx, SASL_MAXOUTBUF, &maxsize);
643 if (result != SASL_OK)
644 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
645 sasl_errdetail(sasl_ctx));
646 sasl_baton->maxsize = *((unsigned int *) maxsize);
648 /* If there is any data left in the read buffer at this point,
649 we need to decrypt it. */
650 if (conn->read_end > conn->read_ptr)
652 result = sasl_decode(sasl_ctx, conn->read_ptr,
653 conn->read_end - conn->read_ptr,
654 &sasl_baton->read_buf,
655 &sasl_baton->read_len);
656 if (result != SASL_OK)
657 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
658 sasl_errdetail(sasl_ctx));
659 conn->read_end = conn->read_ptr;
662 /* Wrap the existing stream. */
663 sasl_baton->stream = conn->stream;
665 conn->stream = svn_ra_svn__stream_create(sasl_baton, sasl_read_cb,
666 sasl_write_cb,
667 sasl_timeout_cb,
668 sasl_pending_cb, conn->pool);
669 /* Yay, we have a security layer! */
670 conn->encrypted = TRUE;
673 return SVN_NO_ERROR;
676 svn_error_t *svn_ra_svn__get_addresses(const char **local_addrport,
677 const char **remote_addrport,
678 svn_ra_svn_conn_t *conn,
679 apr_pool_t *pool)
681 if (conn->sock)
683 apr_status_t apr_err;
684 apr_sockaddr_t *local_sa, *remote_sa;
685 char *local_addr, *remote_addr;
687 apr_err = apr_socket_addr_get(&local_sa, APR_LOCAL, conn->sock);
688 if (apr_err)
689 return svn_error_wrap_apr(apr_err, NULL);
691 apr_err = apr_socket_addr_get(&remote_sa, APR_REMOTE, conn->sock);
692 if (apr_err)
693 return svn_error_wrap_apr(apr_err, NULL);
695 apr_err = apr_sockaddr_ip_get(&local_addr, local_sa);
696 if (apr_err)
697 return svn_error_wrap_apr(apr_err, NULL);
699 apr_err = apr_sockaddr_ip_get(&remote_addr, remote_sa);
700 if (apr_err)
701 return svn_error_wrap_apr(apr_err, NULL);
703 /* Format the IP address and port number like this: a.b.c.d;port */
704 *local_addrport = apr_pstrcat(pool, local_addr, ";",
705 apr_itoa(pool, (int)local_sa->port), NULL);
706 *remote_addrport = apr_pstrcat(pool, remote_addr, ";",
707 apr_itoa(pool, (int)remote_sa->port), NULL);
709 return SVN_NO_ERROR;
712 svn_error_t *
713 svn_ra_svn__do_cyrus_auth(svn_ra_svn__session_baton_t *sess,
714 apr_array_header_t *mechlist,
715 const char *realm, apr_pool_t *pool)
717 apr_pool_t *subpool;
718 sasl_conn_t *sasl_ctx;
719 const char *mechstring = "", *last_err = "", *realmstring;
720 const char *local_addrport = NULL, *remote_addrport = NULL;
721 svn_boolean_t success;
722 /* Reserve space for 3 callbacks (for the username, password and the
723 array terminator). */
724 sasl_callback_t callbacks[3];
725 cred_baton_t cred_baton;
726 int i;
728 if (!sess->is_tunneled)
730 SVN_ERR(svn_ra_svn__get_addresses(&local_addrport, &remote_addrport,
731 sess->conn, pool));
734 /* Create a string containing the list of mechanisms, separated by spaces. */
735 for (i = 0; i < mechlist->nelts; i++)
737 svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(mechlist, i, svn_ra_svn_item_t);
739 /* Force the client to use ANONYMOUS or EXTERNAL if they are available.*/
740 if (strcmp(elt->u.word, "ANONYMOUS") == 0
741 || strcmp(elt->u.word, "EXTERNAL") == 0)
743 mechstring = elt->u.word;
744 break;
746 mechstring = apr_pstrcat(pool,
747 mechstring,
748 i == 0 ? "" : " ",
749 elt->u.word, NULL);
752 realmstring = apr_psprintf(pool, "%s %s", sess->realm_prefix, realm);
754 /* Initialize the credential baton. */
755 memset(&cred_baton, 0, sizeof(cred_baton));
756 cred_baton.auth_baton = sess->callbacks->auth_baton;
757 cred_baton.realmstring = realmstring;
758 cred_baton.pool = pool;
760 /* Initialize the callbacks array. */
762 /* The username callback. */
763 callbacks[0].id = SASL_CB_AUTHNAME;
764 callbacks[0].proc = get_username_cb;
765 callbacks[0].context = &cred_baton;
767 /* The password callback. */
768 callbacks[1].id = SASL_CB_PASS;
769 callbacks[1].proc = get_password_cb;
770 callbacks[1].context = &cred_baton;
772 /* Mark the end of the array. */
773 callbacks[2].id = SASL_CB_LIST_END;
774 callbacks[2].proc = NULL;
775 callbacks[2].context = NULL;
777 subpool = svn_pool_create(pool);
780 svn_error_t *err;
782 /* If last_err was set to a non-empty string, it needs to be duplicated
783 to the parent pool before the subpool is cleared. */
784 if (*last_err)
785 last_err = apr_pstrdup(pool, last_err);
786 svn_pool_clear(subpool);
788 SVN_ERR(new_sasl_ctx(&sasl_ctx, sess->is_tunneled,
789 sess->hostname, local_addrport, remote_addrport,
790 callbacks, sess->conn->pool));
791 err = try_auth(sess, sasl_ctx, &success, &last_err, mechstring,
792 subpool);
794 /* If we encountered an error while fetching credentials, that error
795 has priority. */
796 if (cred_baton.err)
798 svn_error_clear(err);
799 return cred_baton.err;
801 if (cred_baton.no_more_creds
802 || (! success && ! err && ! cred_baton.was_used))
804 svn_error_clear(err);
805 /* If we ran out of authentication providers, or if we got a server
806 error and our callbacks were never called, there's no point in
807 retrying authentication. Return the last error sent by the
808 server. */
809 if (*last_err)
810 return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
811 _("Authentication error from server: %s"),
812 last_err);
813 /* Hmm, we don't have a server error. Return a generic error. */
814 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
815 _("Can't get username or password"));
817 if (err)
819 if (err->apr_err == SVN_ERR_RA_SVN_NO_MECHANISMS)
821 svn_error_clear(err);
823 /* We could not find a supported mechanism in the list sent by the
824 server. In many cases this happens because the client is missing
825 the CRAM-MD5 or ANONYMOUS plugins, in which case we can simply use
826 the built-in implementation. In all other cases this call will be
827 useless, but hey, at least we'll get consistent error messages. */
828 return svn_ra_svn__do_internal_auth(sess, mechlist,
829 realm, pool);
831 return err;
834 while (!success);
835 svn_pool_destroy(subpool);
837 SVN_ERR(svn_ra_svn__enable_sasl_encryption(sess->conn, sasl_ctx, pool));
839 SVN_ERR(svn_auth_save_credentials(cred_baton.iterstate, pool));
841 return SVN_NO_ERROR;
844 #endif /* SVN_HAVE_SASL */