]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_ra_svn/cyrus_auth.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / libsvn_ra_svn / cyrus_auth.c
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. */
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     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 *
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
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
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
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
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 *
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
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 *
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   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 *
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 *
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                       sasl_errdetail(sasl_ctx), sasl_errno_msg);
227 }
228
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 = 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          sasl_errstring(result, NULL, NULL),
244          sasl_errno_msg);
245     }
246
247   return SVN_NO_ERROR;
248 }
249
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
257 static apr_status_t sasl_dispose_cb(void *data)
258 {
259   sasl_conn_t *sasl_ctx = data;
260   sasl_dispose(&sasl_ctx);
261   if (svn_atomic_dec(&sasl_ctx_count) == 0)
262     sasl_done();
263   return APR_SUCCESS;
264 }
265
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
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
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
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. */
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 = 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                                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 = 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   sasl_setprop(*sasl_ctx, SASL_SEC_PROPS, &secprops);
440
441   return SVN_NO_ERROR;
442 }
443
444 /* Perform an authentication exchange */
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 = 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, (char *)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 = 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. */
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 = 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 *
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 = 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. */
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 ra_svn_pending_fn_t. */
708 static svn_boolean_t sasl_pending_cb(void *baton)
709 {
710   sasl_baton_t *sasl_baton = baton;
711   return svn_ra_svn__stream_pending(sasl_baton->stream);
712 }
713
714 svn_error_t *svn_ra_svn__enable_sasl_encryption(svn_ra_svn_conn_t *conn,
715                                                 sasl_conn_t *sasl_ctx,
716                                                 apr_pool_t *pool)
717 {
718   const sasl_ssf_t *ssfp;
719
720   if (! conn->encrypted)
721     {
722       int result;
723
724       /* Get the strength of the security layer. */
725       clear_sasl_errno();
726       result = sasl_getprop(sasl_ctx, SASL_SSF, (void*) &ssfp);
727       if (result != SASL_OK)
728         return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
729                                 get_sasl_error(sasl_ctx, result, pool));
730
731       if (*ssfp > 0)
732         {
733           sasl_baton_t *sasl_baton;
734           const void *maxsize;
735
736           /* Flush the connection, as we're about to replace its stream. */
737           SVN_ERR(svn_ra_svn__flush(conn, pool));
738
739           /* Create and initialize the stream baton. */
740           sasl_baton = apr_pcalloc(conn->pool, sizeof(*sasl_baton));
741           sasl_baton->ctx = sasl_ctx;
742           sasl_baton->scratch_pool = conn->pool;
743
744           /* Find out the maximum input size for sasl_encode. */
745           clear_sasl_errno();
746           result = sasl_getprop(sasl_ctx, SASL_MAXOUTBUF, &maxsize);
747           if (result != SASL_OK)
748             return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
749                                     get_sasl_error(sasl_ctx, result, pool));
750           sasl_baton->maxsize = *((const unsigned int *) maxsize);
751
752           /* If there is any data left in the read buffer at this point,
753              we need to decrypt it. */
754           if (conn->read_end > conn->read_ptr)
755             {
756               clear_sasl_errno();
757               result = sasl_decode(sasl_ctx, conn->read_ptr,
758                              (unsigned int) (conn->read_end - conn->read_ptr),
759                              &sasl_baton->read_buf, &sasl_baton->read_len);
760               if (result != SASL_OK)
761                 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
762                                         get_sasl_error(sasl_ctx, result, pool));
763               conn->read_end = conn->read_ptr;
764             }
765
766           /* Wrap the existing stream. */
767           sasl_baton->stream = conn->stream;
768
769           conn->stream = svn_ra_svn__stream_create(sasl_baton, sasl_read_cb,
770                                                    sasl_write_cb,
771                                                    sasl_timeout_cb,
772                                                    sasl_pending_cb, conn->pool);
773           /* Yay, we have a security layer! */
774           conn->encrypted = TRUE;
775         }
776     }
777   return SVN_NO_ERROR;
778 }
779
780 svn_error_t *svn_ra_svn__get_addresses(const char **local_addrport,
781                                        const char **remote_addrport,
782                                        svn_ra_svn_conn_t *conn,
783                                        apr_pool_t *pool)
784 {
785   if (conn->sock)
786     {
787       apr_status_t apr_err;
788       apr_sockaddr_t *local_sa, *remote_sa;
789       char *local_addr, *remote_addr;
790
791       apr_err = apr_socket_addr_get(&local_sa, APR_LOCAL, conn->sock);
792       if (apr_err)
793         return svn_error_wrap_apr(apr_err, NULL);
794
795       apr_err = apr_socket_addr_get(&remote_sa, APR_REMOTE, conn->sock);
796       if (apr_err)
797         return svn_error_wrap_apr(apr_err, NULL);
798
799       apr_err = apr_sockaddr_ip_get(&local_addr, local_sa);
800       if (apr_err)
801         return svn_error_wrap_apr(apr_err, NULL);
802
803       apr_err = apr_sockaddr_ip_get(&remote_addr, remote_sa);
804       if (apr_err)
805         return svn_error_wrap_apr(apr_err, NULL);
806
807       /* Format the IP address and port number like this: a.b.c.d;port */
808       *local_addrport = apr_pstrcat(pool, local_addr, ";",
809                                     apr_itoa(pool, (int)local_sa->port),
810                                     (char *)NULL);
811       *remote_addrport = apr_pstrcat(pool, remote_addr, ";",
812                                      apr_itoa(pool, (int)remote_sa->port),
813                                      (char *)NULL);
814     }
815   return SVN_NO_ERROR;
816 }
817
818 svn_error_t *
819 svn_ra_svn__do_cyrus_auth(svn_ra_svn__session_baton_t *sess,
820                           const apr_array_header_t *mechlist,
821                           const char *realm, apr_pool_t *pool)
822 {
823   apr_pool_t *subpool;
824   sasl_conn_t *sasl_ctx;
825   const char *mechstring = "", *last_err = "", *realmstring;
826   const char *local_addrport = NULL, *remote_addrport = NULL;
827   svn_boolean_t success;
828   sasl_callback_t *callbacks;
829   cred_baton_t cred_baton = { 0 };
830   int i;
831
832   if (!sess->is_tunneled)
833     {
834       SVN_ERR(svn_ra_svn__get_addresses(&local_addrport, &remote_addrport,
835                                         sess->conn, pool));
836     }
837
838   /* Prefer EXTERNAL, then ANONYMOUS, then let SASL decide. */
839   if (svn_ra_svn__find_mech(mechlist, "EXTERNAL"))
840     mechstring = "EXTERNAL";
841   else if (svn_ra_svn__find_mech(mechlist, "ANONYMOUS"))
842     mechstring = "ANONYMOUS";
843   else
844     {
845       /* Create a string containing the list of mechanisms, separated by spaces. */
846       for (i = 0; i < mechlist->nelts; i++)
847         {
848           svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(mechlist, i, svn_ra_svn_item_t);
849           mechstring = apr_pstrcat(pool,
850                                    mechstring,
851                                    i == 0 ? "" : " ",
852                                    elt->u.word, (char *)NULL);
853         }
854     }
855
856   realmstring = apr_psprintf(pool, "%s %s", sess->realm_prefix, realm);
857
858   /* Initialize the credential baton. */
859   cred_baton.auth_baton = sess->callbacks->auth_baton;
860   cred_baton.realmstring = realmstring;
861   cred_baton.pool = pool;
862
863   /* Reserve space for 3 callbacks (for the username, password and the
864      array terminator).  These structures must persist until the
865      disposal of the SASL context at pool cleanup, however the
866      callback functions will not be invoked outside this function so
867      other structures can have a shorter lifetime. */
868   callbacks = apr_palloc(sess->conn->pool, sizeof(*callbacks) * 3);
869
870   /* Initialize the callbacks array. */
871
872   /* The username callback. */
873   callbacks[0].id = SASL_CB_AUTHNAME;
874   callbacks[0].proc = (int (*)(void))get_username_cb;
875   callbacks[0].context = &cred_baton;
876
877   /* The password callback. */
878   callbacks[1].id = SASL_CB_PASS;
879   callbacks[1].proc = (int (*)(void))get_password_cb;
880   callbacks[1].context = &cred_baton;
881
882   /* Mark the end of the array. */
883   callbacks[2].id = SASL_CB_LIST_END;
884   callbacks[2].proc = NULL;
885   callbacks[2].context = NULL;
886
887   subpool = svn_pool_create(pool);
888   do
889     {
890       svn_error_t *err;
891
892       /* If last_err was set to a non-empty string, it needs to be duplicated
893          to the parent pool before the subpool is cleared. */
894       if (*last_err)
895         last_err = apr_pstrdup(pool, last_err);
896       svn_pool_clear(subpool);
897
898       SVN_ERR(new_sasl_ctx(&sasl_ctx, sess->is_tunneled,
899                            sess->hostname, local_addrport, remote_addrport,
900                            callbacks, sess->conn->pool));
901       err = try_auth(sess, sasl_ctx, &success, &last_err, mechstring,
902                      subpool);
903
904       /* If we encountered an error while fetching credentials, that error
905          has priority. */
906       if (cred_baton.err)
907         {
908           svn_error_clear(err);
909           return cred_baton.err;
910         }
911       if (cred_baton.no_more_creds
912           || (! err && ! success && ! cred_baton.was_used))
913         {
914           svn_error_clear(err);
915           /* If we ran out of authentication providers, or if we got a server
916              error and our callbacks were never called, there's no point in
917              retrying authentication.  Return the last error sent by the
918              server. */
919           if (*last_err)
920             return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
921                                      _("Authentication error from server: %s"),
922                                      last_err);
923           /* Hmm, we don't have a server error. Return a generic error. */
924           return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
925                                   _("Can't get username or password"));
926         }
927       if (err)
928         {
929           if (err->apr_err == SVN_ERR_RA_SVN_NO_MECHANISMS)
930             {
931               svn_error_clear(err);
932
933               /* We could not find a supported mechanism in the list sent by the
934                  server. In many cases this happens because the client is missing
935                  the CRAM-MD5 or ANONYMOUS plugins, in which case we can simply use
936                  the built-in implementation. In all other cases this call will be
937                  useless, but hey, at least we'll get consistent error messages. */
938               return svn_ra_svn__do_internal_auth(sess, mechlist,
939                                                   realm, pool);
940             }
941           return err;
942         }
943     }
944   while (!success);
945   svn_pool_destroy(subpool);
946
947   SVN_ERR(svn_ra_svn__enable_sasl_encryption(sess->conn, sasl_ctx, pool));
948
949   SVN_ERR(svn_auth_save_credentials(cred_baton.iterstate, pool));
950
951   return SVN_NO_ERROR;
952 }
953
954 #endif /* SVN_HAVE_SASL */