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