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)
71362181Sdim    svn_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
177362181Sdim  svn_sasl__set_mutex(sasl_mutex_alloc_cb,
178362181Sdim                      sasl_mutex_lock_cb,
179362181Sdim                      sasl_mutex_unlock_cb,
180362181Sdim                      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"),
226362181Sdim                      svn_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();
235362181Sdim  result = svn_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"),
243362181Sdim         svn_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;
260362181Sdim  svn_sasl__dispose(&sasl_ctx);
261251881Speter  if (svn_atomic_dec(&sasl_ctx_count) == 0)
262362181Sdim    svn_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();
406362181Sdim  result = svn_sasl__client_new(SVN_RA_SVN_SASL_NAME,
407362181Sdim                                hostname, local_addrport, remote_addrport,
408362181Sdim                                callbacks, SASL_SUCCESS_DATA,
409362181Sdim                                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"),
416362181Sdim                               svn_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();
430362181Sdim      result = svn_sasl__setprop(*sasl_ctx,
431362181Sdim                                 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);
439362181Sdim  svn_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();
463362181Sdim      result = svn_sasl__client_start(sasl_ctx,
464362181Sdim                                      mechstring,
465362181Sdim                                      &client_interact,
466362181Sdim                                      &out,
467362181Sdim                                      &outlen,
468362181Sdim                                      &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
491289180Speter              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();
534362181Sdim      result = svn_sasl__client_step(sasl_ctx,
535362181Sdim                                     in->data,
536362181Sdim                                     (const unsigned int) in->len,
537362181Sdim                                     &client_interact,
538362181Sdim                                     &out, /* Filled in by SASL. */
539362181Sdim                                     &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();
623362181Sdim      result = svn_sasl__decode(sasl_baton->ctx, buffer, (unsigned int) len2,
624362181Sdim                                &sasl_baton->read_buf,
625362181Sdim                                &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();
665362181Sdim      result = svn_sasl__encode(sasl_baton->ctx, buffer, (unsigned int) *len,
666362181Sdim                                &sasl_baton->write_buf,
667362181Sdim                                &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
707289180Speter/* Implements svn_stream_data_available_fn_t. */
708289180Speterstatic svn_error_t *
709289180Spetersasl_data_available_cb(void *baton, svn_boolean_t *data_available)
710251881Speter{
711251881Speter  sasl_baton_t *sasl_baton = baton;
712289180Speter  return svn_error_trace(svn_ra_svn__stream_data_available(sasl_baton->stream,
713289180Speter                                                         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();
728362181Sdim      result = svn_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();
748362181Sdim          result = svn_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();
759362181Sdim              result = svn_sasl__decode(
760362181Sdim                  sasl_ctx, conn->read_ptr,
761362181Sdim                  (unsigned int) (conn->read_end - conn->read_ptr),
762362181Sdim                  &sasl_baton->read_buf, &sasl_baton->read_len);
763251881Speter              if (result != SASL_OK)
764251881Speter                return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
765251881Speter                                        get_sasl_error(sasl_ctx, result, pool));
766251881Speter              conn->read_end = conn->read_ptr;
767251881Speter            }
768251881Speter
769251881Speter          /* Wrap the existing stream. */
770251881Speter          sasl_baton->stream = conn->stream;
771251881Speter
772289180Speter          {
773289180Speter            svn_stream_t *sasl_in = svn_stream_create(sasl_baton, conn->pool);
774289180Speter            svn_stream_t *sasl_out = svn_stream_create(sasl_baton, conn->pool);
775289180Speter
776289180Speter            svn_stream_set_read2(sasl_in, sasl_read_cb, NULL /* use default */);
777289180Speter            svn_stream_set_data_available(sasl_in, sasl_data_available_cb);
778289180Speter            svn_stream_set_write(sasl_out, sasl_write_cb);
779289180Speter
780289180Speter            conn->stream = svn_ra_svn__stream_create(sasl_in, sasl_out,
781289180Speter                                                     sasl_baton,
782289180Speter                                                     sasl_timeout_cb,
783289180Speter                                                     conn->pool);
784289180Speter          }
785251881Speter          /* Yay, we have a security layer! */
786251881Speter          conn->encrypted = TRUE;
787251881Speter        }
788251881Speter    }
789251881Speter  return SVN_NO_ERROR;
790251881Speter}
791251881Speter
792251881Spetersvn_error_t *svn_ra_svn__get_addresses(const char **local_addrport,
793251881Speter                                       const char **remote_addrport,
794251881Speter                                       svn_ra_svn_conn_t *conn,
795251881Speter                                       apr_pool_t *pool)
796251881Speter{
797251881Speter  if (conn->sock)
798251881Speter    {
799251881Speter      apr_status_t apr_err;
800251881Speter      apr_sockaddr_t *local_sa, *remote_sa;
801251881Speter      char *local_addr, *remote_addr;
802251881Speter
803251881Speter      apr_err = apr_socket_addr_get(&local_sa, APR_LOCAL, conn->sock);
804251881Speter      if (apr_err)
805251881Speter        return svn_error_wrap_apr(apr_err, NULL);
806251881Speter
807251881Speter      apr_err = apr_socket_addr_get(&remote_sa, APR_REMOTE, conn->sock);
808251881Speter      if (apr_err)
809251881Speter        return svn_error_wrap_apr(apr_err, NULL);
810251881Speter
811251881Speter      apr_err = apr_sockaddr_ip_get(&local_addr, local_sa);
812251881Speter      if (apr_err)
813251881Speter        return svn_error_wrap_apr(apr_err, NULL);
814251881Speter
815251881Speter      apr_err = apr_sockaddr_ip_get(&remote_addr, remote_sa);
816251881Speter      if (apr_err)
817251881Speter        return svn_error_wrap_apr(apr_err, NULL);
818251881Speter
819251881Speter      /* Format the IP address and port number like this: a.b.c.d;port */
820251881Speter      *local_addrport = apr_pstrcat(pool, local_addr, ";",
821251881Speter                                    apr_itoa(pool, (int)local_sa->port),
822289180Speter                                    SVN_VA_NULL);
823251881Speter      *remote_addrport = apr_pstrcat(pool, remote_addr, ";",
824251881Speter                                     apr_itoa(pool, (int)remote_sa->port),
825289180Speter                                     SVN_VA_NULL);
826251881Speter    }
827251881Speter  return SVN_NO_ERROR;
828251881Speter}
829251881Speter
830251881Spetersvn_error_t *
831251881Spetersvn_ra_svn__do_cyrus_auth(svn_ra_svn__session_baton_t *sess,
832362181Sdim                          const svn_ra_svn__list_t *mechlist,
833251881Speter                          const char *realm, apr_pool_t *pool)
834251881Speter{
835251881Speter  apr_pool_t *subpool;
836251881Speter  sasl_conn_t *sasl_ctx;
837251881Speter  const char *mechstring = "", *last_err = "", *realmstring;
838251881Speter  const char *local_addrport = NULL, *remote_addrport = NULL;
839251881Speter  svn_boolean_t success;
840251881Speter  sasl_callback_t *callbacks;
841251881Speter  cred_baton_t cred_baton = { 0 };
842251881Speter  int i;
843251881Speter
844251881Speter  if (!sess->is_tunneled)
845251881Speter    {
846251881Speter      SVN_ERR(svn_ra_svn__get_addresses(&local_addrport, &remote_addrport,
847251881Speter                                        sess->conn, pool));
848251881Speter    }
849251881Speter
850251881Speter  /* Prefer EXTERNAL, then ANONYMOUS, then let SASL decide. */
851251881Speter  if (svn_ra_svn__find_mech(mechlist, "EXTERNAL"))
852251881Speter    mechstring = "EXTERNAL";
853251881Speter  else if (svn_ra_svn__find_mech(mechlist, "ANONYMOUS"))
854251881Speter    mechstring = "ANONYMOUS";
855251881Speter  else
856251881Speter    {
857251881Speter      /* Create a string containing the list of mechanisms, separated by spaces. */
858251881Speter      for (i = 0; i < mechlist->nelts; i++)
859251881Speter        {
860362181Sdim          svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(mechlist, i);
861251881Speter          mechstring = apr_pstrcat(pool,
862251881Speter                                   mechstring,
863251881Speter                                   i == 0 ? "" : " ",
864362181Sdim                                   elt->u.word.data, SVN_VA_NULL);
865251881Speter        }
866251881Speter    }
867251881Speter
868251881Speter  realmstring = apr_psprintf(pool, "%s %s", sess->realm_prefix, realm);
869251881Speter
870251881Speter  /* Initialize the credential baton. */
871289180Speter  cred_baton.auth_baton = sess->auth_baton;
872251881Speter  cred_baton.realmstring = realmstring;
873251881Speter  cred_baton.pool = pool;
874251881Speter
875251881Speter  /* Reserve space for 3 callbacks (for the username, password and the
876251881Speter     array terminator).  These structures must persist until the
877251881Speter     disposal of the SASL context at pool cleanup, however the
878251881Speter     callback functions will not be invoked outside this function so
879251881Speter     other structures can have a shorter lifetime. */
880251881Speter  callbacks = apr_palloc(sess->conn->pool, sizeof(*callbacks) * 3);
881251881Speter
882251881Speter  /* Initialize the callbacks array. */
883251881Speter
884251881Speter  /* The username callback. */
885251881Speter  callbacks[0].id = SASL_CB_AUTHNAME;
886251881Speter  callbacks[0].proc = (int (*)(void))get_username_cb;
887251881Speter  callbacks[0].context = &cred_baton;
888251881Speter
889251881Speter  /* The password callback. */
890251881Speter  callbacks[1].id = SASL_CB_PASS;
891251881Speter  callbacks[1].proc = (int (*)(void))get_password_cb;
892251881Speter  callbacks[1].context = &cred_baton;
893251881Speter
894251881Speter  /* Mark the end of the array. */
895251881Speter  callbacks[2].id = SASL_CB_LIST_END;
896251881Speter  callbacks[2].proc = NULL;
897251881Speter  callbacks[2].context = NULL;
898251881Speter
899251881Speter  subpool = svn_pool_create(pool);
900251881Speter  do
901251881Speter    {
902251881Speter      svn_error_t *err;
903251881Speter
904251881Speter      /* If last_err was set to a non-empty string, it needs to be duplicated
905251881Speter         to the parent pool before the subpool is cleared. */
906251881Speter      if (*last_err)
907251881Speter        last_err = apr_pstrdup(pool, last_err);
908251881Speter      svn_pool_clear(subpool);
909251881Speter
910251881Speter      SVN_ERR(new_sasl_ctx(&sasl_ctx, sess->is_tunneled,
911251881Speter                           sess->hostname, local_addrport, remote_addrport,
912251881Speter                           callbacks, sess->conn->pool));
913251881Speter      err = try_auth(sess, sasl_ctx, &success, &last_err, mechstring,
914251881Speter                     subpool);
915251881Speter
916251881Speter      /* If we encountered an error while fetching credentials, that error
917251881Speter         has priority. */
918251881Speter      if (cred_baton.err)
919251881Speter        {
920251881Speter          svn_error_clear(err);
921251881Speter          return cred_baton.err;
922251881Speter        }
923251881Speter      if (cred_baton.no_more_creds
924251881Speter          || (! err && ! success && ! cred_baton.was_used))
925251881Speter        {
926251881Speter          svn_error_clear(err);
927251881Speter          /* If we ran out of authentication providers, or if we got a server
928251881Speter             error and our callbacks were never called, there's no point in
929251881Speter             retrying authentication.  Return the last error sent by the
930251881Speter             server. */
931251881Speter          if (*last_err)
932251881Speter            return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
933251881Speter                                     _("Authentication error from server: %s"),
934251881Speter                                     last_err);
935251881Speter          /* Hmm, we don't have a server error. Return a generic error. */
936251881Speter          return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
937251881Speter                                  _("Can't get username or password"));
938251881Speter        }
939251881Speter      if (err)
940251881Speter        {
941251881Speter          if (err->apr_err == SVN_ERR_RA_SVN_NO_MECHANISMS)
942251881Speter            {
943251881Speter              svn_error_clear(err);
944251881Speter
945251881Speter              /* We could not find a supported mechanism in the list sent by the
946251881Speter                 server. In many cases this happens because the client is missing
947251881Speter                 the CRAM-MD5 or ANONYMOUS plugins, in which case we can simply use
948251881Speter                 the built-in implementation. In all other cases this call will be
949251881Speter                 useless, but hey, at least we'll get consistent error messages. */
950289180Speter              return svn_error_trace(svn_ra_svn__do_internal_auth(sess, mechlist,
951289180Speter                                                                realm, pool));
952251881Speter            }
953251881Speter          return err;
954251881Speter        }
955251881Speter    }
956251881Speter  while (!success);
957251881Speter  svn_pool_destroy(subpool);
958251881Speter
959251881Speter  SVN_ERR(svn_ra_svn__enable_sasl_encryption(sess->conn, sasl_ctx, pool));
960251881Speter
961251881Speter  SVN_ERR(svn_auth_save_credentials(cred_baton.iterstate, pool));
962251881Speter
963251881Speter  return SVN_NO_ERROR;
964251881Speter}
965251881Speter
966251881Speter#endif /* SVN_HAVE_SASL */
967