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 */
56 volatile svn_atomic_t svn_ra_svn__sasl_status = 0;
57
58 /* Initialized by svn_ra_svn__sasl_common_init(). */
59 static volatile svn_atomic_t sasl_ctx_count;
60
61 static apr_pool_t *sasl_pool = NULL;
62
63
64 /* Pool cleanup called when sasl_pool is destroyed. */
sasl_done_cb(void * data)65 static 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. */
85 static apr_array_header_t *free_mutexes = NULL;
86
87 /* A mutex to serialize access to the array. */
88 static svn_mutex__t *array_mutex = NULL;
89
90 /* Callbacks we pass to sasl_set_mutex(). */
91
92 static svn_error_t *
sasl_mutex_alloc_cb_internal(svn_mutex__t ** mutex)93 sasl_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
sasl_mutex_alloc_cb(void)103 static 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
check_result(svn_error_t * err)121 static 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
sasl_mutex_lock_cb(void * mutex)132 static 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
sasl_mutex_unlock_cb(void * mutex)139 static 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
146 static svn_error_t *
sasl_mutex_free_cb_internal(void * mutex)147 sasl_mutex_free_cb_internal(void *mutex)
148 {
149 APR_ARRAY_PUSH(free_mutexes, svn_mutex__t*) = mutex;
150 return SVN_NO_ERROR;
151 }
152
sasl_mutex_free_cb(void * mutex)153 static 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
169 svn_error_t *
svn_ra_svn__sasl_common_init(apr_pool_t * pool)170 svn_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. */
203 static const char *
get_sasl_errno_msg(int result,apr_pool_t * result_pool)204 get_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. */
219 static const char *
get_sasl_error(sasl_conn_t * sasl_ctx,int result,apr_pool_t * result_pool)220 get_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
sasl_init_cb(void * baton,apr_pool_t * pool)229 static 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
svn_ra_svn__sasl_init(void)250 svn_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
sasl_dispose_cb(void * data)257 static 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
svn_ra_svn__default_secprops(sasl_security_properties_t * secprops)266 void 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. */
289 typedef 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. */
318 static svn_boolean_t
get_credentials(cred_baton_t * baton)319 get_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. */
348 static int
get_username_cb(void * b,int id,const char ** username,size_t * len)349 get_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. */
367 static int
get_password_cb(sasl_conn_t * conn,void * b,int id,sasl_secret_t ** psecret)368 get_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. */
new_sasl_ctx(sasl_conn_t ** sasl_ctx,svn_boolean_t is_tunneled,const char * hostname,const char * local_addrport,const char * remote_addrport,sasl_callback_t * callbacks,apr_pool_t * pool)394 static 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 */
try_auth(svn_ra_svn__session_baton_t * sess,sasl_conn_t * sasl_ctx,svn_boolean_t * success,const char ** last_err,const char * mechstring,apr_pool_t * pool)445 static 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. */
591 typedef 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. */
sasl_read_cb(void * baton,char * buffer,apr_size_t * len)605 static 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. */
654 static svn_error_t *
sasl_write_cb(void * baton,const char * buffer,apr_size_t * len)655 sasl_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. */
sasl_timeout_cb(void * baton,apr_interval_time_t interval)701 static 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. */
708 static svn_error_t *
sasl_data_available_cb(void * baton,svn_boolean_t * data_available)709 sasl_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
svn_ra_svn__enable_sasl_encryption(svn_ra_svn_conn_t * conn,sasl_conn_t * sasl_ctx,apr_pool_t * pool)716 svn_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
svn_ra_svn__get_addresses(const char ** local_addrport,const char ** remote_addrport,svn_ra_svn_conn_t * conn,apr_pool_t * pool)792 svn_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
830 svn_error_t *
svn_ra_svn__do_cyrus_auth(svn_ra_svn__session_baton_t * sess,const svn_ra_svn__list_t * mechlist,const char * realm,apr_pool_t * pool)831 svn_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