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