1251881Speter/*
2251881Speter * cyrus_auth.c :  functions for Cyrus SASL-based authentication
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter#include "svn_private_config.h"
25251881Speter#ifdef SVN_HAVE_SASL
26251881Speter
27251881Speter#define APR_WANT_STRFUNC
28251881Speter#include <apr_want.h>
29251881Speter#include <apr_general.h>
30251881Speter#include <apr_strings.h>
31251881Speter#include <apr_version.h>
32251881Speter
33251881Speter#include "svn_types.h"
34251881Speter#include "svn_string.h"
35251881Speter#include "svn_error.h"
36251881Speter#include "svn_pools.h"
37251881Speter#include "svn_ra.h"
38251881Speter#include "svn_ra_svn.h"
39251881Speter#include "svn_base64.h"
40251881Speter
41251881Speter#include "private/svn_atomic.h"
42251881Speter#include "private/ra_svn_sasl.h"
43251881Speter#include "private/svn_mutex.h"
44251881Speter
45251881Speter#include "ra_svn.h"
46251881Speter
47251881Speter/* Note: In addition to being used via svn_atomic__init_once to control
48251881Speter *       initialization of the SASL code this will also be referenced in
49251881Speter *       the various functions that work with sasl mutexes to determine
50251881Speter *       if the sasl pool has been destroyed.  This should be safe, since
51251881Speter *       it is only set back to zero in the sasl pool's cleanups, which
52251881Speter *       only happens during apr_terminate, which we assume is occurring
53251881Speter *       in atexit processing, at which point we are already running in
54251881Speter *       single threaded mode.
55251881Speter */
56251881Spetervolatile svn_atomic_t svn_ra_svn__sasl_status = 0;
57251881Speter
58251881Speter/* Initialized by svn_ra_svn__sasl_common_init(). */
59251881Speterstatic volatile svn_atomic_t sasl_ctx_count;
60251881Speter
61251881Speterstatic apr_pool_t *sasl_pool = NULL;
62251881Speter
63251881Speter
64251881Speter/* Pool cleanup called when sasl_pool is destroyed. */
65251881Speterstatic apr_status_t sasl_done_cb(void *data)
66251881Speter{
67251881Speter  /* Reset svn_ra_svn__sasl_status, in case the client calls
68251881Speter     apr_initialize()/apr_terminate() more than once. */
69251881Speter  svn_ra_svn__sasl_status = 0;
70251881Speter  if (svn_atomic_dec(&sasl_ctx_count) == 0)
71251881Speter    sasl_done();
72251881Speter  return APR_SUCCESS;
73251881Speter}
74251881Speter
75251881Speter#if APR_HAS_THREADS
76251881Speter/* Cyrus SASL is thread-safe only if we supply it with mutex functions
77251881Speter * (with sasl_set_mutex()).  To make this work with APR, we need to use the
78251881Speter * global sasl_pool for the mutex allocations.  Freeing a mutex actually
79251881Speter * returns it to a global array.  We allocate mutexes from this
80251881Speter * array if it is non-empty, or directly from the pool otherwise.
81251881Speter * We also need a mutex to serialize accesses to the array itself.
82251881Speter */
83251881Speter
84251881Speter/* An array of allocated, but unused, apr_thread_mutex_t's. */
85251881Speterstatic apr_array_header_t *free_mutexes = NULL;
86251881Speter
87251881Speter/* A mutex to serialize access to the array. */
88251881Speterstatic svn_mutex__t *array_mutex = NULL;
89251881Speter
90251881Speter/* Callbacks we pass to sasl_set_mutex(). */
91251881Speter
92251881Speterstatic svn_error_t *
93251881Spetersasl_mutex_alloc_cb_internal(svn_mutex__t **mutex)
94251881Speter{
95251881Speter  if (apr_is_empty_array(free_mutexes))
96251881Speter    return svn_mutex__init(mutex, TRUE, sasl_pool);
97251881Speter  else
98251881Speter    *mutex = *((svn_mutex__t**)apr_array_pop(free_mutexes));
99251881Speter
100251881Speter  return SVN_NO_ERROR;
101251881Speter}
102251881Speter
103251881Speterstatic void *sasl_mutex_alloc_cb(void)
104251881Speter{
105251881Speter  svn_mutex__t *mutex = NULL;
106251881Speter  svn_error_t *err;
107251881Speter
108251881Speter  if (!svn_ra_svn__sasl_status)
109251881Speter    return NULL;
110251881Speter
111251881Speter  err = svn_mutex__lock(array_mutex);
112251881Speter  if (err)
113251881Speter    svn_error_clear(err);
114251881Speter  else
115251881Speter    svn_error_clear(svn_mutex__unlock(array_mutex,
116251881Speter                                      sasl_mutex_alloc_cb_internal(&mutex)));
117251881Speter
118251881Speter  return mutex;
119251881Speter}
120251881Speter
121251881Speterstatic int check_result(svn_error_t *err)
122251881Speter{
123251881Speter  if (err)
124251881Speter    {
125251881Speter      svn_error_clear(err);
126251881Speter      return -1;
127251881Speter    }
128251881Speter
129251881Speter  return 0;
130251881Speter}
131251881Speter
132251881Speterstatic int sasl_mutex_lock_cb(void *mutex)
133251881Speter{
134251881Speter  if (!svn_ra_svn__sasl_status)
135251881Speter    return 0;
136251881Speter  return check_result(svn_mutex__lock(mutex));
137251881Speter}
138251881Speter
139251881Speterstatic int sasl_mutex_unlock_cb(void *mutex)
140251881Speter{
141251881Speter  if (!svn_ra_svn__sasl_status)
142251881Speter    return 0;
143251881Speter  return check_result(svn_mutex__unlock(mutex, SVN_NO_ERROR));
144251881Speter}
145251881Speter
146251881Speterstatic svn_error_t *
147251881Spetersasl_mutex_free_cb_internal(void *mutex)
148251881Speter{
149251881Speter  APR_ARRAY_PUSH(free_mutexes, svn_mutex__t*) = mutex;
150251881Speter  return SVN_NO_ERROR;
151251881Speter}
152251881Speter
153251881Speterstatic void sasl_mutex_free_cb(void *mutex)
154251881Speter{
155251881Speter  svn_error_t *err;
156251881Speter
157251881Speter  if (!svn_ra_svn__sasl_status)
158251881Speter    return;
159251881Speter
160251881Speter  err = svn_mutex__lock(array_mutex);
161251881Speter  if (err)
162251881Speter    svn_error_clear(err);
163251881Speter  else
164251881Speter    svn_error_clear(svn_mutex__unlock(array_mutex,
165251881Speter                                      sasl_mutex_free_cb_internal(mutex)));
166251881Speter}
167251881Speter#endif /* APR_HAS_THREADS */
168251881Speter
169251881Spetersvn_error_t *
170251881Spetersvn_ra_svn__sasl_common_init(apr_pool_t *pool)
171251881Speter{
172251881Speter  sasl_pool = svn_pool_create(pool);
173251881Speter  sasl_ctx_count = 1;
174251881Speter  apr_pool_cleanup_register(sasl_pool, NULL, sasl_done_cb,
175251881Speter                            apr_pool_cleanup_null);
176251881Speter#if APR_HAS_THREADS
177251881Speter  sasl_set_mutex(sasl_mutex_alloc_cb,
178251881Speter                 sasl_mutex_lock_cb,
179251881Speter                 sasl_mutex_unlock_cb,
180251881Speter                 sasl_mutex_free_cb);
181251881Speter  free_mutexes = apr_array_make(sasl_pool, 0, sizeof(svn_mutex__t *));
182251881Speter  SVN_ERR(svn_mutex__init(&array_mutex, TRUE, sasl_pool));
183251881Speter
184251881Speter#endif /* APR_HAS_THREADS */
185251881Speter
186251881Speter  return SVN_NO_ERROR;
187251881Speter}
188251881Speter
189251881Speter/* We are going to look at errno when we get SASL_FAIL but we don't
190251881Speter   know for sure whether SASL always sets errno.  Clearing errno
191251881Speter   before calling SASL functions helps in cases where SASL does
192251881Speter   nothing to set errno. */
193251881Speter#ifdef apr_set_os_error
194251881Speter#define clear_sasl_errno() apr_set_os_error(APR_SUCCESS)
195251881Speter#else
196251881Speter#define clear_sasl_errno() (void)0
197251881Speter#endif
198251881Speter
199251881Speter/* Sometimes SASL returns SASL_FAIL as RESULT and sets errno.
200251881Speter * SASL_FAIL translates to "generic error" which is quite unhelpful.
201251881Speter * Try to append a more informative error message based on errno so
202251881Speter * should be called before doing anything that may change errno. */
203251881Speterstatic const char *
204251881Speterget_sasl_errno_msg(int result, apr_pool_t *result_pool)
205251881Speter{
206251881Speter#ifdef apr_get_os_error
207251881Speter  char buf[1024];
208251881Speter
209251881Speter  if (result == SASL_FAIL && apr_get_os_error() != 0)
210251881Speter    return apr_psprintf(result_pool, ": %s",
211251881Speter                        svn_strerror(apr_get_os_error(), buf, sizeof(buf)));
212251881Speter#endif
213251881Speter  return "";
214251881Speter}
215251881Speter
216251881Speter/* Wrap an error message from SASL with a prefix that allows users
217251881Speter * to tell that the error message came from SASL.  Queries errno and
218251881Speter * so should be called before doing anything that may change errno. */
219251881Speterstatic const char *
220251881Speterget_sasl_error(sasl_conn_t *sasl_ctx, int result, apr_pool_t *result_pool)
221251881Speter{
222251881Speter  const char *sasl_errno_msg = get_sasl_errno_msg(result, result_pool);
223251881Speter
224251881Speter  return apr_psprintf(result_pool,
225251881Speter                      _("SASL authentication error: %s%s"),
226251881Speter                      sasl_errdetail(sasl_ctx), sasl_errno_msg);
227251881Speter}
228251881Speter
229251881Speterstatic svn_error_t *sasl_init_cb(void *baton, apr_pool_t *pool)
230251881Speter{
231251881Speter  int result;
232251881Speter
233251881Speter  SVN_ERR(svn_ra_svn__sasl_common_init(pool));
234251881Speter  clear_sasl_errno();
235251881Speter  result = sasl_client_init(NULL);
236251881Speter  if (result != SASL_OK)
237251881Speter    {
238251881Speter      const char *sasl_errno_msg = get_sasl_errno_msg(result, pool);
239251881Speter
240251881Speter      return svn_error_createf
241251881Speter        (SVN_ERR_RA_NOT_AUTHORIZED, NULL,
242251881Speter         _("Could not initialized the SASL library: %s%s"),
243251881Speter         sasl_errstring(result, NULL, NULL),
244251881Speter         sasl_errno_msg);
245251881Speter    }
246251881Speter
247251881Speter  return SVN_NO_ERROR;
248251881Speter}
249251881Speter
250251881Spetersvn_error_t *svn_ra_svn__sasl_init(void)
251251881Speter{
252251881Speter  SVN_ERR(svn_atomic__init_once(&svn_ra_svn__sasl_status,
253251881Speter                                sasl_init_cb, NULL, NULL));
254251881Speter  return SVN_NO_ERROR;
255251881Speter}
256251881Speter
257251881Speterstatic apr_status_t sasl_dispose_cb(void *data)
258251881Speter{
259251881Speter  sasl_conn_t *sasl_ctx = data;
260251881Speter  sasl_dispose(&sasl_ctx);
261251881Speter  if (svn_atomic_dec(&sasl_ctx_count) == 0)
262251881Speter    sasl_done();
263251881Speter  return APR_SUCCESS;
264251881Speter}
265251881Speter
266251881Spetervoid svn_ra_svn__default_secprops(sasl_security_properties_t *secprops)
267251881Speter{
268251881Speter  /* The minimum and maximum security strength factors that the chosen
269251881Speter     SASL mechanism should provide.  0 means 'no encryption', 256 means
270251881Speter     '256-bit encryption', which is about the best that any SASL
271251881Speter     mechanism can provide.  Using these values effectively means 'use
272251881Speter     whatever encryption the other side wants'.  Note that SASL will try
273251881Speter     to use better encryption whenever possible, so if both the server and
274251881Speter     the client use these values the highest possible encryption strength
275251881Speter     will be used. */
276251881Speter  secprops->min_ssf = 0;
277251881Speter  secprops->max_ssf = 256;
278251881Speter
279251881Speter  /* Set maxbufsize to the maximum amount of data we can read at any one time.
280251881Speter     This value needs to be commmunicated to the peer if a security layer
281251881Speter     is negotiated. */
282251881Speter  secprops->maxbufsize = SVN_RA_SVN__READBUF_SIZE;
283251881Speter
284251881Speter  secprops->security_flags = 0;
285251881Speter  secprops->property_names = secprops->property_values = NULL;
286251881Speter}
287251881Speter
288251881Speter/* A baton type used by the SASL username and password callbacks. */
289251881Spetertypedef struct cred_baton {
290251881Speter  svn_auth_baton_t *auth_baton;
291251881Speter  svn_auth_iterstate_t *iterstate;
292251881Speter  const char *realmstring;
293251881Speter
294251881Speter  /* Unfortunately SASL uses two separate callbacks for the username and
295251881Speter     password, but we must fetch both of them at the same time. So we cache
296251881Speter     their values in the baton, set them to NULL individually when SASL
297251881Speter     demands them, and fetch the next pair when both are NULL. */
298251881Speter  const char *username;
299251881Speter  const char *password;
300251881Speter
301251881Speter  /* Any errors we receive from svn_auth_{first,next}_credentials
302251881Speter     are saved here. */
303251881Speter  svn_error_t *err;
304251881Speter
305251881Speter  /* This flag is set when we run out of credential providers. */
306251881Speter  svn_boolean_t no_more_creds;
307251881Speter
308251881Speter  /* Were the auth callbacks ever called? */
309251881Speter  svn_boolean_t was_used;
310251881Speter
311251881Speter  apr_pool_t *pool;
312251881Speter} cred_baton_t;
313251881Speter
314251881Speter/* Call svn_auth_{first,next}_credentials. If successful, set BATON->username
315251881Speter   and BATON->password to the new username and password and return TRUE,
316251881Speter   otherwise return FALSE. If there are no more credentials, set
317251881Speter   BATON->no_more_creds to TRUE. Any errors are saved in BATON->err. */
318251881Speterstatic svn_boolean_t
319251881Speterget_credentials(cred_baton_t *baton)
320251881Speter{
321251881Speter  void *creds;
322251881Speter
323251881Speter  if (baton->iterstate)
324251881Speter    baton->err = svn_auth_next_credentials(&creds, baton->iterstate,
325251881Speter                                           baton->pool);
326251881Speter  else
327251881Speter    baton->err = svn_auth_first_credentials(&creds, &baton->iterstate,
328251881Speter                                            SVN_AUTH_CRED_SIMPLE,
329251881Speter                                            baton->realmstring,
330251881Speter                                            baton->auth_baton, baton->pool);
331251881Speter  if (baton->err)
332251881Speter    return FALSE;
333251881Speter
334251881Speter  if (! creds)
335251881Speter    {
336251881Speter      baton->no_more_creds = TRUE;
337251881Speter      return FALSE;
338251881Speter    }
339251881Speter
340251881Speter  baton->username = ((svn_auth_cred_simple_t *)creds)->username;
341251881Speter  baton->password = ((svn_auth_cred_simple_t *)creds)->password;
342251881Speter  baton->was_used = TRUE;
343251881Speter
344251881Speter  return TRUE;
345251881Speter}
346251881Speter
347251881Speter/* The username callback. Implements the sasl_getsimple_t interface. */
348251881Speterstatic int
349251881Speterget_username_cb(void *b, int id, const char **username, size_t *len)
350251881Speter{
351251881Speter  cred_baton_t *baton = b;
352251881Speter
353251881Speter  if (baton->username || get_credentials(baton))
354251881Speter    {
355251881Speter      *username = baton->username;
356251881Speter      if (len)
357251881Speter        *len = strlen(baton->username);
358251881Speter      baton->username = NULL;
359251881Speter
360251881Speter      return SASL_OK;
361251881Speter    }
362251881Speter
363251881Speter  return SASL_FAIL;
364251881Speter}
365251881Speter
366251881Speter/* The password callback. Implements the sasl_getsecret_t interface. */
367251881Speterstatic int
368251881Speterget_password_cb(sasl_conn_t *conn, void *b, int id, sasl_secret_t **psecret)
369251881Speter{
370251881Speter  cred_baton_t *baton = b;
371251881Speter
372251881Speter  if (baton->password || get_credentials(baton))
373251881Speter    {
374251881Speter      sasl_secret_t *secret;
375251881Speter      size_t len = strlen(baton->password);
376251881Speter
377251881Speter      /* sasl_secret_t is a struct with a variable-sized array as a final
378251881Speter         member, which means we need to allocate len-1 supplementary bytes
379251881Speter         (one byte is part of sasl_secret_t, and we don't need a NULL
380251881Speter         terminator). */
381251881Speter      secret = apr_palloc(baton->pool, sizeof(*secret) + len - 1);
382251881Speter      secret->len = len;
383251881Speter      memcpy(secret->data, baton->password, len);
384251881Speter      baton->password = NULL;
385251881Speter      *psecret = secret;
386251881Speter
387251881Speter      return SASL_OK;
388251881Speter    }
389251881Speter
390251881Speter  return SASL_FAIL;
391251881Speter}
392251881Speter
393251881Speter/* Create a new SASL context. */
394251881Speterstatic svn_error_t *new_sasl_ctx(sasl_conn_t **sasl_ctx,
395251881Speter                                 svn_boolean_t is_tunneled,
396251881Speter                                 const char *hostname,
397251881Speter                                 const char *local_addrport,
398251881Speter                                 const char *remote_addrport,
399251881Speter                                 sasl_callback_t *callbacks,
400251881Speter                                 apr_pool_t *pool)
401251881Speter{
402251881Speter  sasl_security_properties_t secprops;
403251881Speter  int result;
404251881Speter
405251881Speter  clear_sasl_errno();
406251881Speter  result = sasl_client_new(SVN_RA_SVN_SASL_NAME,
407251881Speter                           hostname, local_addrport, remote_addrport,
408251881Speter                           callbacks, SASL_SUCCESS_DATA,
409251881Speter                           sasl_ctx);
410251881Speter  if (result != SASL_OK)
411251881Speter    {
412251881Speter      const char *sasl_errno_msg = get_sasl_errno_msg(result, pool);
413251881Speter
414251881Speter      return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
415251881Speter                               _("Could not create SASL context: %s%s"),
416251881Speter                               sasl_errstring(result, NULL, NULL),
417251881Speter                               sasl_errno_msg);
418251881Speter    }
419251881Speter  svn_atomic_inc(&sasl_ctx_count);
420251881Speter  apr_pool_cleanup_register(pool, *sasl_ctx, sasl_dispose_cb,
421251881Speter                            apr_pool_cleanup_null);
422251881Speter
423251881Speter  if (is_tunneled)
424251881Speter    {
425251881Speter      /* We need to tell SASL that this connection is tunneled,
426251881Speter         otherwise it will ignore EXTERNAL. The third parameter
427251881Speter         should be the username, but since SASL doesn't seem
428251881Speter         to use it on the client side, any non-empty string will do. */
429251881Speter      clear_sasl_errno();
430251881Speter      result = sasl_setprop(*sasl_ctx,
431251881Speter                            SASL_AUTH_EXTERNAL, " ");
432251881Speter      if (result != SASL_OK)
433251881Speter        return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
434251881Speter                                get_sasl_error(*sasl_ctx, result, pool));
435251881Speter    }
436251881Speter
437251881Speter  /* Set security properties. */
438251881Speter  svn_ra_svn__default_secprops(&secprops);
439251881Speter  sasl_setprop(*sasl_ctx, SASL_SEC_PROPS, &secprops);
440251881Speter
441251881Speter  return SVN_NO_ERROR;
442251881Speter}
443251881Speter
444251881Speter/* Perform an authentication exchange */
445251881Speterstatic svn_error_t *try_auth(svn_ra_svn__session_baton_t *sess,
446251881Speter                             sasl_conn_t *sasl_ctx,
447251881Speter                             svn_boolean_t *success,
448251881Speter                             const char **last_err,
449251881Speter                             const char *mechstring,
450251881Speter                             apr_pool_t *pool)
451251881Speter{
452251881Speter  sasl_interact_t *client_interact = NULL;
453251881Speter  const char *out, *mech, *status = NULL;
454251881Speter  const svn_string_t *arg = NULL, *in;
455251881Speter  int result;
456251881Speter  unsigned int outlen;
457251881Speter  svn_boolean_t again;
458251881Speter
459251881Speter  do
460251881Speter    {
461251881Speter      again = FALSE;
462251881Speter      clear_sasl_errno();
463251881Speter      result = sasl_client_start(sasl_ctx,
464251881Speter                                 mechstring,
465251881Speter                                 &client_interact,
466251881Speter                                 &out,
467251881Speter                                 &outlen,
468251881Speter                                 &mech);
469251881Speter      switch (result)
470251881Speter        {
471251881Speter          case SASL_OK:
472251881Speter          case SASL_CONTINUE:
473251881Speter            /* Success. */
474251881Speter            break;
475251881Speter          case SASL_NOMECH:
476251881Speter            return svn_error_create(SVN_ERR_RA_SVN_NO_MECHANISMS, NULL, NULL);
477251881Speter          case SASL_BADPARAM:
478251881Speter          case SASL_NOMEM:
479251881Speter            /* Fatal error.  Fail the authentication. */
480251881Speter            return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
481251881Speter                                    get_sasl_error(sasl_ctx, result, pool));
482251881Speter          default:
483251881Speter            /* For anything else, delete the mech from the list
484251881Speter               and try again. */
485251881Speter            {
486251881Speter              const char *pmech = strstr(mechstring, mech);
487251881Speter              const char *head = apr_pstrndup(pool, mechstring,
488251881Speter                                              pmech - mechstring);
489251881Speter              const char *tail = pmech + strlen(mech);
490251881Speter
491299742Sdim              mechstring = apr_pstrcat(pool, head, tail, SVN_VA_NULL);
492251881Speter              again = TRUE;
493251881Speter            }
494251881Speter        }
495251881Speter    }
496251881Speter  while (again);
497251881Speter
498251881Speter  /* Prepare the initial authentication token. */
499251881Speter  if (outlen > 0 || strcmp(mech, "EXTERNAL") == 0)
500251881Speter    arg = svn_base64_encode_string2(svn_string_ncreate(out, outlen, pool),
501251881Speter                                    TRUE, pool);
502251881Speter
503251881Speter  /* Send the initial client response */
504251881Speter  SVN_ERR(svn_ra_svn__auth_response(sess->conn, pool, mech,
505251881Speter                                    arg ? arg->data : NULL));
506251881Speter
507251881Speter  while (result == SASL_CONTINUE)
508251881Speter    {
509251881Speter      /* Read the server response */
510251881Speter      SVN_ERR(svn_ra_svn__read_tuple(sess->conn, pool, "w(?s)",
511251881Speter                                     &status, &in));
512251881Speter
513251881Speter      if (strcmp(status, "failure") == 0)
514251881Speter        {
515251881Speter          /* Authentication failed.  Use the next set of credentials */
516251881Speter          *success = FALSE;
517251881Speter          /* Remember the message sent by the server because we'll want to
518251881Speter             return a meaningful error if we run out of auth providers. */
519251881Speter          *last_err = in ? in->data : "";
520251881Speter          return SVN_NO_ERROR;
521251881Speter        }
522251881Speter
523251881Speter      if ((strcmp(status, "success") != 0 && strcmp(status, "step") != 0)
524251881Speter          || in == NULL)
525251881Speter        return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
526251881Speter                                _("Unexpected server response"
527251881Speter                                " to authentication"));
528251881Speter
529251881Speter      /* If the mech is CRAM-MD5 we don't base64-decode the server response. */
530251881Speter      if (strcmp(mech, "CRAM-MD5") != 0)
531251881Speter        in = svn_base64_decode_string(in, pool);
532251881Speter
533251881Speter      clear_sasl_errno();
534251881Speter      result = sasl_client_step(sasl_ctx,
535251881Speter                                in->data,
536251881Speter                                (const unsigned int) in->len,
537251881Speter                                &client_interact,
538251881Speter                                &out, /* Filled in by SASL. */
539251881Speter                                &outlen);
540251881Speter
541251881Speter      if (result != SASL_OK && result != SASL_CONTINUE)
542251881Speter        return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
543251881Speter                                get_sasl_error(sasl_ctx, result, pool));
544251881Speter
545251881Speter      /* If the server thinks we're done, then don't send any response. */
546251881Speter      if (strcmp(status, "success") == 0)
547251881Speter        break;
548251881Speter
549251881Speter      if (outlen > 0)
550251881Speter        {
551251881Speter          arg = svn_string_ncreate(out, outlen, pool);
552251881Speter          /* Write our response. */
553251881Speter          /* For CRAM-MD5, we don't use base64-encoding. */
554251881Speter          if (strcmp(mech, "CRAM-MD5") != 0)
555251881Speter            arg = svn_base64_encode_string2(arg, TRUE, pool);
556251881Speter          SVN_ERR(svn_ra_svn__write_cstring(sess->conn, pool, arg->data));
557251881Speter        }
558251881Speter      else
559251881Speter        {
560251881Speter          SVN_ERR(svn_ra_svn__write_cstring(sess->conn, pool, ""));
561251881Speter        }
562251881Speter    }
563251881Speter
564251881Speter  if (!status || strcmp(status, "step") == 0)
565251881Speter    {
566251881Speter      /* This is a client-send-last mech.  Read the last server response. */
567251881Speter      SVN_ERR(svn_ra_svn__read_tuple(sess->conn, pool, "w(?s)",
568251881Speter              &status, &in));
569251881Speter
570251881Speter      if (strcmp(status, "failure") == 0)
571251881Speter        {
572251881Speter          *success = FALSE;
573251881Speter          *last_err = in ? in->data : "";
574251881Speter        }
575251881Speter      else if (strcmp(status, "success") == 0)
576251881Speter        {
577251881Speter          /* We're done */
578251881Speter          *success = TRUE;
579251881Speter        }
580251881Speter      else
581251881Speter        return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
582251881Speter                                _("Unexpected server response"
583251881Speter                                " to authentication"));
584251881Speter    }
585251881Speter  else
586251881Speter    *success = TRUE;
587251881Speter  return SVN_NO_ERROR;
588251881Speter}
589251881Speter
590251881Speter/* Baton for a SASL encrypted svn_ra_svn__stream_t. */
591251881Spetertypedef struct sasl_baton {
592251881Speter  svn_ra_svn__stream_t *stream; /* Inherited stream. */
593251881Speter  sasl_conn_t *ctx;             /* The SASL context for this connection. */
594251881Speter  unsigned int maxsize;         /* The maximum amount of data we can encode. */
595251881Speter  const char *read_buf;         /* The buffer returned by sasl_decode. */
596251881Speter  unsigned int read_len;        /* Its current length. */
597251881Speter  const char *write_buf;        /* The buffer returned by sasl_encode. */
598251881Speter  unsigned int write_len;       /* Its length. */
599251881Speter  apr_pool_t *scratch_pool;
600251881Speter} sasl_baton_t;
601251881Speter
602251881Speter/* Functions to implement a SASL encrypted svn_ra_svn__stream_t. */
603251881Speter
604251881Speter/* Implements svn_read_fn_t. */
605251881Speterstatic svn_error_t *sasl_read_cb(void *baton, char *buffer, apr_size_t *len)
606251881Speter{
607251881Speter  sasl_baton_t *sasl_baton = baton;
608251881Speter  int result;
609251881Speter  /* A copy of *len, used by the wrapped stream. */
610251881Speter  apr_size_t len2 = *len;
611251881Speter
612251881Speter  /* sasl_decode might need more data than a single read can provide,
613251881Speter     hence the need to put a loop around the decoding. */
614251881Speter  while (! sasl_baton->read_buf || sasl_baton->read_len == 0)
615251881Speter    {
616251881Speter      SVN_ERR(svn_ra_svn__stream_read(sasl_baton->stream, buffer, &len2));
617251881Speter      if (len2 == 0)
618251881Speter        {
619251881Speter          *len = 0;
620251881Speter          return SVN_NO_ERROR;
621251881Speter        }
622251881Speter      clear_sasl_errno();
623251881Speter      result = sasl_decode(sasl_baton->ctx, buffer, (unsigned int) len2,
624251881Speter                           &sasl_baton->read_buf,
625251881Speter                           &sasl_baton->read_len);
626251881Speter      if (result != SASL_OK)
627251881Speter        return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
628251881Speter                                get_sasl_error(sasl_baton->ctx, result,
629251881Speter                                               sasl_baton->scratch_pool));
630251881Speter    }
631251881Speter
632251881Speter  /* The buffer returned by sasl_decode might be larger than what the
633251881Speter     caller wants.  If this is the case, we only copy back *len bytes now
634251881Speter     (the rest will be returned by subsequent calls to this function).
635251881Speter     If not, we just copy back the whole thing. */
636251881Speter  if (*len >= sasl_baton->read_len)
637251881Speter    {
638251881Speter      memcpy(buffer, sasl_baton->read_buf, sasl_baton->read_len);
639251881Speter      *len = sasl_baton->read_len;
640251881Speter      sasl_baton->read_buf = NULL;
641251881Speter      sasl_baton->read_len = 0;
642251881Speter    }
643251881Speter  else
644251881Speter    {
645251881Speter      memcpy(buffer, sasl_baton->read_buf, *len);
646251881Speter      sasl_baton->read_len -= *len;
647251881Speter      sasl_baton->read_buf += *len;
648251881Speter    }
649251881Speter
650251881Speter  return SVN_NO_ERROR;
651251881Speter}
652251881Speter
653251881Speter/* Implements svn_write_fn_t. */
654251881Speterstatic svn_error_t *
655251881Spetersasl_write_cb(void *baton, const char *buffer, apr_size_t *len)
656251881Speter{
657251881Speter  sasl_baton_t *sasl_baton = baton;
658251881Speter  int result;
659251881Speter
660251881Speter  if (! sasl_baton->write_buf || sasl_baton->write_len == 0)
661251881Speter    {
662251881Speter      /* Make sure we don't write too much. */
663251881Speter      *len = (*len > sasl_baton->maxsize) ? sasl_baton->maxsize : *len;
664251881Speter      clear_sasl_errno();
665251881Speter      result = sasl_encode(sasl_baton->ctx, buffer, (unsigned int) *len,
666251881Speter                           &sasl_baton->write_buf,
667251881Speter                           &sasl_baton->write_len);
668251881Speter
669251881Speter      if (result != SASL_OK)
670251881Speter        return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
671251881Speter                                get_sasl_error(sasl_baton->ctx, result,
672251881Speter                                               sasl_baton->scratch_pool));
673251881Speter    }
674251881Speter
675251881Speter  do
676251881Speter    {
677251881Speter      apr_size_t tmplen = sasl_baton->write_len;
678251881Speter      SVN_ERR(svn_ra_svn__stream_write(sasl_baton->stream,
679251881Speter                                       sasl_baton->write_buf,
680251881Speter                                       &tmplen));
681251881Speter      if (tmplen == 0)
682251881Speter      {
683251881Speter        /* The output buffer and its length will be preserved in sasl_baton
684251881Speter           and will be written out during the next call to this function
685251881Speter           (which will have the same arguments). */
686251881Speter        *len = 0;
687251881Speter        return SVN_NO_ERROR;
688251881Speter      }
689251881Speter      sasl_baton->write_len -= (unsigned int) tmplen;
690251881Speter      sasl_baton->write_buf += tmplen;
691251881Speter    }
692251881Speter  while (sasl_baton->write_len > 0);
693251881Speter
694251881Speter  sasl_baton->write_buf = NULL;
695251881Speter  sasl_baton->write_len = 0;
696251881Speter
697251881Speter  return SVN_NO_ERROR;
698251881Speter}
699251881Speter
700251881Speter/* Implements ra_svn_timeout_fn_t. */
701251881Speterstatic void sasl_timeout_cb(void *baton, apr_interval_time_t interval)
702251881Speter{
703251881Speter  sasl_baton_t *sasl_baton = baton;
704251881Speter  svn_ra_svn__stream_timeout(sasl_baton->stream, interval);
705251881Speter}
706251881Speter
707299742Sdim/* Implements svn_stream_data_available_fn_t. */
708299742Sdimstatic svn_error_t *
709299742Sdimsasl_data_available_cb(void *baton, svn_boolean_t *data_available)
710251881Speter{
711251881Speter  sasl_baton_t *sasl_baton = baton;
712299742Sdim  return svn_error_trace(svn_ra_svn__stream_data_available(sasl_baton->stream,
713299742Sdim                                                         data_available));
714251881Speter}
715251881Speter
716251881Spetersvn_error_t *svn_ra_svn__enable_sasl_encryption(svn_ra_svn_conn_t *conn,
717251881Speter                                                sasl_conn_t *sasl_ctx,
718251881Speter                                                apr_pool_t *pool)
719251881Speter{
720251881Speter  const sasl_ssf_t *ssfp;
721251881Speter
722251881Speter  if (! conn->encrypted)
723251881Speter    {
724251881Speter      int result;
725251881Speter
726251881Speter      /* Get the strength of the security layer. */
727251881Speter      clear_sasl_errno();
728251881Speter      result = sasl_getprop(sasl_ctx, SASL_SSF, (void*) &ssfp);
729251881Speter      if (result != SASL_OK)
730251881Speter        return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
731251881Speter                                get_sasl_error(sasl_ctx, result, pool));
732251881Speter
733251881Speter      if (*ssfp > 0)
734251881Speter        {
735251881Speter          sasl_baton_t *sasl_baton;
736251881Speter          const void *maxsize;
737251881Speter
738251881Speter          /* Flush the connection, as we're about to replace its stream. */
739251881Speter          SVN_ERR(svn_ra_svn__flush(conn, pool));
740251881Speter
741251881Speter          /* Create and initialize the stream baton. */
742251881Speter          sasl_baton = apr_pcalloc(conn->pool, sizeof(*sasl_baton));
743251881Speter          sasl_baton->ctx = sasl_ctx;
744251881Speter          sasl_baton->scratch_pool = conn->pool;
745251881Speter
746251881Speter          /* Find out the maximum input size for sasl_encode. */
747251881Speter          clear_sasl_errno();
748251881Speter          result = sasl_getprop(sasl_ctx, SASL_MAXOUTBUF, &maxsize);
749251881Speter          if (result != SASL_OK)
750251881Speter            return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
751251881Speter                                    get_sasl_error(sasl_ctx, result, pool));
752251881Speter          sasl_baton->maxsize = *((const unsigned int *) maxsize);
753251881Speter
754251881Speter          /* If there is any data left in the read buffer at this point,
755251881Speter             we need to decrypt it. */
756251881Speter          if (conn->read_end > conn->read_ptr)
757251881Speter            {
758251881Speter              clear_sasl_errno();
759251881Speter              result = sasl_decode(sasl_ctx, conn->read_ptr,
760251881Speter                             (unsigned int) (conn->read_end - conn->read_ptr),
761251881Speter                             &sasl_baton->read_buf, &sasl_baton->read_len);
762251881Speter              if (result != SASL_OK)
763251881Speter                return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
764251881Speter                                        get_sasl_error(sasl_ctx, result, pool));
765251881Speter              conn->read_end = conn->read_ptr;
766251881Speter            }
767251881Speter
768251881Speter          /* Wrap the existing stream. */
769251881Speter          sasl_baton->stream = conn->stream;
770251881Speter
771299742Sdim          {
772299742Sdim            svn_stream_t *sasl_in = svn_stream_create(sasl_baton, conn->pool);
773299742Sdim            svn_stream_t *sasl_out = svn_stream_create(sasl_baton, conn->pool);
774299742Sdim
775299742Sdim            svn_stream_set_read2(sasl_in, sasl_read_cb, NULL /* use default */);
776299742Sdim            svn_stream_set_data_available(sasl_in, sasl_data_available_cb);
777299742Sdim            svn_stream_set_write(sasl_out, sasl_write_cb);
778299742Sdim
779299742Sdim            conn->stream = svn_ra_svn__stream_create(sasl_in, sasl_out,
780299742Sdim                                                     sasl_baton,
781299742Sdim                                                     sasl_timeout_cb,
782299742Sdim                                                     conn->pool);
783299742Sdim          }
784251881Speter          /* Yay, we have a security layer! */
785251881Speter          conn->encrypted = TRUE;
786251881Speter        }
787251881Speter    }
788251881Speter  return SVN_NO_ERROR;
789251881Speter}
790251881Speter
791251881Spetersvn_error_t *svn_ra_svn__get_addresses(const char **local_addrport,
792251881Speter                                       const char **remote_addrport,
793251881Speter                                       svn_ra_svn_conn_t *conn,
794251881Speter                                       apr_pool_t *pool)
795251881Speter{
796251881Speter  if (conn->sock)
797251881Speter    {
798251881Speter      apr_status_t apr_err;
799251881Speter      apr_sockaddr_t *local_sa, *remote_sa;
800251881Speter      char *local_addr, *remote_addr;
801251881Speter
802251881Speter      apr_err = apr_socket_addr_get(&local_sa, APR_LOCAL, conn->sock);
803251881Speter      if (apr_err)
804251881Speter        return svn_error_wrap_apr(apr_err, NULL);
805251881Speter
806251881Speter      apr_err = apr_socket_addr_get(&remote_sa, APR_REMOTE, conn->sock);
807251881Speter      if (apr_err)
808251881Speter        return svn_error_wrap_apr(apr_err, NULL);
809251881Speter
810251881Speter      apr_err = apr_sockaddr_ip_get(&local_addr, local_sa);
811251881Speter      if (apr_err)
812251881Speter        return svn_error_wrap_apr(apr_err, NULL);
813251881Speter
814251881Speter      apr_err = apr_sockaddr_ip_get(&remote_addr, remote_sa);
815251881Speter      if (apr_err)
816251881Speter        return svn_error_wrap_apr(apr_err, NULL);
817251881Speter
818251881Speter      /* Format the IP address and port number like this: a.b.c.d;port */
819251881Speter      *local_addrport = apr_pstrcat(pool, local_addr, ";",
820251881Speter                                    apr_itoa(pool, (int)local_sa->port),
821299742Sdim                                    SVN_VA_NULL);
822251881Speter      *remote_addrport = apr_pstrcat(pool, remote_addr, ";",
823251881Speter                                     apr_itoa(pool, (int)remote_sa->port),
824299742Sdim                                     SVN_VA_NULL);
825251881Speter    }
826251881Speter  return SVN_NO_ERROR;
827251881Speter}
828251881Speter
829251881Spetersvn_error_t *
830251881Spetersvn_ra_svn__do_cyrus_auth(svn_ra_svn__session_baton_t *sess,
831251881Speter                          const apr_array_header_t *mechlist,
832251881Speter                          const char *realm, apr_pool_t *pool)
833251881Speter{
834251881Speter  apr_pool_t *subpool;
835251881Speter  sasl_conn_t *sasl_ctx;
836251881Speter  const char *mechstring = "", *last_err = "", *realmstring;
837251881Speter  const char *local_addrport = NULL, *remote_addrport = NULL;
838251881Speter  svn_boolean_t success;
839251881Speter  sasl_callback_t *callbacks;
840251881Speter  cred_baton_t cred_baton = { 0 };
841251881Speter  int i;
842251881Speter
843251881Speter  if (!sess->is_tunneled)
844251881Speter    {
845251881Speter      SVN_ERR(svn_ra_svn__get_addresses(&local_addrport, &remote_addrport,
846251881Speter                                        sess->conn, pool));
847251881Speter    }
848251881Speter
849251881Speter  /* Prefer EXTERNAL, then ANONYMOUS, then let SASL decide. */
850251881Speter  if (svn_ra_svn__find_mech(mechlist, "EXTERNAL"))
851251881Speter    mechstring = "EXTERNAL";
852251881Speter  else if (svn_ra_svn__find_mech(mechlist, "ANONYMOUS"))
853251881Speter    mechstring = "ANONYMOUS";
854251881Speter  else
855251881Speter    {
856251881Speter      /* Create a string containing the list of mechanisms, separated by spaces. */
857251881Speter      for (i = 0; i < mechlist->nelts; i++)
858251881Speter        {
859251881Speter          svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(mechlist, i, svn_ra_svn_item_t);
860251881Speter          mechstring = apr_pstrcat(pool,
861251881Speter                                   mechstring,
862251881Speter                                   i == 0 ? "" : " ",
863299742Sdim                                   elt->u.word, SVN_VA_NULL);
864251881Speter        }
865251881Speter    }
866251881Speter
867251881Speter  realmstring = apr_psprintf(pool, "%s %s", sess->realm_prefix, realm);
868251881Speter
869251881Speter  /* Initialize the credential baton. */
870299742Sdim  cred_baton.auth_baton = sess->auth_baton;
871251881Speter  cred_baton.realmstring = realmstring;
872251881Speter  cred_baton.pool = pool;
873251881Speter
874251881Speter  /* Reserve space for 3 callbacks (for the username, password and the
875251881Speter     array terminator).  These structures must persist until the
876251881Speter     disposal of the SASL context at pool cleanup, however the
877251881Speter     callback functions will not be invoked outside this function so
878251881Speter     other structures can have a shorter lifetime. */
879251881Speter  callbacks = apr_palloc(sess->conn->pool, sizeof(*callbacks) * 3);
880251881Speter
881251881Speter  /* Initialize the callbacks array. */
882251881Speter
883251881Speter  /* The username callback. */
884251881Speter  callbacks[0].id = SASL_CB_AUTHNAME;
885251881Speter  callbacks[0].proc = (int (*)(void))get_username_cb;
886251881Speter  callbacks[0].context = &cred_baton;
887251881Speter
888251881Speter  /* The password callback. */
889251881Speter  callbacks[1].id = SASL_CB_PASS;
890251881Speter  callbacks[1].proc = (int (*)(void))get_password_cb;
891251881Speter  callbacks[1].context = &cred_baton;
892251881Speter
893251881Speter  /* Mark the end of the array. */
894251881Speter  callbacks[2].id = SASL_CB_LIST_END;
895251881Speter  callbacks[2].proc = NULL;
896251881Speter  callbacks[2].context = NULL;
897251881Speter
898251881Speter  subpool = svn_pool_create(pool);
899251881Speter  do
900251881Speter    {
901251881Speter      svn_error_t *err;
902251881Speter
903251881Speter      /* If last_err was set to a non-empty string, it needs to be duplicated
904251881Speter         to the parent pool before the subpool is cleared. */
905251881Speter      if (*last_err)
906251881Speter        last_err = apr_pstrdup(pool, last_err);
907251881Speter      svn_pool_clear(subpool);
908251881Speter
909251881Speter      SVN_ERR(new_sasl_ctx(&sasl_ctx, sess->is_tunneled,
910251881Speter                           sess->hostname, local_addrport, remote_addrport,
911251881Speter                           callbacks, sess->conn->pool));
912251881Speter      err = try_auth(sess, sasl_ctx, &success, &last_err, mechstring,
913251881Speter                     subpool);
914251881Speter
915251881Speter      /* If we encountered an error while fetching credentials, that error
916251881Speter         has priority. */
917251881Speter      if (cred_baton.err)
918251881Speter        {
919251881Speter          svn_error_clear(err);
920251881Speter          return cred_baton.err;
921251881Speter        }
922251881Speter      if (cred_baton.no_more_creds
923251881Speter          || (! err && ! success && ! cred_baton.was_used))
924251881Speter        {
925251881Speter          svn_error_clear(err);
926251881Speter          /* If we ran out of authentication providers, or if we got a server
927251881Speter             error and our callbacks were never called, there's no point in
928251881Speter             retrying authentication.  Return the last error sent by the
929251881Speter             server. */
930251881Speter          if (*last_err)
931251881Speter            return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
932251881Speter                                     _("Authentication error from server: %s"),
933251881Speter                                     last_err);
934251881Speter          /* Hmm, we don't have a server error. Return a generic error. */
935251881Speter          return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
936251881Speter                                  _("Can't get username or password"));
937251881Speter        }
938251881Speter      if (err)
939251881Speter        {
940251881Speter          if (err->apr_err == SVN_ERR_RA_SVN_NO_MECHANISMS)
941251881Speter            {
942251881Speter              svn_error_clear(err);
943251881Speter
944251881Speter              /* We could not find a supported mechanism in the list sent by the
945251881Speter                 server. In many cases this happens because the client is missing
946251881Speter                 the CRAM-MD5 or ANONYMOUS plugins, in which case we can simply use
947251881Speter                 the built-in implementation. In all other cases this call will be
948251881Speter                 useless, but hey, at least we'll get consistent error messages. */
949299742Sdim              return svn_error_trace(svn_ra_svn__do_internal_auth(sess, mechlist,
950299742Sdim                                                                realm, pool));
951251881Speter            }
952251881Speter          return err;
953251881Speter        }
954251881Speter    }
955251881Speter  while (!success);
956251881Speter  svn_pool_destroy(subpool);
957251881Speter
958251881Speter  SVN_ERR(svn_ra_svn__enable_sasl_encryption(sess->conn, sasl_ctx, pool));
959251881Speter
960251881Speter  SVN_ERR(svn_auth_save_credentials(cred_baton.iterstate, pool));
961251881Speter
962251881Speter  return SVN_NO_ERROR;
963251881Speter}
964251881Speter
965251881Speter#endif /* SVN_HAVE_SASL */
966